@antelopejs/dms-ai 0.7.0 → 0.9.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/README.md +17 -7
- package/dist/lifecycle/skill-sources.js +17 -17
- package/dist/lifecycle/skill-sources.js.map +1 -1
- package/dist/pages/settings/availability.js.map +1 -1
- package/dist/pages/settings/sections.js +10 -0
- package/dist/pages/settings/sections.js.map +1 -1
- package/dist/routes/builder.d.ts +7 -1
- package/dist/routes/builder.js +3 -0
- package/dist/routes/builder.js.map +1 -1
- package/dist/routes/changes.d.ts +5 -4
- package/dist/routes/changes.js.map +1 -1
- package/dist/routes/codex-account.d.ts +3 -2
- package/dist/routes/codex-account.js.map +1 -1
- package/dist/routes/sidecar-results.d.ts +2 -2
- package/dist/routes/sidecar-results.js.map +1 -1
- package/dist/routes/status.js.map +1 -1
- package/dist/sidecar/http-client.js +3 -1
- package/dist/sidecar/http-client.js.map +1 -1
- package/dist/sidecar/index.d.ts +1 -0
- package/dist/sidecar/index.js +1 -0
- package/dist/sidecar/index.js.map +1 -1
- package/dist/sidecar/json-body.d.ts +4 -0
- package/dist/sidecar/json-body.js +33 -0
- package/dist/sidecar/json-body.js.map +1 -0
- package/dist/sidecar/settings-values.d.ts +5 -2
- package/dist/sidecar/settings-values.js.map +1 -1
- package/dist/vocabulary/tools.js +3 -0
- package/dist/vocabulary/tools.js.map +1 -1
- package/frontend-vue/app/chat/components/ChatView.vue +38 -37
- package/frontend-vue/app/chat/components/NoticeCard.vue +16 -0
- package/frontend-vue/app/chat/components/PanelHeader.vue +2 -6
- package/frontend-vue/app/chat/composables/conversation-events.ts +4 -0
- package/frontend-vue/app/chat/composables/useConversation.ts +6 -0
- package/frontend-vue/app/chat/types/protocol.ts +9 -1
- package/frontend-vue/app/chat/utils/status-label.ts +50 -0
- package/frontend-vue/app/chat/utils/tool-lexicon.ts +16 -0
- package/frontend-vue/app/components/SkillDetail.vue +1 -1
- package/frontend-vue/app/plugins/ai.client.ts +2 -2
- package/frontend-vue/app/runtime/assistant-actions.ts +4 -2
- package/frontend-vue/app/runtime/assistant-session.ts +5 -1
- package/frontend-vue/app/runtime/block-health.ts +135 -0
- package/frontend-vue/app/runtime/constants.ts +1 -0
- package/frontend-vue/app/runtime/navigation-complete.ts +47 -6
- package/frontend-vue/app/runtime/panel-intents.ts +6 -2
- package/frontend-vue/app/views/ChangeSetPanel.vue +1 -1
- package/frontend-vue/app/views/composables/useAiData.ts +10 -18
- package/frontend-vue/app/views/composables/useChangeSetActions.ts +5 -15
- package/frontend-vue/app/views/constants.ts +1 -1
- package/frontend-vue/app/views/types.ts +1 -1
- package/frontend-vue/build/backend-boundary.ts +54 -21
- package/frontend-vue/i18n/locales/dms-ai-pages-en-GB.json +7 -0
- package/frontend-vue/i18n/locales/dms-ai-pages-fr-FR.json +7 -0
- package/frontend-vue/i18n/locales/dms-ai-panel-en-GB.json +6 -1
- package/frontend-vue/i18n/locales/dms-ai-panel-fr-FR.json +6 -1
- package/frontend-vue/tests/assistant-actions.test.ts +37 -0
- package/frontend-vue/tests/block-health.test.ts +142 -0
- package/frontend-vue/tests/chat/conversation-events.test.ts +34 -0
- package/frontend-vue/tests/chat/tool-lexicon.test.ts +1 -0
- package/frontend-vue/tests/navigation.test.ts +67 -2
- package/frontend-vue/tests/support/api-error-presenter.ts +28 -0
- package/frontend-vue/tests/support/panel-harness.ts +2 -2
- package/frontend-vue/tests/views/api-data-reads.test.ts +2 -0
- package/frontend-vue/tests/views/api-error-presentation.test.ts +89 -0
- package/package.json +7 -6
- package/sidecar/dist/agent/provider.d.ts +5 -0
- package/sidecar/dist/agent/runner.d.ts +5 -0
- package/sidecar/dist/agent/runner.js +2 -0
- package/sidecar/dist/agent/runner.js.map +1 -1
- package/sidecar/dist/builder/builder-client.js +2 -2
- package/sidecar/dist/builder/builder-client.js.map +1 -1
- package/sidecar/dist/constants/builder.d.ts +2 -1
- package/sidecar/dist/constants/builder.js +12 -0
- package/sidecar/dist/constants/builder.js.map +1 -1
- package/sidecar/dist/constants/claude.d.ts +2 -0
- package/sidecar/dist/constants/claude.js +2 -0
- package/sidecar/dist/constants/claude.js.map +1 -1
- package/sidecar/dist/constants/mcp.d.ts +1 -1
- package/sidecar/dist/constants/mcp.js +1 -1
- package/sidecar/dist/constants/mcp.js.map +1 -1
- package/sidecar/dist/constants/native-binary.d.ts +5 -2
- package/sidecar/dist/constants/native-binary.js +5 -2
- package/sidecar/dist/constants/native-binary.js.map +1 -1
- package/sidecar/dist/constants/settings.d.ts +1 -0
- package/sidecar/dist/constants/settings.js +2 -0
- package/sidecar/dist/constants/settings.js.map +1 -1
- package/sidecar/dist/constants/timing.d.ts +1 -0
- package/sidecar/dist/constants/timing.js +1 -0
- package/sidecar/dist/constants/timing.js.map +1 -1
- package/sidecar/dist/constants/tool-kinds.js +1 -0
- package/sidecar/dist/constants/tool-kinds.js.map +1 -1
- package/sidecar/dist/logs/logs-client.js +2 -1
- package/sidecar/dist/logs/logs-client.js.map +1 -1
- package/sidecar/dist/logs/types.d.ts +2 -1
- package/sidecar/dist/mcp/define-tool.d.ts +1 -1
- package/sidecar/dist/mcp/define-tool.js.map +1 -1
- package/sidecar/dist/mcp/tools/builder-compose.d.ts +51 -0
- package/sidecar/dist/mcp/tools/builder-compose.js +96 -0
- package/sidecar/dist/mcp/tools/builder-compose.js.map +1 -0
- package/sidecar/dist/mcp/tools/builder-expr.d.ts +5 -0
- package/sidecar/dist/mcp/tools/builder-expr.js +25 -0
- package/sidecar/dist/mcp/tools/builder-expr.js.map +1 -0
- package/sidecar/dist/mcp/tools/builder-gate.js +2 -2
- package/sidecar/dist/mcp/tools/builder-gate.js.map +1 -1
- package/sidecar/dist/mcp/tools/builder-project.js +54 -1
- package/sidecar/dist/mcp/tools/builder-project.js.map +1 -1
- package/sidecar/dist/mcp/tools/builder-queries.d.ts +65 -0
- package/sidecar/dist/mcp/tools/builder-queries.js +73 -0
- package/sidecar/dist/mcp/tools/builder-queries.js.map +1 -0
- package/sidecar/dist/mcp/tools/builder-table-view-schema.d.ts +99 -0
- package/sidecar/dist/mcp/tools/builder-table-view-schema.js +118 -0
- package/sidecar/dist/mcp/tools/builder-table-view-schema.js.map +1 -0
- package/sidecar/dist/mcp/tools/builder-table-view.d.ts +102 -0
- package/sidecar/dist/mcp/tools/builder-table-view.js +24 -0
- package/sidecar/dist/mcp/tools/builder-table-view.js.map +1 -0
- package/sidecar/dist/mcp/tools/builder.d.ts +166 -20
- package/sidecar/dist/mcp/tools/builder.js +20 -73
- package/sidecar/dist/mcp/tools/builder.js.map +1 -1
- package/sidecar/dist/mcp/tools/navigate-to-page.js +12 -4
- package/sidecar/dist/mcp/tools/navigate-to-page.js.map +1 -1
- package/sidecar/dist/mcp/types.d.ts +2 -1
- package/sidecar/dist/pages/block-health.d.ts +14 -0
- package/sidecar/dist/pages/block-health.js +60 -0
- package/sidecar/dist/pages/block-health.js.map +1 -0
- package/sidecar/dist/prompts/agent.d.ts +1 -1
- package/sidecar/dist/prompts/agent.js +4 -3
- package/sidecar/dist/prompts/agent.js.map +1 -1
- package/sidecar/dist/protocol/events.d.ts +43 -0
- package/sidecar/dist/protocol/events.js +10 -0
- package/sidecar/dist/protocol/events.js.map +1 -1
- package/sidecar/dist/protocol/json.d.ts +4 -0
- package/sidecar/dist/protocol/json.js +29 -0
- package/sidecar/dist/protocol/json.js.map +1 -1
- package/sidecar/dist/protocol/messages.d.ts +57 -0
- package/sidecar/dist/protocol/messages.js +22 -0
- package/sidecar/dist/protocol/messages.js.map +1 -1
- package/sidecar/dist/providers/claude/config.d.ts +20 -3
- package/sidecar/dist/providers/claude/config.js +17 -5
- package/sidecar/dist/providers/claude/config.js.map +1 -1
- package/sidecar/dist/providers/claude/model-fallback.d.ts +9 -0
- package/sidecar/dist/providers/claude/model-fallback.js +37 -0
- package/sidecar/dist/providers/claude/model-fallback.js.map +1 -0
- package/sidecar/dist/providers/claude/provider.js +23 -22
- package/sidecar/dist/providers/claude/provider.js.map +1 -1
- package/sidecar/dist/providers/claude/resolve-binary.js +18 -13
- package/sidecar/dist/providers/claude/resolve-binary.js.map +1 -1
- package/sidecar/dist/providers/claude/session-stream.d.ts +23 -0
- package/sidecar/dist/providers/claude/session-stream.js +58 -0
- package/sidecar/dist/providers/claude/session-stream.js.map +1 -0
- package/sidecar/dist/server/chat-events.d.ts +16 -3
- package/sidecar/dist/server/chat-events.js +1 -0
- package/sidecar/dist/server/chat-events.js.map +1 -1
- package/sidecar/dist/server/http-io.d.ts +2 -1
- package/sidecar/dist/server/http-io.js.map +1 -1
- package/sidecar/dist/server/http-routes.js +4 -7
- package/sidecar/dist/server/http-routes.js.map +1 -1
- package/sidecar/dist/server/navigation-completer.d.ts +15 -2
- package/sidecar/dist/server/navigation-completer.js +42 -41
- package/sidecar/dist/server/navigation-completer.js.map +1 -1
- package/sidecar/dist/server/permission-events.d.ts +8 -4
- package/sidecar/dist/server/permission-events.js.map +1 -1
- package/sidecar/dist/server/routing.js +15 -2
- package/sidecar/dist/server/routing.js.map +1 -1
- package/sidecar/dist/server/safety-net.js +2 -2
- package/sidecar/dist/server/safety-net.js.map +1 -1
- package/sidecar/dist/server/tool-calls.d.ts +13 -4
- package/sidecar/dist/server/tool-calls.js.map +1 -1
- package/sidecar/dist/server/turns.d.ts +16 -2
- package/sidecar/dist/server/turns.js +13 -3
- package/sidecar/dist/server/turns.js.map +1 -1
- package/sidecar/dist/state/change-sets.js +4 -1
- package/sidecar/dist/state/change-sets.js.map +1 -1
- package/sidecar/dist/state/conversations.d.ts +1 -0
- package/sidecar/dist/state/conversations.js +8 -0
- package/sidecar/dist/state/conversations.js.map +1 -1
- package/sidecar/dist/state/settings-store.d.ts +1 -0
- package/sidecar/dist/state/settings-store.js +1 -0
- package/sidecar/dist/state/settings-store.js.map +1 -1
- package/sidecar/dist/state/settings-types.d.ts +1 -0
- package/sidecar/dist/state/store.js +8 -2
- package/sidecar/dist/state/store.js.map +1 -1
- package/sidecar/dist/state/types.d.ts +8 -1
- package/sidecar/dist/state/types.js.map +1 -1
- package/sidecar/package.json +2 -2
- package/skills/dms-ai/SKILL.md +1 -1
- package/skills/dms-builder-safe/REFERENCE.md +5 -6
- package/skills/dms-builder-safe/SKILL.md +12 -10
- package/skills/dms-external-database/REFERENCE.md +20 -8
- package/skills/dms-external-database/SKILL.md +64 -18
- package/skills/dms-page-design/SKILL.md +192 -0
- package/skills/dms-page-design/cases.json +57 -0
|
@@ -8,8 +8,11 @@ import {
|
|
|
8
8
|
watch,
|
|
9
9
|
} from "vue";
|
|
10
10
|
import type { ChatTransport } from "../../runtime/chat-transport";
|
|
11
|
-
import type {
|
|
12
|
-
|
|
11
|
+
import type {
|
|
12
|
+
PanelIntent,
|
|
13
|
+
PanelIntents,
|
|
14
|
+
StartConversationIntent,
|
|
15
|
+
} from "../../runtime/panel-intents";
|
|
13
16
|
import {
|
|
14
17
|
useChangeSetActions,
|
|
15
18
|
type ChatApi,
|
|
@@ -41,9 +44,10 @@ import type { ConversationSummary, CurrentPage } from "../types/conversation";
|
|
|
41
44
|
import type { ExpiredRequest } from "../types/permission";
|
|
42
45
|
import type { QuestionReply } from "../types/question";
|
|
43
46
|
import type { PendingAttachment } from "../utils/attachments";
|
|
44
|
-
import {
|
|
47
|
+
import { formatTokens } from "../utils/format";
|
|
45
48
|
import { newUuid } from "../utils/ids";
|
|
46
49
|
import { isRunStalled, runElapsedMs } from "../utils/run-status";
|
|
50
|
+
import { describeStatus } from "../utils/status-label";
|
|
47
51
|
import { bareToolName } from "../utils/tool-lexicon";
|
|
48
52
|
import { latestTodos } from "../utils/todos";
|
|
49
53
|
import ApprovalDock from "./ApprovalDock.vue";
|
|
@@ -176,27 +180,18 @@ const status = computed<PanelStatusKind>(() => {
|
|
|
176
180
|
return isRunning.value ? "working" : "ready";
|
|
177
181
|
});
|
|
178
182
|
|
|
179
|
-
const statusLabel = computed<string>(() =>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{ count: pendingCount.value },
|
|
192
|
-
pendingCount.value,
|
|
193
|
-
),
|
|
194
|
-
connecting: () => t("dms_ai.panel.status.connecting"),
|
|
195
|
-
offline: () => t("dms_ai.panel.status.offline"),
|
|
196
|
-
};
|
|
197
|
-
const label = labels[status.value]();
|
|
198
|
-
return status.value === "offline" ? label : `${label} · ${agentName.value}`;
|
|
199
|
-
});
|
|
183
|
+
const statusLabel = computed<string>(() =>
|
|
184
|
+
describeStatus(
|
|
185
|
+
{
|
|
186
|
+
kind: status.value,
|
|
187
|
+
elapsedMs: runElapsedMs(conversation.progress.value, nowMs.value),
|
|
188
|
+
pendingCount: pendingCount.value,
|
|
189
|
+
agentName: agentName.value,
|
|
190
|
+
model: conversation.model.value,
|
|
191
|
+
},
|
|
192
|
+
t,
|
|
193
|
+
),
|
|
194
|
+
);
|
|
200
195
|
|
|
201
196
|
const usageLabel = computed<string>(() => {
|
|
202
197
|
if (!isConnected.value) return t("dms_ai.panel.composer.usage_offline");
|
|
@@ -325,16 +320,13 @@ function setDraft(text: string): void {
|
|
|
325
320
|
composerRef.value?.setDraft(text);
|
|
326
321
|
}
|
|
327
322
|
|
|
328
|
-
/**
|
|
329
|
-
function
|
|
323
|
+
/** Runs `run` once the stream is up, if still here. */
|
|
324
|
+
function whenConnected(run: () => void): void {
|
|
330
325
|
const conversationId = activeId.value;
|
|
331
326
|
const apply = (): void => {
|
|
332
|
-
if (activeId.value === conversationId)
|
|
327
|
+
if (activeId.value === conversationId) run();
|
|
333
328
|
};
|
|
334
|
-
if (isConnected.value)
|
|
335
|
-
apply();
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
329
|
+
if (isConnected.value) return apply();
|
|
338
330
|
const stop = watch(isConnected, (connected) => {
|
|
339
331
|
if (!connected) return;
|
|
340
332
|
stop();
|
|
@@ -342,6 +334,20 @@ function setModeWhenConnected(next: ConversationStartMode): void {
|
|
|
342
334
|
});
|
|
343
335
|
}
|
|
344
336
|
|
|
337
|
+
function startConversation(intent: StartConversationIntent): void {
|
|
338
|
+
newConversation();
|
|
339
|
+
const { prompt = "", mode: next } = intent;
|
|
340
|
+
const sends = intent.send === true && prompt !== "";
|
|
341
|
+
if (next !== undefined || sends)
|
|
342
|
+
whenConnected(() => {
|
|
343
|
+
if (next !== undefined) mode.setMode(next);
|
|
344
|
+
if (sends) conversation.sendUserMessage(prompt);
|
|
345
|
+
});
|
|
346
|
+
if (sends) return;
|
|
347
|
+
if (prompt) void nextTick(() => setDraft(prompt));
|
|
348
|
+
else void nextTick(() => composerRef.value?.focus());
|
|
349
|
+
}
|
|
350
|
+
|
|
345
351
|
const INTENT_HANDLERS: Record<
|
|
346
352
|
PanelIntent["kind"],
|
|
347
353
|
(intent: PanelIntent) => void
|
|
@@ -350,12 +356,7 @@ const INTENT_HANDLERS: Record<
|
|
|
350
356
|
if (intent.kind === "open") switchConversation(intent.conversationId);
|
|
351
357
|
},
|
|
352
358
|
start: (intent) => {
|
|
353
|
-
|
|
354
|
-
if (intent.kind === "start" && intent.mode)
|
|
355
|
-
setModeWhenConnected(intent.mode);
|
|
356
|
-
if (intent.kind === "start" && intent.prompt)
|
|
357
|
-
void nextTick(() => setDraft(intent.prompt ?? ""));
|
|
358
|
-
else void nextTick(() => composerRef.value?.focus());
|
|
359
|
+
if (intent.kind === "start") startConversation(intent);
|
|
359
360
|
},
|
|
360
361
|
focus: () => void nextTick(() => composerRef.value?.focus()),
|
|
361
362
|
approvals: () => {
|
|
@@ -30,6 +30,7 @@ const LOOKS: Record<Notice["kind"], NoticeLook> = {
|
|
|
30
30
|
permission_expired: { tone: "neutral", icon: "i-ph-timer" },
|
|
31
31
|
question_skipped: { tone: "neutral", icon: "i-ph-arrow-bend-down-right" },
|
|
32
32
|
full_auto_ended: { tone: "neutral", icon: "i-ph-lightning-slash" },
|
|
33
|
+
model_fallback: { tone: "warning", icon: "i-ph-swap" },
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
const showErrors = ref(false);
|
|
@@ -116,6 +117,21 @@ const expiredTool = computed<string>(() => {
|
|
|
116
117
|
t("dms_ai.panel.notice.question_skipped", { header: notice.header })
|
|
117
118
|
}}
|
|
118
119
|
</template>
|
|
120
|
+
<template v-else-if="notice.kind === 'model_fallback'">
|
|
121
|
+
<b>
|
|
122
|
+
{{
|
|
123
|
+
t("dms_ai.panel.notice.model_fallback_title", {
|
|
124
|
+
model: notice.usedModel,
|
|
125
|
+
})
|
|
126
|
+
}}
|
|
127
|
+
</b>
|
|
128
|
+
{{
|
|
129
|
+
t("dms_ai.panel.notice.model_fallback_text", {
|
|
130
|
+
requested: notice.requestedModel,
|
|
131
|
+
model: notice.usedModel,
|
|
132
|
+
})
|
|
133
|
+
}}
|
|
134
|
+
</template>
|
|
119
135
|
<template v-else>
|
|
120
136
|
{{ t("dms_ai.panel.notice.full_auto_ended", { time }) }}
|
|
121
137
|
</template>
|
|
@@ -4,13 +4,9 @@ import { useChatI18n } from "../composables/useChatI18n";
|
|
|
4
4
|
import type { ActiveRule } from "../types/protocol";
|
|
5
5
|
import { ruleIcon } from "../utils/permission-view";
|
|
6
6
|
import { shortcutLabel } from "../utils/platform";
|
|
7
|
+
import type { StatusKind } from "../utils/status-label";
|
|
7
8
|
|
|
8
|
-
export type PanelStatusKind =
|
|
9
|
-
| "ready"
|
|
10
|
-
| "working"
|
|
11
|
-
| "waiting"
|
|
12
|
-
| "connecting"
|
|
13
|
-
| "offline";
|
|
9
|
+
export type PanelStatusKind = StatusKind;
|
|
14
10
|
|
|
15
11
|
interface Props {
|
|
16
12
|
title: string;
|
|
@@ -73,6 +73,7 @@ interface SnapshotEvent {
|
|
|
73
73
|
changeSets?: ChangeSetSummary[];
|
|
74
74
|
mode?: SnapshotModeWire;
|
|
75
75
|
totalTokens?: number;
|
|
76
|
+
model?: string;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
interface QueueStateEvent {
|
|
@@ -99,6 +100,7 @@ interface BlockPreviewEvent {
|
|
|
99
100
|
|
|
100
101
|
interface UsageEvent {
|
|
101
102
|
totalTokens?: number;
|
|
103
|
+
model?: string;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
type EventHandler = (state: ConversationState, event: never) => void;
|
|
@@ -290,6 +292,7 @@ function applySnapshot(state: ConversationState, event: SnapshotEvent): void {
|
|
|
290
292
|
state.changeSets.value = byId(event.changeSets ?? []);
|
|
291
293
|
state.mode.value = toModeState(event.mode) ?? state.mode.value;
|
|
292
294
|
state.totalTokens.value = event.totalTokens ?? state.totalTokens.value;
|
|
295
|
+
state.model.value = event.model ?? null;
|
|
293
296
|
state.isRunning.value = false;
|
|
294
297
|
state.progress.value = null;
|
|
295
298
|
state.isTurnInFlight.value = false;
|
|
@@ -379,6 +382,7 @@ function appendBlockPreview(
|
|
|
379
382
|
}
|
|
380
383
|
|
|
381
384
|
function applyUsage(state: ConversationState, event: UsageEvent): void {
|
|
385
|
+
if (event.model !== undefined) state.model.value = event.model;
|
|
382
386
|
if (typeof event.totalTokens !== "number") return;
|
|
383
387
|
state.totalTokens.value = event.totalTokens;
|
|
384
388
|
}
|
|
@@ -49,6 +49,8 @@ export interface UseConversationResult {
|
|
|
49
49
|
mode: Ref<ConversationModeState | null>;
|
|
50
50
|
/** Tokens this chat has used so far; null until the sidecar says. */
|
|
51
51
|
totalTokens: Ref<number | null>;
|
|
52
|
+
/** The model that answered last; null until a reply names it. */
|
|
53
|
+
model: Ref<string | null>;
|
|
52
54
|
sendUserMessage: (
|
|
53
55
|
content: string,
|
|
54
56
|
attachments?: PendingAttachment[],
|
|
@@ -76,6 +78,7 @@ export interface ConversationState {
|
|
|
76
78
|
changeSets: Ref<Record<string, ChangeSetSummary>>;
|
|
77
79
|
mode: Ref<ConversationModeState | null>;
|
|
78
80
|
totalTokens: Ref<number | null>;
|
|
81
|
+
model: Ref<string | null>;
|
|
79
82
|
/**
|
|
80
83
|
* When the calls of the stored transcript started and ended. A re-attach
|
|
81
84
|
* replays the running turn's events, and the replayed calls take their
|
|
@@ -285,6 +288,7 @@ function reset(state: ConversationState): void {
|
|
|
285
288
|
state.changeSets.value = {};
|
|
286
289
|
state.mode.value = null;
|
|
287
290
|
state.totalTokens.value = null;
|
|
291
|
+
state.model.value = null;
|
|
288
292
|
state.callTimes.clear();
|
|
289
293
|
}
|
|
290
294
|
|
|
@@ -302,6 +306,7 @@ function createConversationState(
|
|
|
302
306
|
changeSets: ref<Record<string, ChangeSetSummary>>({}),
|
|
303
307
|
mode: ref<ConversationModeState | null>(null),
|
|
304
308
|
totalTokens: ref<number | null>(null),
|
|
309
|
+
model: ref<string | null>(null),
|
|
305
310
|
callTimes: new Map(),
|
|
306
311
|
};
|
|
307
312
|
}
|
|
@@ -326,6 +331,7 @@ export function useConversation(
|
|
|
326
331
|
changeSets: state.changeSets,
|
|
327
332
|
mode: state.mode,
|
|
328
333
|
totalTokens: state.totalTokens,
|
|
334
|
+
model: state.model,
|
|
329
335
|
sendUserMessage: (content, attachments = [], extra = {}) =>
|
|
330
336
|
sendUserMessage(state, content, attachments, extra),
|
|
331
337
|
retry: () => retry(state),
|
|
@@ -165,11 +165,19 @@ export interface FullAutoEndedNotice {
|
|
|
165
165
|
timestampMs: number;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
export interface ModelFallbackNotice {
|
|
169
|
+
kind: "model_fallback";
|
|
170
|
+
requestedModel: string;
|
|
171
|
+
usedModel: string;
|
|
172
|
+
timestampMs: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
168
175
|
export type Notice =
|
|
169
176
|
| AutoFixNotice
|
|
170
177
|
| PermissionExpiredNotice
|
|
171
178
|
| QuestionSkippedNotice
|
|
172
|
-
| FullAutoEndedNotice
|
|
179
|
+
| FullAutoEndedNotice
|
|
180
|
+
| ModelFallbackNotice;
|
|
173
181
|
|
|
174
182
|
export interface FullAutoState {
|
|
175
183
|
duration: FullAutoDuration;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Translate } from "../composables/useChatI18n";
|
|
2
|
+
import { formatClock } from "./format";
|
|
3
|
+
|
|
4
|
+
export type StatusKind =
|
|
5
|
+
| "ready"
|
|
6
|
+
| "working"
|
|
7
|
+
| "waiting"
|
|
8
|
+
| "connecting"
|
|
9
|
+
| "offline";
|
|
10
|
+
|
|
11
|
+
export interface StatusFacts {
|
|
12
|
+
kind: StatusKind;
|
|
13
|
+
elapsedMs: number;
|
|
14
|
+
pendingCount: number;
|
|
15
|
+
agentName: string;
|
|
16
|
+
/** The model that answered last; null until a reply names it. */
|
|
17
|
+
model: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PART_SEPARATOR = " · ";
|
|
21
|
+
|
|
22
|
+
function stateLabel(facts: StatusFacts, t: Translate): string {
|
|
23
|
+
const labels: Record<StatusKind, () => string> = {
|
|
24
|
+
ready: () => t("dms_ai.panel.status.ready"),
|
|
25
|
+
working: () =>
|
|
26
|
+
t("dms_ai.panel.status.working", { time: formatClock(facts.elapsedMs) }),
|
|
27
|
+
waiting: () =>
|
|
28
|
+
t(
|
|
29
|
+
"dms_ai.panel.status.waiting",
|
|
30
|
+
{ count: facts.pendingCount },
|
|
31
|
+
facts.pendingCount,
|
|
32
|
+
),
|
|
33
|
+
connecting: () => t("dms_ai.panel.status.connecting"),
|
|
34
|
+
offline: () => t("dms_ai.panel.status.offline"),
|
|
35
|
+
};
|
|
36
|
+
return labels[facts.kind]();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The panel's status line: the state, then the agent, then the model that
|
|
41
|
+
* answered last ("Ready · Claude Code · claude-opus-5-5"). Offline has no
|
|
42
|
+
* agent to name.
|
|
43
|
+
*/
|
|
44
|
+
export function describeStatus(facts: StatusFacts, t: Translate): string {
|
|
45
|
+
const label = stateLabel(facts, t);
|
|
46
|
+
if (facts.kind === "offline") return label;
|
|
47
|
+
const parts = [label, facts.agentName];
|
|
48
|
+
if (facts.model !== null) parts.push(facts.model);
|
|
49
|
+
return parts.join(PART_SEPARATOR);
|
|
50
|
+
}
|
|
@@ -374,6 +374,16 @@ const BUILDER_TOOLS: Record<string, ToolLexiconEntry> = {
|
|
|
374
374
|
icon: ICONS.BLOCK,
|
|
375
375
|
target: firstOf("path"),
|
|
376
376
|
},
|
|
377
|
+
BuilderComposePage: {
|
|
378
|
+
verb: "compose_page",
|
|
379
|
+
icon: ICONS.BLOCK,
|
|
380
|
+
target: firstOf("pageRef"),
|
|
381
|
+
},
|
|
382
|
+
BuilderConfigureTableView: {
|
|
383
|
+
verb: "configure_table_view",
|
|
384
|
+
icon: ICONS.BLOCK,
|
|
385
|
+
target: firstOf("pageRef"),
|
|
386
|
+
},
|
|
377
387
|
BuilderMoveBlock: {
|
|
378
388
|
verb: "move_block",
|
|
379
389
|
icon: ICONS.BLOCK,
|
|
@@ -475,6 +485,12 @@ const BUILDER_TOOLS: Record<string, ToolLexiconEntry> = {
|
|
|
475
485
|
icon: ICONS.DATA,
|
|
476
486
|
target: firstOf("name"),
|
|
477
487
|
},
|
|
488
|
+
BuilderPreviewQuery: {
|
|
489
|
+
verb: "preview_query",
|
|
490
|
+
icon: ICONS.DATA,
|
|
491
|
+
target: firstOf("name"),
|
|
492
|
+
isReadOnly: true,
|
|
493
|
+
},
|
|
478
494
|
BuilderConfigureQuery: {
|
|
479
495
|
verb: "configure_query",
|
|
480
496
|
icon: ICONS.DATA,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { Tone } from "#dms-
|
|
2
|
+
import type { Tone } from "#dms-core/app/types/tone";
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
import { renderDocument } from "../chat/utils/markdown";
|
|
5
5
|
import { useAssistant } from "../views/composables/useAssistant";
|
|
@@ -268,8 +268,8 @@ function createSession(parts: SessionParts): AssistantSession {
|
|
|
268
268
|
intents.push({ kind: "open", conversationId });
|
|
269
269
|
panel.open();
|
|
270
270
|
},
|
|
271
|
-
startConversation: (prompt, mode) => {
|
|
272
|
-
intents.push({ kind: "start", prompt, mode });
|
|
271
|
+
startConversation: (prompt, mode, send) => {
|
|
272
|
+
intents.push({ kind: "start", prompt, mode, send });
|
|
273
273
|
panel.open();
|
|
274
274
|
},
|
|
275
275
|
holdStream: parts.holds.hold,
|
|
@@ -14,10 +14,12 @@ export interface PublicAssistantActions {
|
|
|
14
14
|
/**
|
|
15
15
|
* Opens the assistant panel on a new conversation with `prompt` in the composer, unsent. With
|
|
16
16
|
* `mode`, the conversation is set to it first: `plan` makes the agent end with a plan to approve.
|
|
17
|
+
* With `send`, the prompt is sent as the first message once the stream is up, and the composer
|
|
18
|
+
* stays empty.
|
|
17
19
|
*/
|
|
18
20
|
startConversation: (
|
|
19
21
|
prompt: string,
|
|
20
|
-
options?: { mode?: ConversationStartMode },
|
|
22
|
+
options?: { mode?: ConversationStartMode; send?: boolean },
|
|
21
23
|
) => void;
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -36,6 +38,6 @@ export function publicAssistantActions(
|
|
|
36
38
|
return session.value !== null;
|
|
37
39
|
},
|
|
38
40
|
startConversation: (prompt, options) =>
|
|
39
|
-
session.value?.startConversation(prompt, options?.mode),
|
|
41
|
+
session.value?.startConversation(prompt, options?.mode, options?.send),
|
|
40
42
|
};
|
|
41
43
|
}
|
|
@@ -38,7 +38,11 @@ export interface AssistantSession {
|
|
|
38
38
|
* Opens the panel on a new conversation, the composer optionally prefilled, and the
|
|
39
39
|
* conversation set to `mode` first when one is given.
|
|
40
40
|
*/
|
|
41
|
-
startConversation: (
|
|
41
|
+
startConversation: (
|
|
42
|
+
prompt?: string,
|
|
43
|
+
mode?: ConversationStartMode,
|
|
44
|
+
send?: boolean,
|
|
45
|
+
) => void;
|
|
42
46
|
/**
|
|
43
47
|
* Keeps the tab's stream open while the panel is closed, until the returned
|
|
44
48
|
* function releases it: the command palette's answer streams through it.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the DMS data blocks (charts, KPIs, lists, tables) say about themselves.
|
|
3
|
+
* Each states its kind, its layout path and what its reader sees on its root
|
|
4
|
+
* element; a test harness reads the same attributes. A host that predates
|
|
5
|
+
* them states nothing, and the page reads as having no data block.
|
|
6
|
+
*/
|
|
7
|
+
const BLOCK_SELECTOR = "[data-dms-block-state]";
|
|
8
|
+
const STATE_ATTRIBUTE = "data-dms-block-state";
|
|
9
|
+
const TYPE_ATTRIBUTE = "data-dms-block-type";
|
|
10
|
+
const PATH_ATTRIBUTE = "data-dms-block";
|
|
11
|
+
const UNKNOWN_TYPE = "Block";
|
|
12
|
+
|
|
13
|
+
/** How long the host waits for a page's blocks to stop loading. */
|
|
14
|
+
export const BLOCK_SETTLE_BUDGET_MS = 15_000;
|
|
15
|
+
/** How long the blocks of a page must stay unchanged before it counts as settled. */
|
|
16
|
+
export const BLOCK_QUIET_MS = 700;
|
|
17
|
+
|
|
18
|
+
const BLOCK_STATES = ["loading", "ok", "empty", "error"] as const;
|
|
19
|
+
type BlockState = (typeof BLOCK_STATES)[number];
|
|
20
|
+
type ReportedState = Exclude<BlockState, "ok">;
|
|
21
|
+
|
|
22
|
+
export interface BlockFact {
|
|
23
|
+
path?: string;
|
|
24
|
+
type: string;
|
|
25
|
+
state: BlockState;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ReportedBlock extends BlockFact {
|
|
29
|
+
state: ReportedState;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PageBlocks {
|
|
33
|
+
status: "settled" | "timeout" | "superseded";
|
|
34
|
+
checked: number;
|
|
35
|
+
/** The blocks showing no data: empty, failed, or still loading. */
|
|
36
|
+
blocks: ReportedBlock[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isBlockState(value: string | null): value is BlockState {
|
|
40
|
+
return BLOCK_STATES.some((state) => state === value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isReported(block: BlockFact): block is ReportedBlock {
|
|
44
|
+
return block.state !== "ok";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readBlock(element: Element): BlockFact | null {
|
|
48
|
+
const state = element.getAttribute(STATE_ATTRIBUTE);
|
|
49
|
+
if (!isBlockState(state)) return null;
|
|
50
|
+
const path = element.getAttribute(PATH_ATTRIBUTE);
|
|
51
|
+
return {
|
|
52
|
+
...(path === null ? {} : { path }),
|
|
53
|
+
type: element.getAttribute(TYPE_ATTRIBUTE) ?? UNKNOWN_TYPE,
|
|
54
|
+
state,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The data blocks under `root`, in document order. */
|
|
59
|
+
export function readBlocks(root: ParentNode): BlockFact[] {
|
|
60
|
+
return [...root.querySelectorAll(BLOCK_SELECTOR)].flatMap((element) => {
|
|
61
|
+
const block = readBlock(element);
|
|
62
|
+
return block === null ? [] : [block];
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function holdsBlock(node: Node): boolean {
|
|
67
|
+
return (
|
|
68
|
+
node instanceof Element &&
|
|
69
|
+
(node.matches(BLOCK_SELECTOR) ||
|
|
70
|
+
node.querySelector(BLOCK_SELECTOR) !== null)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function changesBlocks(record: MutationRecord): boolean {
|
|
75
|
+
if (record.type === "attributes") return true;
|
|
76
|
+
return [...record.addedNodes, ...record.removedNodes].some(holdsBlock);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface AwaitSettledBlocksOptions {
|
|
80
|
+
root: Document;
|
|
81
|
+
/** Aborted when another navigation starts: the wait ends as `superseded`. */
|
|
82
|
+
signal: AbortSignal;
|
|
83
|
+
budgetMs?: number;
|
|
84
|
+
quietMs?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Resolves when the blocks of the page have stopped changing for `quietMs`
|
|
89
|
+
* and none is loading, or at `budgetMs` with the blocks still loading named,
|
|
90
|
+
* or at once when `signal` aborts.
|
|
91
|
+
*/
|
|
92
|
+
export function awaitSettledBlocks(
|
|
93
|
+
options: AwaitSettledBlocksOptions,
|
|
94
|
+
): Promise<PageBlocks> {
|
|
95
|
+
const { root, signal } = options;
|
|
96
|
+
const budgetMs = options.budgetMs ?? BLOCK_SETTLE_BUDGET_MS;
|
|
97
|
+
const quietMs = options.quietMs ?? BLOCK_QUIET_MS;
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
let quietTimer: ReturnType<typeof setTimeout> | undefined;
|
|
100
|
+
const finish = (status: PageBlocks["status"]): void => {
|
|
101
|
+
clearTimeout(quietTimer);
|
|
102
|
+
clearTimeout(budgetTimer);
|
|
103
|
+
observer.disconnect();
|
|
104
|
+
signal.removeEventListener("abort", onAbort);
|
|
105
|
+
const facts = readBlocks(root);
|
|
106
|
+
resolve({
|
|
107
|
+
status,
|
|
108
|
+
checked: facts.length,
|
|
109
|
+
blocks: facts.filter(isReported),
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
const onQuiet = (): void => {
|
|
113
|
+
if (readBlocks(root).every((block) => block.state !== "loading"))
|
|
114
|
+
finish("settled");
|
|
115
|
+
};
|
|
116
|
+
const restartQuiet = (): void => {
|
|
117
|
+
clearTimeout(quietTimer);
|
|
118
|
+
quietTimer = setTimeout(onQuiet, quietMs);
|
|
119
|
+
};
|
|
120
|
+
const onAbort = (): void => finish("superseded");
|
|
121
|
+
const observer = new MutationObserver((records) => {
|
|
122
|
+
if (records.some(changesBlocks)) restartQuiet();
|
|
123
|
+
});
|
|
124
|
+
const budgetTimer = setTimeout(() => finish("timeout"), budgetMs);
|
|
125
|
+
if (signal.aborted) return finish("superseded");
|
|
126
|
+
signal.addEventListener("abort", onAbort);
|
|
127
|
+
observer.observe(root, {
|
|
128
|
+
subtree: true,
|
|
129
|
+
childList: true,
|
|
130
|
+
attributes: true,
|
|
131
|
+
attributeFilter: [STATE_ATTRIBUTE],
|
|
132
|
+
});
|
|
133
|
+
restartQuiet();
|
|
134
|
+
});
|
|
135
|
+
}
|
|
@@ -40,6 +40,7 @@ export const HELLO_MESSAGE_TYPE = "hello";
|
|
|
40
40
|
export const LOG_PREFIX = "[dms-ai]";
|
|
41
41
|
export const HOST_STATE_UPDATE_TYPE = "host_state_update";
|
|
42
42
|
export const HOST_NAVIGATION_COMPLETE_TYPE = "host_navigation_complete";
|
|
43
|
+
export const HOST_PAGE_BLOCKS_TYPE = "host_page_blocks";
|
|
43
44
|
export const HOST_COMMAND_NAVIGATE_TYPE = "host_command_navigate";
|
|
44
45
|
/** Frames of the tab's stream meant for the dashboard; every other one is the chat's. */
|
|
45
46
|
export const HOST_COMMAND_TYPES: ReadonlySet<string> = new Set([
|
|
@@ -1,22 +1,63 @@
|
|
|
1
1
|
import { router } from "@inertiajs/vue3";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
awaitSettledBlocks,
|
|
4
|
+
BLOCK_SETTLE_BUDGET_MS,
|
|
5
|
+
type PageBlocks,
|
|
6
|
+
} from "./block-health";
|
|
7
|
+
import {
|
|
8
|
+
HOST_NAVIGATION_COMPLETE_TYPE,
|
|
9
|
+
HOST_PAGE_BLOCKS_TYPE,
|
|
10
|
+
} from "./constants";
|
|
3
11
|
|
|
4
12
|
interface HostNavigationCompleteMessage {
|
|
5
13
|
type: typeof HOST_NAVIGATION_COMPLETE_TYPE;
|
|
6
14
|
path: string;
|
|
15
|
+
/** A `host_page_blocks` message for this path follows within this long. */
|
|
16
|
+
blocksWithinMs: number;
|
|
7
17
|
}
|
|
8
18
|
|
|
19
|
+
interface HostPageBlocksMessage extends PageBlocks {
|
|
20
|
+
type: typeof HOST_PAGE_BLOCKS_TYPE;
|
|
21
|
+
path: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type NavigationMessage = HostNavigationCompleteMessage | HostPageBlocksMessage;
|
|
25
|
+
|
|
9
26
|
interface InstallNavigationCompleteEmitterOptions {
|
|
10
|
-
send: (msg:
|
|
27
|
+
send: (msg: NavigationMessage) => void;
|
|
11
28
|
}
|
|
12
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Reports each completed visit at once, then what the page's data blocks
|
|
32
|
+
* showed once they settled, so the assistant learns of empty charts without
|
|
33
|
+
* a screenshot.
|
|
34
|
+
*/
|
|
13
35
|
export function installNavigationCompleteEmitter(
|
|
14
36
|
options: InstallNavigationCompleteEmitterOptions,
|
|
15
37
|
): () => void {
|
|
16
|
-
|
|
38
|
+
let settling = new AbortController();
|
|
39
|
+
let stopped = false;
|
|
40
|
+
const stopNavigation = router.on("navigate", (event) => {
|
|
41
|
+
const path = new URL(event.detail.page.url, window.location.origin)
|
|
42
|
+
.pathname;
|
|
43
|
+
settling.abort();
|
|
44
|
+
settling = new AbortController();
|
|
17
45
|
options.send({
|
|
18
46
|
type: HOST_NAVIGATION_COMPLETE_TYPE,
|
|
19
|
-
path
|
|
20
|
-
|
|
21
|
-
|
|
47
|
+
path,
|
|
48
|
+
blocksWithinMs: BLOCK_SETTLE_BUDGET_MS,
|
|
49
|
+
});
|
|
50
|
+
void awaitSettledBlocks({
|
|
51
|
+
root: document,
|
|
52
|
+
signal: settling.signal,
|
|
53
|
+
}).then((blocks) => {
|
|
54
|
+
if (!stopped)
|
|
55
|
+
options.send({ type: HOST_PAGE_BLOCKS_TYPE, path, ...blocks });
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
return () => {
|
|
59
|
+
stopped = true;
|
|
60
|
+
settling.abort();
|
|
61
|
+
stopNavigation();
|
|
62
|
+
};
|
|
22
63
|
}
|
|
@@ -7,11 +7,15 @@ interface OpenConversationIntent {
|
|
|
7
7
|
conversationId: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Opens the panel on a new conversation, in `mode` when given. The prompt is put in the composer,
|
|
12
|
+
* or sent as the first message when `send` is set.
|
|
13
|
+
*/
|
|
14
|
+
export interface StartConversationIntent {
|
|
12
15
|
kind: "start";
|
|
13
16
|
prompt?: string;
|
|
14
17
|
mode?: ConversationStartMode;
|
|
18
|
+
send?: boolean;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
/** Puts the caret in the composer of the current conversation. */
|
|
@@ -7,7 +7,7 @@ import { useAssistant } from "./composables/useAssistant";
|
|
|
7
7
|
import { useChangeSetActions } from "./composables/useChangeSetActions";
|
|
8
8
|
import { useViewFormat } from "./composables/useViewFormat";
|
|
9
9
|
import { AI_ROUTES } from "./constants";
|
|
10
|
-
import type { Tone } from "#dms-
|
|
10
|
+
import type { Tone } from "#dms-core/app/types/tone";
|
|
11
11
|
import type {
|
|
12
12
|
ChangeSetDetail,
|
|
13
13
|
ChangeSetSummary,
|