@antelopejs/dms-ai 0.8.0 → 0.9.1
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 +2 -1
- package/dist/lifecycle/skill-sources.js +19 -146
- package/dist/lifecycle/skill-sources.js.map +1 -1
- package/dist/pages/settings/availability.js +8 -1
- package/dist/pages/settings/availability.js.map +1 -1
- package/dist/pages/settings/options.d.ts +2 -1
- package/dist/pages/settings/options.js +29 -8
- package/dist/pages/settings/options.js.map +1 -1
- package/dist/pages/settings/sections.js +25 -4
- package/dist/pages/settings/sections.js.map +1 -1
- package/dist/routes/builder.d.ts +3 -3
- package/dist/routes/builder.js +10 -2
- package/dist/routes/builder.js.map +1 -1
- package/dist/routes/settings.js +1 -1
- package/dist/routes/settings.js.map +1 -1
- package/dist/sidecar/settings-values.d.ts +3 -0
- package/dist/sidecar/settings-values.js +33 -1
- package/dist/sidecar/settings-values.js.map +1 -1
- package/dist/vocabulary/enumerations.js +2 -0
- package/dist/vocabulary/enumerations.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 +53 -85
- package/frontend-vue/app/chat/components/Composer.vue +23 -39
- package/frontend-vue/app/chat/components/NoticeCard.vue +25 -0
- package/frontend-vue/app/chat/components/PanelHeader.vue +2 -6
- package/frontend-vue/app/chat/components/ProviderNotice.vue +14 -3
- package/frontend-vue/app/chat/composables/conversation-events.ts +4 -3
- package/frontend-vue/app/chat/composables/useConversation.ts +6 -0
- package/frontend-vue/app/chat/composables/useConversationMode.ts +8 -11
- package/frontend-vue/app/chat/composables/useFollowScroll.ts +77 -0
- package/frontend-vue/app/chat/constants/modes.ts +15 -0
- package/frontend-vue/app/chat/constants/protocol.ts +0 -1
- package/frontend-vue/app/chat/constants/settings.ts +1 -1
- package/frontend-vue/app/chat/types/protocol.ts +19 -9
- package/frontend-vue/app/chat/types/settings.ts +7 -2
- package/frontend-vue/app/chat/utils/permission-view.ts +1 -0
- 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/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/constants.ts +2 -0
- package/frontend-vue/app/views/types.ts +3 -1
- package/frontend-vue/i18n/locales/dms-ai-pages-en-GB.json +28 -11
- package/frontend-vue/i18n/locales/dms-ai-pages-fr-FR.json +28 -11
- package/frontend-vue/i18n/locales/dms-ai-panel-en-GB.json +17 -22
- package/frontend-vue/i18n/locales/dms-ai-panel-fr-FR.json +17 -22
- package/frontend-vue/i18n/locales/dms-ai-views-en-GB.json +2 -0
- package/frontend-vue/i18n/locales/dms-ai-views-fr-FR.json +2 -0
- 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/chat-scroll.test.ts +251 -0
- package/frontend-vue/tests/chat/conversation-events.test.ts +35 -3
- package/frontend-vue/tests/chat/provider-notice.test.ts +33 -0
- package/frontend-vue/tests/chat/tool-lexicon.test.ts +1 -0
- package/frontend-vue/tests/chat-panel.test.ts +2 -4
- package/frontend-vue/tests/navigation.test.ts +67 -2
- package/frontend-vue/tests/panel-modes.test.ts +146 -0
- package/frontend-vue/tests/panel-questions.test.ts +1 -19
- package/frontend-vue/tests/panel-states.test.ts +3 -15
- package/frontend-vue/tests/support/fake-layout.ts +119 -0
- package/frontend-vue/tests/support/panel-harness.ts +16 -8
- package/frontend-vue/tests/support/setup-layout.ts +3 -0
- package/frontend-vue/tests/support/stubs.ts +2 -0
- package/frontend-vue/vitest.config.ts +1 -0
- package/package.json +6 -5
- package/sidecar/dist/agent/always-ask-files.d.ts +2 -0
- package/sidecar/dist/agent/always-ask-files.js +34 -0
- package/sidecar/dist/agent/always-ask-files.js.map +1 -0
- package/sidecar/dist/agent/conversation-modes.d.ts +3 -12
- package/sidecar/dist/agent/conversation-modes.js +16 -69
- package/sidecar/dist/agent/conversation-modes.js.map +1 -1
- package/sidecar/dist/agent/effective-mode.d.ts +2 -5
- package/sidecar/dist/agent/effective-mode.js +1 -15
- package/sidecar/dist/agent/effective-mode.js.map +1 -1
- package/sidecar/dist/agent/permission-bus.d.ts +1 -2
- package/sidecar/dist/agent/permission-bus.js +1 -3
- package/sidecar/dist/agent/permission-bus.js.map +1 -1
- package/sidecar/dist/agent/permission-preview.d.ts +6 -0
- package/sidecar/dist/agent/permission-preview.js +35 -10
- package/sidecar/dist/agent/permission-preview.js.map +1 -1
- package/sidecar/dist/agent/permission-rules.js +2 -2
- package/sidecar/dist/agent/permission-rules.js.map +1 -1
- package/sidecar/dist/agent/project-secrets.d.ts +7 -0
- package/sidecar/dist/agent/project-secrets.js +61 -0
- package/sidecar/dist/agent/project-secrets.js.map +1 -0
- package/sidecar/dist/agent/provider.d.ts +10 -4
- package/sidecar/dist/agent/runner.d.ts +8 -2
- package/sidecar/dist/agent/runner.js +11 -3
- package/sidecar/dist/agent/runner.js.map +1 -1
- package/sidecar/dist/agent/session.d.ts +3 -3
- package/sidecar/dist/agent/session.js.map +1 -1
- package/sidecar/dist/agent/tool-kinds.d.ts +7 -1
- package/sidecar/dist/agent/tool-kinds.js +24 -5
- package/sidecar/dist/agent/tool-kinds.js.map +1 -1
- package/sidecar/dist/audit/activity-query.js +2 -0
- package/sidecar/dist/audit/activity-query.js.map +1 -1
- package/sidecar/dist/audit/activity.js +1 -0
- package/sidecar/dist/audit/activity.js.map +1 -1
- package/sidecar/dist/builder/builder-client.js +14 -2
- package/sidecar/dist/builder/builder-client.js.map +1 -1
- package/sidecar/dist/constants/audit.d.ts +1 -3
- package/sidecar/dist/constants/audit.js +2 -1
- package/sidecar/dist/constants/audit.js.map +1 -1
- package/sidecar/dist/constants/builder.d.ts +5 -1
- package/sidecar/dist/constants/builder.js +15 -0
- package/sidecar/dist/constants/builder.js.map +1 -1
- package/sidecar/dist/constants/claude.d.ts +8 -0
- package/sidecar/dist/constants/claude.js +8 -0
- package/sidecar/dist/constants/claude.js.map +1 -1
- package/sidecar/dist/constants/codex.d.ts +4 -0
- package/sidecar/dist/constants/codex.js +4 -0
- package/sidecar/dist/constants/codex.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/permissions.d.ts +2 -0
- package/sidecar/dist/constants/permissions.js +23 -0
- package/sidecar/dist/constants/permissions.js.map +1 -1
- package/sidecar/dist/constants/project-secrets.d.ts +5 -0
- package/sidecar/dist/constants/project-secrets.js +20 -0
- package/sidecar/dist/constants/project-secrets.js.map +1 -0
- package/sidecar/dist/constants/providers.d.ts +2 -0
- package/sidecar/dist/constants/providers.js +1 -0
- package/sidecar/dist/constants/providers.js.map +1 -1
- package/sidecar/dist/constants/settings.d.ts +2 -2
- package/sidecar/dist/constants/settings.js +4 -9
- 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.d.ts +67 -2
- package/sidecar/dist/constants/tool-kinds.js +111 -33
- package/sidecar/dist/constants/tool-kinds.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-gate.js +3 -1
- package/sidecar/dist/mcp/tools/builder-gate.js.map +1 -1
- package/sidecar/dist/mcp/tools/builder-project.js +119 -4
- package/sidecar/dist/mcp/tools/builder-project.js.map +1 -1
- package/sidecar/dist/mcp/tools/builder-queries.d.ts +31 -0
- package/sidecar/dist/mcp/tools/builder-queries.js +30 -13
- package/sidecar/dist/mcp/tools/builder-queries.js.map +1 -1
- 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 +135 -1
- package/sidecar/dist/mcp/tools/builder.js +17 -4
- 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/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 +140 -63
- package/sidecar/dist/protocol/events.js +24 -8
- package/sidecar/dist/protocol/events.js.map +1 -1
- package/sidecar/dist/protocol/messages.d.ts +111 -49
- package/sidecar/dist/protocol/messages.js +30 -13
- package/sidecar/dist/protocol/messages.js.map +1 -1
- package/sidecar/dist/providers/claude/config.d.ts +19 -5
- package/sidecar/dist/providers/claude/config.js +21 -7
- 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 +35 -40
- 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/safe-mode.d.ts +5 -5
- package/sidecar/dist/providers/claude/safe-mode.js +49 -30
- package/sidecar/dist/providers/claude/safe-mode.js.map +1 -1
- package/sidecar/dist/providers/claude/secret-files.d.ts +2 -0
- package/sidecar/dist/providers/claude/secret-files.js +50 -0
- package/sidecar/dist/providers/claude/secret-files.js.map +1 -0
- package/sidecar/dist/providers/claude/session-stream.d.ts +18 -0
- package/sidecar/dist/providers/claude/session-stream.js +80 -0
- package/sidecar/dist/providers/claude/session-stream.js.map +1 -0
- package/sidecar/dist/providers/claude/tool-guard.d.ts +14 -0
- package/sidecar/dist/providers/claude/tool-guard.js +112 -0
- package/sidecar/dist/providers/claude/tool-guard.js.map +1 -0
- package/sidecar/dist/providers/codex/adapter.d.ts +10 -0
- package/sidecar/dist/providers/codex/adapter.js +20 -5
- package/sidecar/dist/providers/codex/adapter.js.map +1 -1
- package/sidecar/dist/providers/codex/config.d.ts +6 -17
- package/sidecar/dist/providers/codex/config.js +9 -56
- package/sidecar/dist/providers/codex/config.js.map +1 -1
- package/sidecar/dist/providers/codex/mcp-elicitation.d.ts +21 -0
- package/sidecar/dist/providers/codex/mcp-elicitation.js +66 -0
- package/sidecar/dist/providers/codex/mcp-elicitation.js.map +1 -0
- package/sidecar/dist/providers/codex/module.js +1 -1
- package/sidecar/dist/providers/codex/module.js.map +1 -1
- package/sidecar/dist/providers/codex/patch-directories.d.ts +6 -0
- package/sidecar/dist/providers/codex/patch-directories.js +53 -0
- package/sidecar/dist/providers/codex/patch-directories.js.map +1 -0
- package/sidecar/dist/providers/codex/permission-profiles.d.ts +11 -0
- package/sidecar/dist/providers/codex/permission-profiles.js +37 -0
- package/sidecar/dist/providers/codex/permission-profiles.js.map +1 -0
- package/sidecar/dist/providers/codex/permissions.d.ts +13 -4
- package/sidecar/dist/providers/codex/permissions.js +28 -2
- package/sidecar/dist/providers/codex/permissions.js.map +1 -1
- package/sidecar/dist/providers/codex/process.d.ts +1 -0
- package/sidecar/dist/providers/codex/process.js +3 -1
- package/sidecar/dist/providers/codex/process.js.map +1 -1
- package/sidecar/dist/providers/codex/protocol/common.d.ts +16 -4
- package/sidecar/dist/providers/codex/protocol/index.d.ts +2 -2
- package/sidecar/dist/providers/codex/protocol/index.js +1 -1
- package/sidecar/dist/providers/codex/protocol/index.js.map +1 -1
- package/sidecar/dist/providers/codex/protocol/v2.d.ts +23 -32
- package/sidecar/dist/providers/codex/provider.js +13 -12
- package/sidecar/dist/providers/codex/provider.js.map +1 -1
- package/sidecar/dist/providers/codex/secret-file-globs.d.ts +2 -0
- package/sidecar/dist/providers/codex/secret-file-globs.js +45 -0
- package/sidecar/dist/providers/codex/secret-file-globs.js.map +1 -0
- package/sidecar/dist/providers/types.d.ts +3 -1
- package/sidecar/dist/providers/types.js +2 -2
- package/sidecar/dist/providers/types.js.map +1 -1
- package/sidecar/dist/server/chat-events.js +1 -0
- package/sidecar/dist/server/chat-events.js.map +1 -1
- package/sidecar/dist/server/http-routes.js +1 -1
- 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 +1 -1
- package/sidecar/dist/server/permission-events.js +5 -8
- package/sidecar/dist/server/permission-events.js.map +1 -1
- package/sidecar/dist/server/routing.js +11 -6
- package/sidecar/dist/server/routing.js.map +1 -1
- package/sidecar/dist/server/tool-calls.d.ts +0 -2
- package/sidecar/dist/server/tool-calls.js +7 -8
- package/sidecar/dist/server/tool-calls.js.map +1 -1
- package/sidecar/dist/server/turns.js +17 -1
- package/sidecar/dist/server/turns.js.map +1 -1
- package/sidecar/dist/server/ws.js +4 -15
- package/sidecar/dist/server/ws.js.map +1 -1
- package/sidecar/dist/skills/skill-usage.js +2 -2
- package/sidecar/dist/skills/skill-usage.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 +14 -5
- package/sidecar/dist/state/settings-store.js +34 -8
- package/sidecar/dist/state/settings-store.js.map +1 -1
- package/sidecar/dist/state/settings-types.d.ts +16 -2
- package/sidecar/dist/state/settings-types.js +14 -1
- package/sidecar/dist/state/settings-types.js.map +1 -1
- package/sidecar/dist/state/types.d.ts +14 -6
- package/sidecar/dist/state/types.js.map +1 -1
- package/sidecar/package.json +3 -3
- package/skills/dms-ai/SKILL.md +2 -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 +69 -20
- package/skills/dms-page-design/SKILL.md +192 -0
- package/skills/dms-page-design/cases.json +57 -0
- package/frontend-vue/app/chat/components/FullAutoBanner.vue +0 -76
- package/frontend-vue/app/chat/components/FullAutoDialog.vue +0 -103
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { flushPromises, type VueWrapper } from "@vue/test-utils";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { nextTick } from "vue";
|
|
5
|
+
import {
|
|
6
|
+
CONVERSATION_ID,
|
|
7
|
+
createHarness,
|
|
8
|
+
deliver,
|
|
9
|
+
type Harness,
|
|
10
|
+
openPanel,
|
|
11
|
+
sendMessage,
|
|
12
|
+
unmountPanel,
|
|
13
|
+
} from "../support/panel-harness";
|
|
14
|
+
import {
|
|
15
|
+
type FakeScroller,
|
|
16
|
+
fakeScroller,
|
|
17
|
+
settleLayout,
|
|
18
|
+
} from "../support/fake-layout";
|
|
19
|
+
|
|
20
|
+
const BOTTOM_TOLERANCE_PX = 4;
|
|
21
|
+
|
|
22
|
+
interface OpenChat {
|
|
23
|
+
harness: Harness;
|
|
24
|
+
panel: VueWrapper;
|
|
25
|
+
scroller: FakeScroller;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function transcript(rows: number) {
|
|
29
|
+
return Array.from({ length: rows }, (_, index) => ({
|
|
30
|
+
role: index % 2 === 0 ? "user" : "assistant",
|
|
31
|
+
content: `message ${index}`,
|
|
32
|
+
timestampMs: 1_700_000_000_000 + index,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** One browser frame: Vue renders, then layout is reported. */
|
|
37
|
+
async function frame(): Promise<void> {
|
|
38
|
+
await nextTick();
|
|
39
|
+
await flushPromises();
|
|
40
|
+
settleLayout();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function openChat(rows = 30): Promise<OpenChat> {
|
|
44
|
+
const harness = createHarness();
|
|
45
|
+
const panel = await openPanel(harness);
|
|
46
|
+
const scroller = fakeScroller(
|
|
47
|
+
panel.find<HTMLElement>(".chat-view-body").element,
|
|
48
|
+
);
|
|
49
|
+
deliver(harness, {
|
|
50
|
+
type: "conversation_snapshot",
|
|
51
|
+
messages: transcript(rows),
|
|
52
|
+
});
|
|
53
|
+
await frame();
|
|
54
|
+
return { harness, panel, scroller };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function growTranscript(chat: OpenChat, rows: number): Promise<void> {
|
|
58
|
+
deliver(chat.harness, {
|
|
59
|
+
type: "conversation_snapshot",
|
|
60
|
+
messages: transcript(rows),
|
|
61
|
+
});
|
|
62
|
+
await frame();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
localStorage.setItem("dms-ai-conversation-id", CONVERSATION_ID);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
unmountPanel();
|
|
71
|
+
localStorage.clear();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("the chat follows the conversation", () => {
|
|
75
|
+
it("shows the end of a chat opened from its snapshot", async () => {
|
|
76
|
+
const { scroller } = await openChat(60);
|
|
77
|
+
expect(scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
78
|
+
BOTTOM_TOLERANCE_PX,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("stays at the bottom when the view loses height without any message arriving", async () => {
|
|
83
|
+
const { scroller } = await openChat();
|
|
84
|
+
scroller.viewportPx -= 250;
|
|
85
|
+
await frame();
|
|
86
|
+
expect(scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
87
|
+
BOTTOM_TOLERANCE_PX,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("stays at the bottom when content grows without any message arriving", async () => {
|
|
92
|
+
const { scroller } = await openChat();
|
|
93
|
+
scroller.extraContentPx += 600;
|
|
94
|
+
await frame();
|
|
95
|
+
expect(scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
96
|
+
BOTTOM_TOLERANCE_PX,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("stays at the bottom through one message taller than the old 80px margin", async () => {
|
|
101
|
+
const { scroller, harness } = await openChat();
|
|
102
|
+
deliver(harness, { type: "assistant_message_chunk", text: "answer" });
|
|
103
|
+
scroller.extraContentPx += 900;
|
|
104
|
+
await frame();
|
|
105
|
+
expect(scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
106
|
+
BOTTOM_TOLERANCE_PX,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("does not pull the person back down once they scroll up", async () => {
|
|
111
|
+
const chat = await openChat();
|
|
112
|
+
chat.scroller.scrollTo(500);
|
|
113
|
+
await growTranscript(chat, 40);
|
|
114
|
+
chat.scroller.extraContentPx += 300;
|
|
115
|
+
await frame();
|
|
116
|
+
expect(
|
|
117
|
+
chat.panel.find<HTMLElement>(".chat-view-body").element.scrollTop,
|
|
118
|
+
).toBe(500);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("jumps to the bottom when a message is sent from far up", async () => {
|
|
122
|
+
const chat = await openChat();
|
|
123
|
+
chat.scroller.scrollTo(200);
|
|
124
|
+
await sendMessage(chat.panel, "and now this");
|
|
125
|
+
await frame();
|
|
126
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
127
|
+
BOTTOM_TOLERANCE_PX,
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("follows again once the person scrolls back to the bottom", async () => {
|
|
132
|
+
const chat = await openChat();
|
|
133
|
+
chat.scroller.scrollTo(500);
|
|
134
|
+
const bottom = chat.scroller.viewportPx;
|
|
135
|
+
chat.scroller.scrollTo(
|
|
136
|
+
chat.panel.find<HTMLElement>(".chat-view-body").element.scrollHeight -
|
|
137
|
+
bottom,
|
|
138
|
+
);
|
|
139
|
+
await growTranscript(chat, 40);
|
|
140
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
141
|
+
BOTTOM_TOLERANCE_PX,
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("stops following on an upward scroll that carries no input", async () => {
|
|
146
|
+
const chat = await openChat();
|
|
147
|
+
chat.scroller.scrollTo(500);
|
|
148
|
+
await growTranscript(chat, 40);
|
|
149
|
+
chat.scroller.extraContentPx += 300;
|
|
150
|
+
await frame();
|
|
151
|
+
expect(
|
|
152
|
+
chat.panel.find<HTMLElement>(".chat-view-body").element.scrollTop,
|
|
153
|
+
).toBe(500);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("keeps following when content above the view collapses and scroll anchoring moves the view up", async () => {
|
|
157
|
+
const chat = await openChat();
|
|
158
|
+
chat.scroller.extraContentPx += 800;
|
|
159
|
+
await frame();
|
|
160
|
+
chat.scroller.collapseAbove(600);
|
|
161
|
+
await frame();
|
|
162
|
+
await frame();
|
|
163
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
164
|
+
BOTTOM_TOLERANCE_PX,
|
|
165
|
+
);
|
|
166
|
+
chat.scroller.extraContentPx += 400;
|
|
167
|
+
await frame();
|
|
168
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
169
|
+
BOTTOM_TOLERANCE_PX,
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("keeps following when a collapse above the view comes with growth below it", async () => {
|
|
174
|
+
const chat = await openChat();
|
|
175
|
+
chat.scroller.extraContentPx += 800;
|
|
176
|
+
await frame();
|
|
177
|
+
chat.scroller.collapseAbove(600);
|
|
178
|
+
chat.scroller.extraContentPx += 700;
|
|
179
|
+
await frame();
|
|
180
|
+
await frame();
|
|
181
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
182
|
+
BOTTOM_TOLERANCE_PX,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("keeps following when the view gains height and the browser clamps the scroll", async () => {
|
|
187
|
+
const chat = await openChat();
|
|
188
|
+
chat.scroller.viewportPx += 250;
|
|
189
|
+
await frame();
|
|
190
|
+
await frame();
|
|
191
|
+
chat.scroller.extraContentPx += 500;
|
|
192
|
+
await frame();
|
|
193
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
194
|
+
BOTTOM_TOLERANCE_PX,
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("keeps following after the content shrinks under the view", async () => {
|
|
199
|
+
const chat = await openChat(60);
|
|
200
|
+
await growTranscript(chat, 20);
|
|
201
|
+
await growTranscript(chat, 30);
|
|
202
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
203
|
+
BOTTOM_TOLERANCE_PX,
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("ends at the bottom for a chat started from outside that sends its prompt", async () => {
|
|
208
|
+
const chat = await openChat();
|
|
209
|
+
chat.scroller.scrollTo(200);
|
|
210
|
+
chat.harness.session.startConversation("build me a page", undefined, true);
|
|
211
|
+
await frame();
|
|
212
|
+
await frame();
|
|
213
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
214
|
+
BOTTOM_TOLERANCE_PX,
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("opens another chat at its bottom, even after scrolling up in this one", async () => {
|
|
219
|
+
const chat = await openChat();
|
|
220
|
+
chat.scroller.scrollTo(200);
|
|
221
|
+
chat.harness.session.openConversation("conv-other");
|
|
222
|
+
await frame();
|
|
223
|
+
deliver(chat.harness, {
|
|
224
|
+
conversationId: "conv-other",
|
|
225
|
+
type: "conversation_snapshot",
|
|
226
|
+
messages: transcript(50),
|
|
227
|
+
});
|
|
228
|
+
await frame();
|
|
229
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
230
|
+
BOTTOM_TOLERANCE_PX,
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("keeps following in a shorter chat opened from the bottom of a long one", async () => {
|
|
235
|
+
const chat = await openChat(60);
|
|
236
|
+
chat.harness.session.openConversation("conv-other");
|
|
237
|
+
await frame();
|
|
238
|
+
deliver(chat.harness, {
|
|
239
|
+
conversationId: "conv-other",
|
|
240
|
+
type: "conversation_snapshot",
|
|
241
|
+
messages: transcript(8),
|
|
242
|
+
});
|
|
243
|
+
await frame();
|
|
244
|
+
await frame();
|
|
245
|
+
chat.scroller.extraContentPx += 500;
|
|
246
|
+
await frame();
|
|
247
|
+
expect(chat.scroller.distanceFromBottom()).toBeLessThanOrEqual(
|
|
248
|
+
BOTTOM_TOLERANCE_PX,
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -134,7 +134,6 @@ describe("the conversation folds the redesign's events", () => {
|
|
|
134
134
|
type: "conversation_mode",
|
|
135
135
|
mode: "plan",
|
|
136
136
|
generationMode: "vibe",
|
|
137
|
-
fullAuto: { duration: "turn" },
|
|
138
137
|
});
|
|
139
138
|
expect(conversation.messages.value[0]).toMatchObject({
|
|
140
139
|
role: "notice",
|
|
@@ -144,7 +143,6 @@ describe("the conversation folds the redesign's events", () => {
|
|
|
144
143
|
expect(conversation.mode.value).toEqual({
|
|
145
144
|
mode: "plan",
|
|
146
145
|
generationMode: "vibe",
|
|
147
|
-
fullAuto: { duration: "turn" },
|
|
148
146
|
});
|
|
149
147
|
});
|
|
150
148
|
|
|
@@ -200,7 +198,7 @@ describe("the conversation folds the redesign's events", () => {
|
|
|
200
198
|
},
|
|
201
199
|
],
|
|
202
200
|
changeSets: [changeSet()],
|
|
203
|
-
mode: { mode: "acceptEdits", generationMode: "safe"
|
|
201
|
+
mode: { mode: "acceptEdits", generationMode: "safe" },
|
|
204
202
|
totalTokens: 1_200,
|
|
205
203
|
});
|
|
206
204
|
expect(conversation.messages.value.map((m) => m.role)).toEqual([
|
|
@@ -220,6 +218,40 @@ describe("the conversation folds the redesign's events", () => {
|
|
|
220
218
|
expect(conversation.totalTokens.value).toBe(1_200);
|
|
221
219
|
});
|
|
222
220
|
|
|
221
|
+
it("shows the model that answered, from a usage event or a snapshot", () => {
|
|
222
|
+
const b = bus();
|
|
223
|
+
const conversation = useConversation(b.options as never);
|
|
224
|
+
expect(conversation.model.value).toBeNull();
|
|
225
|
+
b.receive({ type: "usage", totalTokens: 10, model: "claude-opus-5-5" });
|
|
226
|
+
expect(conversation.model.value).toBe("claude-opus-5-5");
|
|
227
|
+
b.receive({
|
|
228
|
+
type: "conversation_snapshot",
|
|
229
|
+
messages: [],
|
|
230
|
+
changeSets: [],
|
|
231
|
+
totalTokens: 10,
|
|
232
|
+
model: "claude-sonnet-4-6",
|
|
233
|
+
});
|
|
234
|
+
expect(conversation.model.value).toBe("claude-sonnet-4-6");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("keeps the notice that the configured model was refused", () => {
|
|
238
|
+
const b = bus();
|
|
239
|
+
const conversation = useConversation(b.options as never);
|
|
240
|
+
b.receive({
|
|
241
|
+
type: "notice",
|
|
242
|
+
notice: {
|
|
243
|
+
kind: "model_fallback",
|
|
244
|
+
requestedModel: "claude-opus-5-5",
|
|
245
|
+
usedModel: "claude-sonnet-4-6",
|
|
246
|
+
timestampMs: 7,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
expect(conversation.messages.value[0]).toMatchObject({
|
|
250
|
+
role: "notice",
|
|
251
|
+
notice: { kind: "model_fallback", usedModel: "claude-sonnet-4-6" },
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
223
255
|
it("ignores another conversation's events", () => {
|
|
224
256
|
const b = bus();
|
|
225
257
|
const conversation = useConversation(b.options as never);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { mount } from "@vue/test-utils";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import ProviderNotice from "../../app/chat/components/ProviderNotice.vue";
|
|
5
|
+
import { CHAT_I18N_KEY } from "../../app/chat/composables/useChatI18n";
|
|
6
|
+
import fr from "../../i18n/locales/dms-ai-panel-fr-FR.json";
|
|
7
|
+
import { createTestI18n } from "../support/i18n";
|
|
8
|
+
import { PANEL_STUBS } from "../support/stubs";
|
|
9
|
+
|
|
10
|
+
const ENGLISH_REASON =
|
|
11
|
+
"Sign in with ChatGPT in Assistant › Settings to run Codex on your ChatGPT plan.";
|
|
12
|
+
|
|
13
|
+
function noticeText(code?: "codex_not_signed_in"): string {
|
|
14
|
+
return mount(ProviderNotice, {
|
|
15
|
+
props: { agentName: "Codex", reason: ENGLISH_REASON, code },
|
|
16
|
+
global: {
|
|
17
|
+
provide: { [CHAT_I18N_KEY as symbol]: createTestI18n(fr) },
|
|
18
|
+
stubs: PANEL_STUBS,
|
|
19
|
+
},
|
|
20
|
+
}).text();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("the agent can't run notice", () => {
|
|
24
|
+
it("words the ChatGPT sign-in hint in the panel's language", () => {
|
|
25
|
+
const text = noticeText("codex_not_signed_in");
|
|
26
|
+
expect(text).toContain("Connectez-vous avec ChatGPT");
|
|
27
|
+
expect(text).not.toContain(ENGLISH_REASON);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("shows the sidecar's own reason when the panel has no wording for it", () => {
|
|
31
|
+
expect(noticeText()).toContain(ENGLISH_REASON);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -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();
|
|
@@ -47,16 +47,14 @@ describe("the chat as a component of the dashboard", () => {
|
|
|
47
47
|
]);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
it("
|
|
50
|
+
it("picks its conversation up again when the panel reopens, without announcing that it left", async () => {
|
|
51
51
|
const harness = createHarness();
|
|
52
52
|
const panel = await openPanel(harness);
|
|
53
53
|
await sendMessage(panel, "build me a page");
|
|
54
54
|
harness.session.panel.close();
|
|
55
55
|
await nextTick();
|
|
56
56
|
expect(panel.find(".chat-view").exists()).toBe(false);
|
|
57
|
-
expect(sentOfType(harness, "leave_conversation")).toEqual([
|
|
58
|
-
{ type: "leave_conversation", conversationId: CONVERSATION_ID },
|
|
59
|
-
]);
|
|
57
|
+
expect(sentOfType(harness, "leave_conversation")).toEqual([]);
|
|
60
58
|
harness.session.panel.toggle();
|
|
61
59
|
await flushPromises();
|
|
62
60
|
expect(sentOfType(harness, "hello")).toEqual([
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { installCurrentPageTracker } from "../app/runtime/current-page";
|
|
4
|
+
import { BLOCK_SETTLE_BUDGET_MS } from "../app/runtime/block-health";
|
|
4
5
|
import { installNavigationCompleteEmitter } from "../app/runtime/navigation-complete";
|
|
5
6
|
|
|
6
7
|
function navigate(url: string): void {
|
|
@@ -61,7 +62,7 @@ describe("Inertia host navigation", () => {
|
|
|
61
62
|
document.title = "";
|
|
62
63
|
});
|
|
63
64
|
|
|
64
|
-
it("emits completion
|
|
65
|
+
it("emits completion at once after navigation, announcing the block report, and removes its event listener", () => {
|
|
65
66
|
const send = vi.fn();
|
|
66
67
|
const stop = installNavigationCompleteEmitter({ send });
|
|
67
68
|
expect(send).not.toHaveBeenCalled();
|
|
@@ -69,9 +70,73 @@ describe("Inertia host navigation", () => {
|
|
|
69
70
|
expect(send).toHaveBeenCalledExactlyOnceWith({
|
|
70
71
|
type: "host_navigation_complete",
|
|
71
72
|
path: "/finished",
|
|
73
|
+
blocksWithinMs: BLOCK_SETTLE_BUDGET_MS,
|
|
72
74
|
});
|
|
73
75
|
stop();
|
|
74
76
|
navigate("/ignored");
|
|
75
77
|
expect(send).toHaveBeenCalledTimes(1);
|
|
76
78
|
});
|
|
79
|
+
|
|
80
|
+
describe("block report", () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
document.body.replaceChildren();
|
|
83
|
+
vi.useFakeTimers();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
afterEach(() => {
|
|
87
|
+
vi.useRealTimers();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
function addChart(state: string): HTMLElement {
|
|
91
|
+
const element = document.createElement("div");
|
|
92
|
+
element.setAttribute("data-dms-block-state", state);
|
|
93
|
+
element.setAttribute("data-dms-block-type", "ChartCard");
|
|
94
|
+
element.setAttribute("data-dms-block", "revenue");
|
|
95
|
+
document.body.append(element);
|
|
96
|
+
return element;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
it("follows the completion with the blocks showing no data", async () => {
|
|
100
|
+
addChart("empty");
|
|
101
|
+
const send = vi.fn();
|
|
102
|
+
const stop = installNavigationCompleteEmitter({ send });
|
|
103
|
+
navigate("/sales");
|
|
104
|
+
await vi.advanceTimersByTimeAsync(BLOCK_SETTLE_BUDGET_MS);
|
|
105
|
+
expect(send).toHaveBeenLastCalledWith({
|
|
106
|
+
type: "host_page_blocks",
|
|
107
|
+
path: "/sales",
|
|
108
|
+
status: "settled",
|
|
109
|
+
checked: 1,
|
|
110
|
+
blocks: [{ path: "revenue", type: "ChartCard", state: "empty" }],
|
|
111
|
+
});
|
|
112
|
+
stop();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("reports the first page as superseded when a second navigation starts", async () => {
|
|
116
|
+
addChart("loading");
|
|
117
|
+
const send = vi.fn();
|
|
118
|
+
const stop = installNavigationCompleteEmitter({ send });
|
|
119
|
+
navigate("/first");
|
|
120
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
121
|
+
navigate("/second");
|
|
122
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
123
|
+
expect(send).toHaveBeenCalledWith(
|
|
124
|
+
expect.objectContaining({
|
|
125
|
+
type: "host_page_blocks",
|
|
126
|
+
path: "/first",
|
|
127
|
+
status: "superseded",
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
stop();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("reports nothing once stopped", async () => {
|
|
134
|
+
const send = vi.fn();
|
|
135
|
+
const stop = installNavigationCompleteEmitter({ send });
|
|
136
|
+
navigate("/sales");
|
|
137
|
+
stop();
|
|
138
|
+
await vi.advanceTimersByTimeAsync(BLOCK_SETTLE_BUDGET_MS);
|
|
139
|
+
expect(send).toHaveBeenCalledTimes(1);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
77
142
|
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import type { VueWrapper } from "@vue/test-utils";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { nextTick } from "vue";
|
|
5
|
+
import {
|
|
6
|
+
CONVERSATION_ID,
|
|
7
|
+
createHarness,
|
|
8
|
+
deliver,
|
|
9
|
+
openPanel,
|
|
10
|
+
unmountPanel,
|
|
11
|
+
} from "./support/panel-harness";
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
localStorage.setItem("dms-ai-conversation-id", CONVERSATION_ID);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
unmountPanel();
|
|
19
|
+
localStorage.clear();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const SETTINGS = {
|
|
23
|
+
defaultModes: { claude: "auto", codex: "normal" },
|
|
24
|
+
thinking: "medium",
|
|
25
|
+
generationMode: "safe",
|
|
26
|
+
allowLocalSkills: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const PROVIDERS = {
|
|
30
|
+
claude: { available: true },
|
|
31
|
+
codex: { available: true },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function modePill(panel: VueWrapper) {
|
|
35
|
+
const pills = panel.findAll(".cb-mode-btn");
|
|
36
|
+
return pills[pills.length - 1];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function pickerItems(panel: VueWrapper): string[] {
|
|
40
|
+
const menu = modePill(panel).element.closest(".u-dropdown");
|
|
41
|
+
return Array.from(menu?.querySelectorAll(".u-dropdown-item") ?? []).map(
|
|
42
|
+
(item) => item.textContent?.trim() ?? "",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function useProvider(
|
|
47
|
+
harness: ReturnType<typeof createHarness>,
|
|
48
|
+
provider: "claude" | "codex",
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
deliver(harness, {
|
|
51
|
+
type: "settings_update",
|
|
52
|
+
settings: { ...SETTINGS, provider },
|
|
53
|
+
providers: PROVIDERS,
|
|
54
|
+
});
|
|
55
|
+
await nextTick();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("the approval mode pill", () => {
|
|
59
|
+
it("shows Auto review on a fresh Claude chat, without danger styling", async () => {
|
|
60
|
+
const harness = createHarness();
|
|
61
|
+
const panel = await openPanel(harness);
|
|
62
|
+
await useProvider(harness, "claude");
|
|
63
|
+
const pill = modePill(panel);
|
|
64
|
+
expect(pill.text()).toBe("Auto review");
|
|
65
|
+
expect(pickerItems(panel)).toContain("Auto review");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("reads Ask first on Codex for a chat holding auto, and offers no Auto review", async () => {
|
|
69
|
+
const harness = createHarness();
|
|
70
|
+
const panel = await openPanel(harness);
|
|
71
|
+
await useProvider(harness, "codex");
|
|
72
|
+
deliver(harness, {
|
|
73
|
+
type: "conversation_mode",
|
|
74
|
+
mode: "auto",
|
|
75
|
+
generationMode: "safe",
|
|
76
|
+
});
|
|
77
|
+
await nextTick();
|
|
78
|
+
expect(modePill(panel).text()).toBe("Ask first");
|
|
79
|
+
expect(pickerItems(panel)).not.toContain("Auto review");
|
|
80
|
+
expect(pickerItems(panel)).toContain("Ask first");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it.each(["claude", "codex"] as const)(
|
|
84
|
+
"offers no Full auto in the %s mode menu",
|
|
85
|
+
async (provider) => {
|
|
86
|
+
const harness = createHarness();
|
|
87
|
+
const panel = await openPanel(harness);
|
|
88
|
+
await useProvider(harness, provider);
|
|
89
|
+
const items = pickerItems(panel);
|
|
90
|
+
expect(items.length).toBeGreaterThan(0);
|
|
91
|
+
expect(items.filter((item) => /full auto/i.test(item))).toEqual([]);
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
it.each([
|
|
96
|
+
["codex", true],
|
|
97
|
+
["claude", false],
|
|
98
|
+
] as const)(
|
|
99
|
+
"says Accept edits works as Ask first on %s: %s",
|
|
100
|
+
async (provider, worksAsAskFirst) => {
|
|
101
|
+
const harness = createHarness();
|
|
102
|
+
const panel = await openPanel(harness);
|
|
103
|
+
await useProvider(harness, provider);
|
|
104
|
+
const acceptEdits = panel
|
|
105
|
+
.findAll(".u-dropdown-item")
|
|
106
|
+
.find((item) => item.text() === "Accept edits");
|
|
107
|
+
const description = acceptEdits?.attributes("data-description") ?? "";
|
|
108
|
+
expect(description).not.toBe("");
|
|
109
|
+
expect(description.includes("works as Ask first")).toBe(worksAsAskFirst);
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
it("keeps the pill on the chat's own mode when an older sidecar still sends Full auto", async () => {
|
|
114
|
+
const harness = createHarness();
|
|
115
|
+
const panel = await openPanel(harness);
|
|
116
|
+
await useProvider(harness, "claude");
|
|
117
|
+
deliver(harness, {
|
|
118
|
+
type: "conversation_mode",
|
|
119
|
+
mode: "auto",
|
|
120
|
+
generationMode: "safe",
|
|
121
|
+
fullAuto: { duration: "chat" },
|
|
122
|
+
});
|
|
123
|
+
await nextTick();
|
|
124
|
+
expect(modePill(panel).text()).toBe("Auto review");
|
|
125
|
+
expect(panel.find(".cb-banner-auto").exists()).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("says Auto review is not available, naming the model", async () => {
|
|
129
|
+
const harness = createHarness();
|
|
130
|
+
const panel = await openPanel(harness);
|
|
131
|
+
deliver(harness, {
|
|
132
|
+
type: "notice",
|
|
133
|
+
notice: {
|
|
134
|
+
kind: "auto_unavailable",
|
|
135
|
+
model: "claude-haiku-4-5",
|
|
136
|
+
timestampMs: Date.now(),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
await nextTick();
|
|
140
|
+
const notice = panel.find(".cb-notice");
|
|
141
|
+
expect(notice.text()).toContain("Auto review isn't available here.");
|
|
142
|
+
expect(notice.text()).toContain(
|
|
143
|
+
"claude-haiku-4-5, this plan or a setting doesn't allow it.",
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -106,35 +106,17 @@ describe("questions, queue and modes", () => {
|
|
|
106
106
|
).toEqual(["queue_update", "queue_move", "queue_clear"]);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
it("
|
|
109
|
+
it("sends the chosen mode alone, with nothing that approves for the person", async () => {
|
|
110
110
|
const harness = createHarness();
|
|
111
111
|
const panel = await openPanel(harness);
|
|
112
112
|
await buttonWithText(panel, "Plan only").trigger("click");
|
|
113
|
-
await buttonWithText(panel, "Full auto…").trigger("click");
|
|
114
|
-
await nextTick();
|
|
115
|
-
const modal = panel.find(".u-modal");
|
|
116
|
-
expect(modal.text()).toContain("Turn on Full auto?");
|
|
117
|
-
await buttonWithText(modal, "30 minutes").trigger("click");
|
|
118
|
-
await buttonWithText(modal, "Turn on Full auto").trigger("click");
|
|
119
113
|
expect(sentOfType(harness, "set_conversation_mode")).toEqual([
|
|
120
114
|
{
|
|
121
115
|
type: "set_conversation_mode",
|
|
122
116
|
conversationId: CONVERSATION_ID,
|
|
123
117
|
mode: "plan",
|
|
124
118
|
},
|
|
125
|
-
{
|
|
126
|
-
type: "set_conversation_mode",
|
|
127
|
-
conversationId: CONVERSATION_ID,
|
|
128
|
-
fullAuto: { duration: "30m" },
|
|
129
|
-
},
|
|
130
119
|
]);
|
|
131
|
-
await nextTick();
|
|
132
|
-
const banner = panel.find(".cb-banner-auto");
|
|
133
|
-
expect(banner.text()).toContain("Full auto · for 30 minutes");
|
|
134
|
-
await buttonWithText(banner, "Turn off").trigger("click");
|
|
135
|
-
expect(sentOfType(harness, "set_conversation_mode").at(-1)).toMatchObject({
|
|
136
|
-
fullAuto: null,
|
|
137
|
-
});
|
|
138
120
|
});
|
|
139
121
|
|
|
140
122
|
it("runs a proposed plan in Ask first", async () => {
|
|
@@ -60,16 +60,6 @@ describe("the panel's header and empty chat", () => {
|
|
|
60
60
|
);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
it("sends leave_conversation when the panel closes", async () => {
|
|
64
|
-
const harness = createHarness();
|
|
65
|
-
await openPanel(harness);
|
|
66
|
-
harness.session.panel.close();
|
|
67
|
-
await nextTick();
|
|
68
|
-
expect(sentOfType(harness, "leave_conversation")).toEqual([
|
|
69
|
-
{ type: "leave_conversation", conversationId: CONVERSATION_ID },
|
|
70
|
-
]);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
63
|
it("stamps a message with the page unless its chip was removed", async () => {
|
|
74
64
|
const harness = createHarness();
|
|
75
65
|
const panel = await openPanel(harness);
|
|
@@ -116,7 +106,7 @@ describe("notices and errors", () => {
|
|
|
116
106
|
type: "settings_update",
|
|
117
107
|
settings: {
|
|
118
108
|
provider: "codex",
|
|
119
|
-
|
|
109
|
+
defaultModes: { claude: "auto", codex: "normal" },
|
|
120
110
|
thinking: "medium",
|
|
121
111
|
generationMode: "safe",
|
|
122
112
|
allowLocalSkills: false,
|
|
@@ -161,7 +151,7 @@ describe("the history drawer", () => {
|
|
|
161
151
|
id: "c-old",
|
|
162
152
|
title: "Why is order pending?",
|
|
163
153
|
createdAtMs: 1,
|
|
164
|
-
updatedAtMs: Date.now() -
|
|
154
|
+
updatedAtMs: Date.now() - 1_000,
|
|
165
155
|
messageCount: 2,
|
|
166
156
|
filesChanged: 0,
|
|
167
157
|
filesUndone: 1,
|
|
@@ -185,9 +175,7 @@ describe("the history drawer", () => {
|
|
|
185
175
|
expect(drawer.text()).toContain("9.6k tok");
|
|
186
176
|
expect(drawer.text()).toContain("1 file undone");
|
|
187
177
|
await buttonWithText(drawer, "Why is order pending?").trigger("click");
|
|
188
|
-
expect(sentOfType(harness, "leave_conversation")[
|
|
189
|
-
conversationId: CONVERSATION_ID,
|
|
190
|
-
});
|
|
178
|
+
expect(sentOfType(harness, "leave_conversation")).toEqual([]);
|
|
191
179
|
expect(sentOfType(harness, "hello").at(-1)).toMatchObject({
|
|
192
180
|
conversationId: "c-old",
|
|
193
181
|
});
|