@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
|
@@ -40,16 +40,10 @@ interface FetchErrorResponse {
|
|
|
40
40
|
status?: number;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
interface FetchErrorBody {
|
|
44
|
-
message?: unknown;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
43
|
interface FetchErrorLike {
|
|
48
44
|
status?: number;
|
|
49
45
|
statusCode?: number;
|
|
50
46
|
response?: FetchErrorResponse;
|
|
51
|
-
data?: FetchErrorBody;
|
|
52
|
-
message?: string;
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
/** The HTTP status of a `$authFetch` rejection, if it got an answer. */
|
|
@@ -60,14 +54,6 @@ function errorStatus(error: unknown): number | undefined {
|
|
|
60
54
|
);
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
/** The server's `{ message }`, else the error's own text. */
|
|
64
|
-
export function errorMessage(error: unknown): string {
|
|
65
|
-
const candidate = error as FetchErrorLike | null;
|
|
66
|
-
const serverMessage = candidate?.data?.message;
|
|
67
|
-
if (typeof serverMessage === "string") return serverMessage;
|
|
68
|
-
return candidate?.message ?? String(error);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
57
|
export function classifyFailure(error: unknown): RequestFailure {
|
|
72
58
|
return errorStatus(error) === HTTP_SERVICE_UNAVAILABLE
|
|
73
59
|
? "unavailable"
|
|
@@ -80,15 +66,21 @@ export function classifyFailure(error: unknown): RequestFailure {
|
|
|
80
66
|
* whether the assistant isn't running (503) rather than an error.
|
|
81
67
|
*/
|
|
82
68
|
function describeFailure(result: FailingRead): FailureDescription {
|
|
69
|
+
const { resolveError } = useApiErrorPresenter();
|
|
70
|
+
const notice = computed(() =>
|
|
71
|
+
result.error.value
|
|
72
|
+
? resolveError(result.error.value, {
|
|
73
|
+
fallbackKey: `dms_ai.views.${classifyFailure(result.error.value)}.description`,
|
|
74
|
+
})
|
|
75
|
+
: null,
|
|
76
|
+
);
|
|
83
77
|
return {
|
|
84
78
|
failure: computed(() =>
|
|
85
|
-
|
|
79
|
+
notice.value && result.status.value !== "pending"
|
|
86
80
|
? classifyFailure(result.error.value)
|
|
87
81
|
: null,
|
|
88
82
|
),
|
|
89
|
-
message: computed(() =>
|
|
90
|
-
result.error.value ? errorMessage(result.error.value) : "",
|
|
91
|
-
),
|
|
83
|
+
message: computed(() => notice.value?.message ?? ""),
|
|
92
84
|
};
|
|
93
85
|
}
|
|
94
86
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ref, type Ref } from "vue";
|
|
2
2
|
import { AI_ROUTES } from "../constants";
|
|
3
3
|
import type { ChangeSetSummary } from "../types";
|
|
4
|
-
import { classifyFailure
|
|
4
|
+
import { classifyFailure } from "./useAiData";
|
|
5
5
|
|
|
6
6
|
export interface ChangeSetActions {
|
|
7
7
|
isBusy: Ref<boolean>;
|
|
@@ -28,23 +28,11 @@ function changeSetPath(changeSetId: string, action: string): string {
|
|
|
28
28
|
export function useChangeSetActions(): ChangeSetActions {
|
|
29
29
|
const { t } = useI18n();
|
|
30
30
|
const toast = useToast();
|
|
31
|
+
const { presentError } = useApiErrorPresenter();
|
|
31
32
|
const { $authFetch } = useAuthFetch();
|
|
32
33
|
const { confirmFrom } = useConfirmFrom();
|
|
33
34
|
const isBusy = ref(false);
|
|
34
35
|
|
|
35
|
-
function failureToast(error: unknown): void {
|
|
36
|
-
const failure = classifyFailure(error);
|
|
37
|
-
toast.add({
|
|
38
|
-
title: t(`dms_ai.views.${failure}.title`),
|
|
39
|
-
description:
|
|
40
|
-
failure === "error"
|
|
41
|
-
? errorMessage(error)
|
|
42
|
-
: t("dms_ai.views.unavailable.description"),
|
|
43
|
-
color: "error",
|
|
44
|
-
icon: "i-ph-warning-circle",
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
36
|
async function redo(changeSet: ChangeSetSummary): Promise<void> {
|
|
49
37
|
isBusy.value = true;
|
|
50
38
|
try {
|
|
@@ -59,7 +47,9 @@ export function useChangeSetActions(): ChangeSetActions {
|
|
|
59
47
|
});
|
|
60
48
|
await refreshPageBlocks();
|
|
61
49
|
} catch (error) {
|
|
62
|
-
|
|
50
|
+
presentError(error, {
|
|
51
|
+
fallbackKey: `dms_ai.views.${classifyFailure(error)}.description`,
|
|
52
|
+
});
|
|
63
53
|
} finally {
|
|
64
54
|
isBusy.value = false;
|
|
65
55
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
-
import type {
|
|
3
|
+
import type { DmsVitePluginEntry } from "#dms/frontend-build";
|
|
4
|
+
|
|
5
|
+
type VitePlugin = Extract<DmsVitePluginEntry, { name: string }>;
|
|
4
6
|
|
|
5
7
|
const BACKEND_PACKAGE = "@antelopejs/dms-ai";
|
|
6
8
|
const BACKEND_SOURCE = "src";
|
|
@@ -8,20 +10,32 @@ const QUERY_OR_HASH = /[?#].*$/;
|
|
|
8
10
|
|
|
9
11
|
const backendRoots = new Map<string, string | null>();
|
|
10
12
|
|
|
13
|
+
/** Whether a parsed manifest names its package. */
|
|
14
|
+
function isNamedManifest(value: unknown): value is { name: string } {
|
|
15
|
+
return (
|
|
16
|
+
typeof value === "object" &&
|
|
17
|
+
value !== null &&
|
|
18
|
+
"name" in value &&
|
|
19
|
+
typeof value.name === "string"
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The package name a `package.json` declares, or `undefined`. */
|
|
11
24
|
function packageName(manifest: string): string | undefined {
|
|
12
25
|
try {
|
|
13
|
-
const parsed:
|
|
14
|
-
|
|
15
|
-
);
|
|
16
|
-
return parsed.name;
|
|
26
|
+
const parsed: unknown = JSON.parse(readFileSync(manifest, "utf8"));
|
|
27
|
+
return isNamedManifest(parsed) ? parsed.name : undefined;
|
|
17
28
|
} catch {
|
|
18
29
|
return undefined;
|
|
19
30
|
}
|
|
20
31
|
}
|
|
21
32
|
|
|
33
|
+
/** The directory of the backend package that holds `directory`, or null. */
|
|
22
34
|
function backendRoot(directory: string): string | null {
|
|
23
35
|
const known = backendRoots.get(directory);
|
|
24
|
-
if (known !== undefined)
|
|
36
|
+
if (known !== undefined) {
|
|
37
|
+
return known;
|
|
38
|
+
}
|
|
25
39
|
const manifest = join(directory, "package.json");
|
|
26
40
|
const parent = dirname(directory);
|
|
27
41
|
let root: string | null = null;
|
|
@@ -38,12 +52,13 @@ function backendRoot(directory: string): string | null {
|
|
|
38
52
|
* The real path of a resolved module id when it lies under the `src`
|
|
39
53
|
* directory of the backend package, or `undefined`. The package that owns the
|
|
40
54
|
* file decides, not a path, so the answer is the same in the repository and in
|
|
41
|
-
* the workspace
|
|
42
|
-
* not files are ignored.
|
|
55
|
+
* the build workspace. Virtual ids and ids that are not files are ignored.
|
|
43
56
|
*/
|
|
44
57
|
function backendSourceFile(id: string): string | undefined {
|
|
45
58
|
const path = id.replace(QUERY_OR_HASH, "");
|
|
46
|
-
if (!isAbsolute(path))
|
|
59
|
+
if (!isAbsolute(path)) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
47
62
|
let real: string;
|
|
48
63
|
try {
|
|
49
64
|
real = realpathSync(path);
|
|
@@ -51,38 +66,56 @@ function backendSourceFile(id: string): string | undefined {
|
|
|
51
66
|
return undefined;
|
|
52
67
|
}
|
|
53
68
|
const root = backendRoot(dirname(real));
|
|
54
|
-
if (root === null)
|
|
69
|
+
if (root === null) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
55
72
|
return relative(root, real).split(sep)[0] === BACKEND_SOURCE
|
|
56
73
|
? real
|
|
57
74
|
: undefined;
|
|
58
75
|
}
|
|
59
76
|
|
|
60
77
|
/**
|
|
61
|
-
* Fails the build
|
|
62
|
-
*
|
|
63
|
-
* backend only through its HTTP
|
|
64
|
-
* chain returns, so an alias is seen through
|
|
65
|
-
* returns is followed to its real path, and it runs
|
|
66
|
-
* (`enforce: "pre"`).
|
|
78
|
+
* Fails the build and the dev server when a module of the frontend resolves to
|
|
79
|
+
* a file under the `src` of the backend package: the browser reaches the
|
|
80
|
+
* backend only through its HTTP interface. It checks what the whole resolution
|
|
81
|
+
* chain returns, an id marked external included, so an alias is seen through
|
|
82
|
+
* and a symlink the resolver returns is followed to its real path, and it runs
|
|
83
|
+
* before the other plugins (`enforce: "pre"`). Vite builds workers with their
|
|
84
|
+
* own plugin list, so the plugin adds itself there too.
|
|
85
|
+
*
|
|
86
|
+
* antelopejs-sync writes this file from `@antelopejs/tooling-configs` and
|
|
87
|
+
* restores it on every run: change the plugin in the pack, not here.
|
|
67
88
|
*/
|
|
68
|
-
export function backendBoundary():
|
|
89
|
+
export function backendBoundary(): VitePlugin {
|
|
90
|
+
return {
|
|
91
|
+
...boundaryCheck(),
|
|
92
|
+
config: () => ({ worker: { plugins: () => [boundaryCheck()] } }),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The check alone, as a worker build runs it. */
|
|
97
|
+
function boundaryCheck(): VitePlugin {
|
|
69
98
|
return {
|
|
70
|
-
name:
|
|
99
|
+
name: `${BACKEND_PACKAGE}:backend-boundary`,
|
|
71
100
|
enforce: "pre",
|
|
72
101
|
async resolveId(source, importer, options) {
|
|
73
102
|
const resolved = await this.resolve(source, importer, {
|
|
74
103
|
...options,
|
|
75
104
|
skipSelf: true,
|
|
76
105
|
});
|
|
77
|
-
if (resolved === null
|
|
106
|
+
if (resolved === null) {
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
78
109
|
const file = backendSourceFile(resolved.id);
|
|
79
|
-
if (file === undefined)
|
|
110
|
+
if (file === undefined) {
|
|
111
|
+
return resolved;
|
|
112
|
+
}
|
|
80
113
|
const from =
|
|
81
114
|
importer === undefined
|
|
82
115
|
? "an entry point"
|
|
83
116
|
: importer.replace(QUERY_OR_HASH, "");
|
|
84
117
|
return this.error(
|
|
85
|
-
`Backend boundary: ${from} imports ${file}, which is under src of ${BACKEND_PACKAGE}. The frontend reaches the backend only through its HTTP
|
|
118
|
+
`Backend boundary: ${from} imports ${file}, which is under the src of ${BACKEND_PACKAGE}. The frontend reaches the backend only through its HTTP interface.`,
|
|
86
119
|
);
|
|
87
120
|
},
|
|
88
121
|
};
|
|
@@ -314,6 +314,8 @@
|
|
|
314
314
|
"BuilderPageStructure": "Read page",
|
|
315
315
|
"BuilderCreatePage": "Create page",
|
|
316
316
|
"BuilderConfigurePage": "Configure page",
|
|
317
|
+
"BuilderComposePage": "Compose page",
|
|
318
|
+
"BuilderConfigureTableView": "Configure table view",
|
|
317
319
|
"BuilderDeletePage": "Delete page",
|
|
318
320
|
"BuilderAddBlock": "Add block",
|
|
319
321
|
"BuilderConfigureBlock": "Configure block",
|
|
@@ -332,6 +334,7 @@
|
|
|
332
334
|
"BuilderRemoveField": "Remove field",
|
|
333
335
|
"BuilderQueryTemplates": "Read query templates",
|
|
334
336
|
"BuilderAddQuery": "Add query",
|
|
337
|
+
"BuilderPreviewQuery": "Preview query",
|
|
335
338
|
"BuilderConfigureQuery": "Configure query",
|
|
336
339
|
"BuilderRemoveQuery": "Remove query",
|
|
337
340
|
"UndoChangeSet": "Undo change set",
|
|
@@ -462,6 +465,10 @@
|
|
|
462
465
|
"high": "High"
|
|
463
466
|
}
|
|
464
467
|
},
|
|
468
|
+
"claude_model": {
|
|
469
|
+
"label": "Claude model",
|
|
470
|
+
"description": "The model Claude Code runs on. Thinking sets its reasoning effort."
|
|
471
|
+
},
|
|
465
472
|
"mode": {
|
|
466
473
|
"label": "Approval mode",
|
|
467
474
|
"description": "What the assistant may do without asking you first.",
|
|
@@ -314,6 +314,8 @@
|
|
|
314
314
|
"BuilderPageStructure": "Lire une page",
|
|
315
315
|
"BuilderCreatePage": "Créer une page",
|
|
316
316
|
"BuilderConfigurePage": "Configurer une page",
|
|
317
|
+
"BuilderComposePage": "Composer une page",
|
|
318
|
+
"BuilderConfigureTableView": "Configurer une vue tableau",
|
|
317
319
|
"BuilderDeletePage": "Supprimer une page",
|
|
318
320
|
"BuilderAddBlock": "Ajouter un bloc",
|
|
319
321
|
"BuilderConfigureBlock": "Configurer un bloc",
|
|
@@ -332,6 +334,7 @@
|
|
|
332
334
|
"BuilderRemoveField": "Retirer un champ",
|
|
333
335
|
"BuilderQueryTemplates": "Lire les modèles de requêtes",
|
|
334
336
|
"BuilderAddQuery": "Ajouter une requête",
|
|
337
|
+
"BuilderPreviewQuery": "Aperçu de la requête",
|
|
335
338
|
"BuilderConfigureQuery": "Configurer une requête",
|
|
336
339
|
"BuilderRemoveQuery": "Retirer une requête",
|
|
337
340
|
"UndoChangeSet": "Annuler un lot de modifications",
|
|
@@ -462,6 +465,10 @@
|
|
|
462
465
|
"high": "Élevée"
|
|
463
466
|
}
|
|
464
467
|
},
|
|
468
|
+
"claude_model": {
|
|
469
|
+
"label": "Modèle Claude",
|
|
470
|
+
"description": "Le modèle sur lequel tourne Claude Code. La réflexion règle son niveau d'effort."
|
|
471
|
+
},
|
|
465
472
|
"mode": {
|
|
466
473
|
"label": "Mode de validation",
|
|
467
474
|
"description": "Ce que l'assistant peut faire sans vous demander d'abord.",
|
|
@@ -142,7 +142,9 @@
|
|
|
142
142
|
"expired_title": "{tool} request expired.",
|
|
143
143
|
"expired_text": "“{summary}” was not answered in time, so it was denied at {time} and the assistant was told why.",
|
|
144
144
|
"question_skipped": "You let the assistant decide “{header}”.",
|
|
145
|
-
"full_auto_ended": "Full auto ended at {time}. The assistant asks first again."
|
|
145
|
+
"full_auto_ended": "Full auto ended at {time}. The assistant asks first again.",
|
|
146
|
+
"model_fallback_title": "Switched to {model}.",
|
|
147
|
+
"model_fallback_text": "The requested model, {requested}, was refused (unknown, or no access on this plan). This answer comes from {model}, the default model."
|
|
146
148
|
},
|
|
147
149
|
"errors": {
|
|
148
150
|
"turn_title": "The turn stopped.",
|
|
@@ -430,6 +432,8 @@
|
|
|
430
432
|
"configure_page": "Configure page",
|
|
431
433
|
"delete_page": "Delete page",
|
|
432
434
|
"add_block": "Add block",
|
|
435
|
+
"compose_page": "Compose page",
|
|
436
|
+
"configure_table_view": "Configure table view",
|
|
433
437
|
"configure_block": "Configure block",
|
|
434
438
|
"move_block": "Move block",
|
|
435
439
|
"remove_block": "Remove block",
|
|
@@ -446,6 +450,7 @@
|
|
|
446
450
|
"remove_field": "Remove field",
|
|
447
451
|
"browse_queries": "Browse query templates",
|
|
448
452
|
"add_query": "Add query",
|
|
453
|
+
"preview_query": "Preview query",
|
|
449
454
|
"configure_query": "Configure query",
|
|
450
455
|
"remove_query": "Remove query",
|
|
451
456
|
"search_tools": "Load tools",
|
|
@@ -142,7 +142,9 @@
|
|
|
142
142
|
"expired_title": "Demande « {tool} » expirée.",
|
|
143
143
|
"expired_text": "« {summary} » est restée sans réponse : elle a été refusée à {time} et l'assistant en a été informé.",
|
|
144
144
|
"question_skipped": "Vous avez laissé l'assistant décider pour « {header} ».",
|
|
145
|
-
"full_auto_ended": "Le mode automatique complet a pris fin à {time}. L'assistant redemande avant d'agir."
|
|
145
|
+
"full_auto_ended": "Le mode automatique complet a pris fin à {time}. L'assistant redemande avant d'agir.",
|
|
146
|
+
"model_fallback_title": "Passage à {model}.",
|
|
147
|
+
"model_fallback_text": "Le modèle demandé, {requested}, a été refusé (inconnu, ou sans accès avec cet abonnement). Cette réponse vient de {model}, le modèle par défaut."
|
|
146
148
|
},
|
|
147
149
|
"errors": {
|
|
148
150
|
"turn_title": "Le tour s'est arrêté.",
|
|
@@ -430,6 +432,8 @@
|
|
|
430
432
|
"configure_page": "Configurer la page",
|
|
431
433
|
"delete_page": "Supprimer la page",
|
|
432
434
|
"add_block": "Ajouter un bloc",
|
|
435
|
+
"compose_page": "Composer une page",
|
|
436
|
+
"configure_table_view": "Configurer la vue de tableau",
|
|
433
437
|
"configure_block": "Configurer le bloc",
|
|
434
438
|
"move_block": "Déplacer le bloc",
|
|
435
439
|
"remove_block": "Retirer le bloc",
|
|
@@ -446,6 +450,7 @@
|
|
|
446
450
|
"remove_field": "Retirer le champ",
|
|
447
451
|
"browse_queries": "Parcourir les modèles de requête",
|
|
448
452
|
"add_query": "Ajouter une requête",
|
|
453
|
+
"preview_query": "Aperçu de la requête",
|
|
449
454
|
"configure_query": "Configurer la requête",
|
|
450
455
|
"remove_query": "Retirer la requête",
|
|
451
456
|
"search_tools": "Charger des outils",
|
|
@@ -64,6 +64,43 @@ describe("the assistant actions another module's layer injects", () => {
|
|
|
64
64
|
]);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
it("send the prompt as the first message, in plan mode, when asked to", async () => {
|
|
68
|
+
const harness = createHarness();
|
|
69
|
+
const panel = await openPanel(harness);
|
|
70
|
+
publicAssistantActions(shallowRef(harness.session)).startConversation(
|
|
71
|
+
PROMPT,
|
|
72
|
+
{ mode: "plan", send: true },
|
|
73
|
+
);
|
|
74
|
+
await flushPromises();
|
|
75
|
+
const started = sentOfType(harness, "hello").at(-1)?.conversationId;
|
|
76
|
+
expect(sentOfType(harness, "set_conversation_mode")).toEqual([
|
|
77
|
+
expect.objectContaining({ conversationId: started, mode: "plan" }),
|
|
78
|
+
]);
|
|
79
|
+
expect(sentOfType(harness, "user_message")).toEqual([
|
|
80
|
+
expect.objectContaining({ content: PROMPT }),
|
|
81
|
+
]);
|
|
82
|
+
expect((panel.find("textarea").element as HTMLTextAreaElement).value).toBe(
|
|
83
|
+
"",
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("send the prompt once the stream is up, when the panel opens before it", async () => {
|
|
88
|
+
const harness = createHarness();
|
|
89
|
+
await openPanel(harness);
|
|
90
|
+
harness.setChannelStatus("disconnected");
|
|
91
|
+
publicAssistantActions(shallowRef(harness.session)).startConversation(
|
|
92
|
+
PROMPT,
|
|
93
|
+
{ send: true },
|
|
94
|
+
);
|
|
95
|
+
await flushPromises();
|
|
96
|
+
expect(sentOfType(harness, "user_message")).toEqual([]);
|
|
97
|
+
harness.setChannelStatus("connected");
|
|
98
|
+
await flushPromises();
|
|
99
|
+
expect(sentOfType(harness, "user_message")).toEqual([
|
|
100
|
+
expect.objectContaining({ content: PROMPT }),
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
67
104
|
it("are unavailable without a session, and starting then does nothing", () => {
|
|
68
105
|
const actions = publicAssistantActions(shallowRef(null));
|
|
69
106
|
expect(actions.isAvailable).toBe(false);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
BLOCK_SETTLE_BUDGET_MS,
|
|
5
|
+
awaitSettledBlocks,
|
|
6
|
+
readBlocks,
|
|
7
|
+
} from "../app/runtime/block-health";
|
|
8
|
+
|
|
9
|
+
function addBlock(
|
|
10
|
+
state: string,
|
|
11
|
+
type: string,
|
|
12
|
+
path?: string,
|
|
13
|
+
parent: Element = document.body,
|
|
14
|
+
): HTMLElement {
|
|
15
|
+
const element = document.createElement("div");
|
|
16
|
+
element.setAttribute("data-dms-block-state", state);
|
|
17
|
+
element.setAttribute("data-dms-block-type", type);
|
|
18
|
+
if (path !== undefined) element.setAttribute("data-dms-block", path);
|
|
19
|
+
parent.append(element);
|
|
20
|
+
return element;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const never = new AbortController().signal;
|
|
24
|
+
|
|
25
|
+
describe("readBlocks", () => {
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
document.body.replaceChildren();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("reads the state, kind and layout path each block states on its root", () => {
|
|
31
|
+
addBlock("empty", "ChartCard", "revenue");
|
|
32
|
+
addBlock("ok", "KpiCard");
|
|
33
|
+
document.body.append(document.createElement("section"));
|
|
34
|
+
expect(readBlocks(document)).toEqual([
|
|
35
|
+
{ path: "revenue", type: "ChartCard", state: "empty" },
|
|
36
|
+
{ type: "KpiCard", state: "ok" },
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("finds none on a host that states nothing", () => {
|
|
41
|
+
document.body.append(document.createElement("section"));
|
|
42
|
+
expect(readBlocks(document)).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("awaitSettledBlocks", () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
document.body.replaceChildren();
|
|
49
|
+
vi.useFakeTimers();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
vi.useRealTimers();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("reports the blocks showing no data once the page stopped changing", async () => {
|
|
57
|
+
addBlock("ok", "KpiCard", "orders");
|
|
58
|
+
addBlock("empty", "ChartCard", "revenue");
|
|
59
|
+
addBlock("error", "TopListCard", "top");
|
|
60
|
+
const settled = awaitSettledBlocks({ root: document, signal: never });
|
|
61
|
+
await vi.advanceTimersByTimeAsync(BLOCK_SETTLE_BUDGET_MS);
|
|
62
|
+
await expect(settled).resolves.toEqual({
|
|
63
|
+
status: "settled",
|
|
64
|
+
checked: 3,
|
|
65
|
+
blocks: [
|
|
66
|
+
{ path: "revenue", type: "ChartCard", state: "empty" },
|
|
67
|
+
{ path: "top", type: "TopListCard", state: "error" },
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("waits for a loading block and reports what it turned into", async () => {
|
|
73
|
+
const chart = addBlock("loading", "ChartCard", "revenue");
|
|
74
|
+
let result: Awaited<ReturnType<typeof awaitSettledBlocks>> | undefined;
|
|
75
|
+
void awaitSettledBlocks({ root: document, signal: never }).then((value) => {
|
|
76
|
+
result = value;
|
|
77
|
+
});
|
|
78
|
+
await vi.advanceTimersByTimeAsync(4_000);
|
|
79
|
+
expect(result).toBeUndefined();
|
|
80
|
+
chart.setAttribute("data-dms-block-state", "empty");
|
|
81
|
+
await vi.advanceTimersByTimeAsync(4_000);
|
|
82
|
+
expect(result).toEqual({
|
|
83
|
+
status: "settled",
|
|
84
|
+
checked: 1,
|
|
85
|
+
blocks: [{ path: "revenue", type: "ChartCard", state: "empty" }],
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("waits for a block that mounts after the others settled", async () => {
|
|
90
|
+
addBlock("ok", "KpiCard", "orders");
|
|
91
|
+
const settled = awaitSettledBlocks({ root: document, signal: never });
|
|
92
|
+
await vi.advanceTimersByTimeAsync(300);
|
|
93
|
+
addBlock("loading", "ChartCard", "late");
|
|
94
|
+
await vi.advanceTimersByTimeAsync(BLOCK_SETTLE_BUDGET_MS);
|
|
95
|
+
await expect(settled).resolves.toMatchObject({
|
|
96
|
+
status: "timeout",
|
|
97
|
+
checked: 2,
|
|
98
|
+
blocks: [{ path: "late", type: "ChartCard", state: "loading" }],
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("gives up at the budget and names the blocks still loading", async () => {
|
|
103
|
+
addBlock("loading", "Chart", "slow");
|
|
104
|
+
let finishedAt = -1;
|
|
105
|
+
const started = Date.now();
|
|
106
|
+
const settled = awaitSettledBlocks({ root: document, signal: never });
|
|
107
|
+
void settled.then(() => {
|
|
108
|
+
finishedAt = Date.now() - started;
|
|
109
|
+
});
|
|
110
|
+
await vi.advanceTimersByTimeAsync(BLOCK_SETTLE_BUDGET_MS - 1);
|
|
111
|
+
expect(finishedAt).toBe(-1);
|
|
112
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
113
|
+
await expect(settled).resolves.toEqual({
|
|
114
|
+
status: "timeout",
|
|
115
|
+
checked: 1,
|
|
116
|
+
blocks: [{ path: "slow", type: "Chart", state: "loading" }],
|
|
117
|
+
});
|
|
118
|
+
expect(finishedAt).toBe(BLOCK_SETTLE_BUDGET_MS);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("reports a page without data blocks as having none", async () => {
|
|
122
|
+
const settled = awaitSettledBlocks({ root: document, signal: never });
|
|
123
|
+
await vi.advanceTimersByTimeAsync(BLOCK_SETTLE_BUDGET_MS);
|
|
124
|
+
await expect(settled).resolves.toEqual({
|
|
125
|
+
status: "settled",
|
|
126
|
+
checked: 0,
|
|
127
|
+
blocks: [],
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("answers at once as superseded when another navigation starts", async () => {
|
|
132
|
+
addBlock("loading", "Chart", "slow");
|
|
133
|
+
const controller = new AbortController();
|
|
134
|
+
const settled = awaitSettledBlocks({
|
|
135
|
+
root: document,
|
|
136
|
+
signal: controller.signal,
|
|
137
|
+
});
|
|
138
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
139
|
+
controller.abort();
|
|
140
|
+
await expect(settled).resolves.toMatchObject({ status: "superseded" });
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -220,6 +220,40 @@ describe("the conversation folds the redesign's events", () => {
|
|
|
220
220
|
expect(conversation.totalTokens.value).toBe(1_200);
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
+
it("shows the model that answered, from a usage event or a snapshot", () => {
|
|
224
|
+
const b = bus();
|
|
225
|
+
const conversation = useConversation(b.options as never);
|
|
226
|
+
expect(conversation.model.value).toBeNull();
|
|
227
|
+
b.receive({ type: "usage", totalTokens: 10, model: "claude-opus-5-5" });
|
|
228
|
+
expect(conversation.model.value).toBe("claude-opus-5-5");
|
|
229
|
+
b.receive({
|
|
230
|
+
type: "conversation_snapshot",
|
|
231
|
+
messages: [],
|
|
232
|
+
changeSets: [],
|
|
233
|
+
totalTokens: 10,
|
|
234
|
+
model: "claude-sonnet-4-6",
|
|
235
|
+
});
|
|
236
|
+
expect(conversation.model.value).toBe("claude-sonnet-4-6");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("keeps the notice that the configured model was refused", () => {
|
|
240
|
+
const b = bus();
|
|
241
|
+
const conversation = useConversation(b.options as never);
|
|
242
|
+
b.receive({
|
|
243
|
+
type: "notice",
|
|
244
|
+
notice: {
|
|
245
|
+
kind: "model_fallback",
|
|
246
|
+
requestedModel: "claude-opus-5-5",
|
|
247
|
+
usedModel: "claude-sonnet-4-6",
|
|
248
|
+
timestampMs: 7,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
expect(conversation.messages.value[0]).toMatchObject({
|
|
252
|
+
role: "notice",
|
|
253
|
+
notice: { kind: "model_fallback", usedModel: "claude-sonnet-4-6" },
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
223
257
|
it("ignores another conversation's events", () => {
|
|
224
258
|
const b = bus();
|
|
225
259
|
const conversation = useConversation(b.options as never);
|
|
@@ -68,6 +68,7 @@ describe("the tool lexicon names a call by what it does", () => {
|
|
|
68
68
|
"BuilderRemoveField",
|
|
69
69
|
"BuilderRemoveQuery",
|
|
70
70
|
"BuilderQueryTemplates",
|
|
71
|
+
"BuilderPreviewQuery",
|
|
71
72
|
];
|
|
72
73
|
for (const name of [...builder, "AskUser", "Typecheck", "NavigateToPage"])
|
|
73
74
|
expect(describeTool(`mcp__dms-ai__${name}`, {}).verbKey).not.toBeNull();
|