@openchambery/web 1.19.7-beta.2 → 1.19.7-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/AgentAvatar-DI2zs2WH.js +1 -0
- package/dist/assets/AssistantView-sV1PVQiM.js +2 -0
- package/dist/assets/DiagramView-D2hqP5VV.js +1 -0
- package/dist/assets/FadeInOnReveal-BWJcUp5P.js +1 -0
- package/dist/assets/Icon-4My8YZwt.js +1 -0
- package/dist/assets/MarkdownLoadingSkeleton-Vezjvk9B.js +3 -0
- package/dist/assets/MarkdownRenderer-x091Z--3.js +2 -0
- package/dist/assets/MarkdownRendererImpl-DML8BSro.js +7 -0
- package/dist/assets/MarkstreamRendererImpl-BK-HM2k3.js +1 -0
- package/dist/assets/{MarkstreamRendererImpl-CR0fBG5w.css → MarkstreamRendererImpl-Ca7RATbs.css} +1 -1
- package/dist/assets/MobileDetailNavigation-DcxkWiks.js +1 -0
- package/dist/assets/MobileShareBridge-TAGcOiyb.js +4 -0
- package/dist/assets/MobileSurface-zqlZzEFQ.js +2 -0
- package/dist/assets/{MultiRunWindow-khpnWyZO.js → MultiRunWindow-iH1d9S14.js} +1 -1
- package/dist/assets/OnboardingScreen-BNEtPmvw.js +1 -0
- package/dist/assets/OverlayScrollbar-BZB21Yp_.js +2 -0
- package/dist/assets/SettingsView-CzW23DZA.js +25 -0
- package/dist/assets/{SettingsWindow-Dx5rU9v5.js → SettingsWindow-DZXCWZid.js} +1 -1
- package/dist/assets/TerminalView-pU0wpD8s.js +8 -0
- package/dist/assets/ToolOutputDialog-Bt7JChFG.js +10 -0
- package/dist/assets/appThemeRegistry-BI7WR2Wo.js +1 -0
- package/dist/assets/{button-z0GOBtWi.js → button-Or0daT9o.js} +1 -1
- package/dist/assets/clipboard-BXxLshVq.js +1 -0
- package/dist/assets/{collapsible-D9zJRpeB.js → collapsible-C-kzSGkq.js} +1 -1
- package/dist/assets/context-menu-Dpjla5p4.js +1 -0
- package/dist/assets/desktopHosts-BzSE0sK7.js +1 -0
- package/dist/assets/desktopRelayRestore-BaKjk1kQ.js +1 -0
- package/dist/assets/{diagram-CoEqmReY.js → diagram-D0mT3mo1.js} +1 -1
- package/dist/assets/{dialog-HCE4eLUW.js → dialog-BkhHBTd4.js} +1 -1
- package/dist/assets/dropdown-menu.styles-Bzo6L3FH.js +1 -0
- package/dist/assets/{es-D9KcV5TX.js → es-BHqnSjoZ.js} +1 -1
- package/dist/assets/fileQueries-DFY6Kiy1.js +1 -0
- package/dist/assets/fileReferenceActions-Di_DoDex.js +1 -0
- package/dist/assets/{fr-DPA9qUl3.js → fr-BfrlJY9i.js} +1 -1
- package/dist/assets/{gitApi-DNTMAa3F.js → gitApi-CeFszLvk.js} +3 -3
- package/dist/assets/{input-BG3VWTIF.js → input-BUsGnzwe.js} +1 -1
- package/dist/assets/insertionBoundaries-DDJNusn2.js +4 -0
- package/dist/assets/{ja-Dsif3NoD.js → ja-DP-7qt-x.js} +1 -1
- package/dist/assets/{ko-Dixx4Ctg.js → ko-E2B5vGQP.js} +1 -1
- package/dist/assets/main-CjnOsKDL.js +169 -0
- package/dist/assets/main-j-Ij13jc.js +2 -0
- package/dist/assets/markdown-worker-BuveaH3O.js +1 -0
- package/dist/assets/markdownTheme-CVS01wHf.js +2 -0
- package/dist/assets/miniChat-DgxavREE.js +2 -0
- package/dist/assets/mobile-C0rwPMPM.js +2 -0
- package/dist/assets/multirun-ot7-bKg7.js +4 -0
- package/dist/assets/outsideFileGrants-BB4GPvCq.js +1 -0
- package/dist/assets/path-utils-jGozTg9d.js +1 -0
- package/dist/assets/persistence-CSZp1CNB.js +3 -0
- package/dist/assets/{pl-Bj78ZGgf.js → pl-r8X5gRM5.js} +1 -1
- package/dist/assets/platform-CenlWqxu.js +2 -0
- package/dist/assets/projectMeta-3yOEc0oI.js +3 -0
- package/dist/assets/{pt-BR-CFL3xSAf.js → pt-BR-DcpdXCf5.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-BPNvFA_N.js +2 -0
- package/dist/assets/renderMobileApp-CNog29Xy.js +28 -0
- package/dist/assets/runtimeConfig-D28kNEYO.js +2 -0
- package/dist/assets/runtimeEndpointReset-CorvFIRk.js +147 -0
- package/dist/assets/sessionChangeSummary-CX2MJIkc.js +1 -0
- package/dist/assets/sessionLookup-CbatbQf7.js +1 -0
- package/dist/assets/sortable-tabs-strip-C5gGnikj.js +1 -0
- package/dist/assets/src-Af9cMFKF.css +2 -0
- package/dist/assets/{store-DzpbXWX1.js → store-BDFdnk6l.js} +2 -2
- package/dist/assets/terminalApi-CIIs9vFx.js +1 -0
- package/dist/assets/textarea-CIO34TZz.js +1 -0
- package/dist/assets/toolHelpers-DEvVP9U0.js +2 -0
- package/dist/assets/{tooltip-CtH61mQh.js → tooltip-BSrJYYYc.js} +1 -1
- package/dist/assets/{uk-BY60Ub3Z.js → uk-Di0vl9V-.js} +1 -1
- package/dist/assets/url-CE_gAfNa.js +1 -0
- package/dist/assets/{ChatContainer-CnOrdAgs.js → useAppFontEffects-CDpeZWK4.js} +44 -39
- package/dist/assets/useAssistantUIStore-BHacrxe_.js +1 -0
- package/dist/assets/useDesktopWindowControlsLayout-D-xPByG0.js +1 -0
- package/dist/assets/useEffectiveDirectory-Dp-tez_r.js +1 -0
- package/dist/assets/useFontPreferences-BxOHKuVw.js +1 -0
- package/dist/assets/useMobileNavigationStore-DE1Pe9A9.js +412 -0
- package/dist/assets/useSessionAutoCleanup-jNJ_WC38.js +1 -0
- package/dist/assets/useUIStore-CqqO11ZI.js +1 -0
- package/dist/assets/useWorkerHighlightedLines-MDnGj3yv.js +7 -0
- package/dist/assets/utils-B0mQXN9L.js +1 -0
- package/dist/assets/{vendor-markstream-react-CwvbEg9R.js → vendor-markstream-react-C3K8YH0d.js} +4 -4
- package/dist/assets/{zh-CN-Cox1LaOW.js → zh-CN-coFCfeQE.js} +1 -1
- package/dist/assets/{zh-TW-B6RU0mxa.js → zh-TW-oDRZ1-yb.js} +1 -1
- package/dist/index.html +9 -11
- package/dist/mini-chat.html +9 -11
- package/dist/mobile.html +15 -17
- package/package.json +2 -1
- package/server/index.js +2 -0
- package/server/lib/assistant-delivery-parts.js +3 -1
- package/server/lib/assistant-delivery-parts.test.js +11 -0
- package/server/lib/assistants/DOCUMENTATION.md +76 -3
- package/server/lib/assistants/assign.js +225 -0
- package/server/lib/assistants/assign.test.js +118 -0
- package/server/lib/assistants/bubbles.js +45 -0
- package/server/lib/assistants/bubbles.test.js +36 -0
- package/server/lib/assistants/cards.js +148 -0
- package/server/lib/assistants/cards.test.js +116 -0
- package/server/lib/assistants/contact-store.js +284 -0
- package/server/lib/assistants/contact-store.test.js +131 -0
- package/server/lib/assistants/contact-tools.js +761 -0
- package/server/lib/assistants/contact-tools.test.js +328 -0
- package/server/lib/assistants/contracts.js +20 -1
- package/server/lib/assistants/harness.js +551 -0
- package/server/lib/assistants/harness.test.js +607 -0
- package/server/lib/assistants/routes.js +44 -4
- package/server/lib/assistants/service.js +930 -17
- package/server/lib/assistants/service.test.js +1163 -45
- package/server/lib/llm/DOCUMENTATION.md +84 -0
- package/server/lib/llm/catalog.js +100 -0
- package/server/lib/llm/catalog.test.js +78 -0
- package/server/lib/llm/completions.js +151 -0
- package/server/lib/llm/completions.test.js +127 -0
- package/server/lib/llm/generate.js +570 -0
- package/server/lib/llm/generate.test.js +459 -0
- package/server/lib/llm/routes.js +47 -0
- package/server/lib/llm/temp-directory.js +37 -0
- package/server/lib/notifications/APNS.md +23 -17
- package/server/lib/notifications/DOCUMENTATION.md +5 -4
- package/server/lib/notifications/apns-runtime.js +139 -20
- package/server/lib/notifications/apns-runtime.test.js +66 -7
- package/server/lib/notifications/apns-titles.js +62 -62
- package/server/lib/notifications/apns-titles.test.js +3 -1
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +177 -86
- package/server/lib/notifications/runtime.test.js +122 -0
- package/server/lib/opencode/feature-routes-runtime.js +30 -0
- package/server/lib/opencode/feature-routes-runtime.test.js +20 -0
- package/server/lib/opencode/settings-runtime.js +18 -2
- package/server/lib/session-goal/DOCUMENTATION.md +8 -1
- package/server/lib/session-goal/runtime.js +20 -0
- package/server/lib/session-index/DOCUMENTATION.md +3 -1
- package/server/lib/session-index/service.js +10 -4
- package/server/lib/session-index/service.test.js +37 -0
- package/server/lib/session-title/runtime.js +1 -0
- package/server/lib/session-title/runtime.test.js +11 -0
- package/dist/assets/AgentAvatar-ziUs4sw3.js +0 -1
- package/dist/assets/AssistantView-DF4yXFgS.js +0 -1
- package/dist/assets/ContextPanel-Cg_uCHzJ.js +0 -285
- package/dist/assets/DiagramView-DYh3Zn2y.js +0 -1
- package/dist/assets/FadeInOnReveal-BAlsRFJH.js +0 -1
- package/dist/assets/Icon-CLpn1mIz.js +0 -1
- package/dist/assets/MarkdownRendererImpl-QzUEpBEm.js +0 -7
- package/dist/assets/MarkstreamRendererImpl-CXrfeUia.js +0 -1
- package/dist/assets/MobileDetailNavigation-DJXEYfq4.js +0 -1
- package/dist/assets/MobileShareBridge-CliVo1YH.js +0 -4
- package/dist/assets/MobileSurface-9Xh6WQkY.js +0 -2
- package/dist/assets/OnboardingScreen-CJYyR54B.js +0 -1
- package/dist/assets/OverlayScrollbar-C-jZ8ITG.js +0 -2
- package/dist/assets/SettingsView-DyCZehFG.js +0 -25
- package/dist/assets/TerminalView-XSALfUaH.js +0 -8
- package/dist/assets/ToolOutputDialog-BfuXazW9.js +0 -10
- package/dist/assets/appThemeRegistry-DDWrniMM.js +0 -2
- package/dist/assets/clipboard-Bz0Zm9VJ.js +0 -1
- package/dist/assets/desktop-CfLlmafK.js +0 -1
- package/dist/assets/desktopHosts-C1Ns3DUu.js +0 -1
- package/dist/assets/desktopRelayRestore-LBt0CBOj.js +0 -1
- package/dist/assets/fileQueries-Dnn15WsA.js +0 -1
- package/dist/assets/insertionBoundaries-DMxuUnL_.js +0 -4
- package/dist/assets/main-B9HVtgXE.js +0 -2
- package/dist/assets/main-ZNyIs3Sn.js +0 -29
- package/dist/assets/markdown-worker-y3ewaqAK.js +0 -4
- package/dist/assets/markdownTheme-TdhQOZSN.js +0 -2
- package/dist/assets/miniChat-q0T0WGkZ.js +0 -2
- package/dist/assets/mobile-DEduvLJj.js +0 -2
- package/dist/assets/multirun-DU-wrxlG.js +0 -4
- package/dist/assets/persistence-CmjKImz2.js +0 -3
- package/dist/assets/platform-skqcMARt.js +0 -2
- package/dist/assets/projectMeta-DNWx6Uva.js +0 -3
- package/dist/assets/radio-BEonIX1k.js +0 -1
- package/dist/assets/renderElectronMiniChatApp--JYWY41L.js +0 -2
- package/dist/assets/renderMobileApp-B0Ix_uNo.js +0 -25
- package/dist/assets/runtime-switch-BkpTTbEi.js +0 -1
- package/dist/assets/runtimeConfig-BTDIG7mX.js +0 -2
- package/dist/assets/runtimeEndpointReset-CZQCYtVI.js +0 -4
- package/dist/assets/shikiHighlight-C9Cx56Qg.js +0 -1
- package/dist/assets/sortable-tabs-strip-Bj8N0VWf.js +0 -1
- package/dist/assets/src-2eFAiBLq.css +0 -2
- package/dist/assets/terminalApi-CNqqwjfG.js +0 -1
- package/dist/assets/url-KeihbE0d.js +0 -1
- package/dist/assets/useAppFontEffects-CKLSamae.js +0 -7
- package/dist/assets/useAssistantUIStore-DaplPyb0.js +0 -1
- package/dist/assets/useDesktopWindowControlsLayout-DJSkFfhT.js +0 -1
- package/dist/assets/useEffectiveDirectory-B3rbY1Kv.js +0 -1
- package/dist/assets/useFontPreferences-D8emhcNv.js +0 -1
- package/dist/assets/useMobileNavigationStore-CAvVUItx.js +0 -412
- package/dist/assets/useSessionAutoCleanup-DxNgqJ8s.js +0 -1
- package/dist/assets/useUIStore-B7KJS5G7.js +0 -1
- package/dist/assets/useWorkerHighlightedLines-DiDF68LR.js +0 -7
- package/dist/assets/utils-CTL16t2f.js +0 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { AssignError, ASSIGN_CODES, PROJECT_REQUIRED_MESSAGE } from './assign.js';
|
|
3
|
+
import {
|
|
4
|
+
ASSIGN_SESSION_TOOL_NAME,
|
|
5
|
+
CREATE_ASSISTANT_TOOL_NAME,
|
|
6
|
+
LIST_PROJECTS_TOOL_NAME,
|
|
7
|
+
LIST_SESSIONS_TOOL_NAME,
|
|
8
|
+
MESSAGE_ASSISTANT_TOOL_NAME,
|
|
9
|
+
NEW_CONVERSATION_CONFIRM_BUBBLE,
|
|
10
|
+
NEW_CONVERSATION_TOOL_NAME,
|
|
11
|
+
SCHEDULE_TASK_TOOL_NAME,
|
|
12
|
+
confirmBubbleAfterContactReset,
|
|
13
|
+
createContactTools,
|
|
14
|
+
detectRequestedContactTools,
|
|
15
|
+
filterRegisteredProjects,
|
|
16
|
+
formatContactToolsPrompt,
|
|
17
|
+
formatRegisteredProjectsPrompt,
|
|
18
|
+
matchesProjectQuery,
|
|
19
|
+
parseContactToolCalls,
|
|
20
|
+
resolveContactProviderModel,
|
|
21
|
+
resolvePeerAssistant,
|
|
22
|
+
stripContactToolFences,
|
|
23
|
+
} from './contact-tools.js';
|
|
24
|
+
|
|
25
|
+
describe('contact tool protocol', () => {
|
|
26
|
+
it('parses an assign_session fence and strips it from chat text', () => {
|
|
27
|
+
const text = 'On it.\n\n```openchamber-tool\n{"name":"assign_session","arguments":{"prompt":"Fix login","projectPath":"/repo"}}\n```';
|
|
28
|
+
const parsed = parseContactToolCalls(text, [ASSIGN_SESSION_TOOL_NAME]);
|
|
29
|
+
expect(parsed.chatText).toBe('On it.');
|
|
30
|
+
expect(parsed.toolCall).toEqual({
|
|
31
|
+
name: ASSIGN_SESSION_TOOL_NAME,
|
|
32
|
+
arguments: { prompt: 'Fix login', projectPath: '/repo' },
|
|
33
|
+
});
|
|
34
|
+
expect(stripContactToolFences(text)).toBe('On it.');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('parses create_assistant and schedule_task fences from natural-language replies', () => {
|
|
38
|
+
const create = parseContactToolCalls(
|
|
39
|
+
'好。\n\n```openchamber-tool\n{"name":"create_assistant","arguments":{"name":"FlowQA","model":"opencode-go/deepseek-v4-flash"}}\n```',
|
|
40
|
+
[CREATE_ASSISTANT_TOOL_NAME, SCHEDULE_TASK_TOOL_NAME, ASSIGN_SESSION_TOOL_NAME],
|
|
41
|
+
);
|
|
42
|
+
expect(create.chatText).toBe('好。');
|
|
43
|
+
expect(create.toolCall).toEqual({
|
|
44
|
+
name: CREATE_ASSISTANT_TOOL_NAME,
|
|
45
|
+
arguments: { name: 'FlowQA', model: 'opencode-go/deepseek-v4-flash' },
|
|
46
|
+
});
|
|
47
|
+
const schedule = parseContactToolCalls(
|
|
48
|
+
'```openchamber-tool\n{"name":"schedule_task","arguments":{"name":"Daily ping","prompt":"ping","time":"18:00","timezone":"Asia/Shanghai"}}\n```',
|
|
49
|
+
[CREATE_ASSISTANT_TOOL_NAME, SCHEDULE_TASK_TOOL_NAME],
|
|
50
|
+
);
|
|
51
|
+
const peer = parseContactToolCalls(
|
|
52
|
+
'```openchamber-tool\n{"name":"message_assistant","arguments":{"to":"PeerQA","text":"hello-from-assistant 写好了"}}\n```',
|
|
53
|
+
[MESSAGE_ASSISTANT_TOOL_NAME],
|
|
54
|
+
);
|
|
55
|
+
expect(peer.toolCall).toEqual({
|
|
56
|
+
name: MESSAGE_ASSISTANT_TOOL_NAME,
|
|
57
|
+
arguments: { to: 'PeerQA', text: 'hello-from-assistant 写好了' },
|
|
58
|
+
});
|
|
59
|
+
expect(schedule.toolCall).toEqual({
|
|
60
|
+
name: SCHEDULE_TASK_TOOL_NAME,
|
|
61
|
+
arguments: { name: 'Daily ping', prompt: 'ping', time: '18:00', timezone: 'Asia/Shanghai' },
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('parses a bare {name, arguments} object anywhere in the assistant text', () => {
|
|
66
|
+
const parsed = parseContactToolCalls(
|
|
67
|
+
'好的,我来直接创建这个助理,不开编码会话。\n{"name":"create_assistant","arguments":{"name":"FlowNL","model":"opencode-go/deepseek-v4-flash"}}',
|
|
68
|
+
[CREATE_ASSISTANT_TOOL_NAME, ASSIGN_SESSION_TOOL_NAME],
|
|
69
|
+
);
|
|
70
|
+
expect(parsed.toolCall).toEqual({
|
|
71
|
+
name: CREATE_ASSISTANT_TOOL_NAME,
|
|
72
|
+
arguments: { name: 'FlowNL', model: 'opencode-go/deepseek-v4-flash' },
|
|
73
|
+
});
|
|
74
|
+
expect(parsed.chatText).toContain('好的,我来直接创建这个助理');
|
|
75
|
+
expect(parsed.chatText).not.toContain('create_assistant');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('detects 建助理 without treating 不要开编码 session as assign_session', () => {
|
|
79
|
+
const tools = [
|
|
80
|
+
NEW_CONVERSATION_TOOL_NAME,
|
|
81
|
+
LIST_PROJECTS_TOOL_NAME,
|
|
82
|
+
LIST_SESSIONS_TOOL_NAME,
|
|
83
|
+
CREATE_ASSISTANT_TOOL_NAME,
|
|
84
|
+
SCHEDULE_TASK_TOOL_NAME,
|
|
85
|
+
MESSAGE_ASSISTANT_TOOL_NAME,
|
|
86
|
+
ASSIGN_SESSION_TOOL_NAME,
|
|
87
|
+
];
|
|
88
|
+
expect(detectRequestedContactTools('帮我新建一个助理,名叫 FlowNL,不要开编码 session', tools)).toEqual([
|
|
89
|
+
CREATE_ASSISTANT_TOOL_NAME,
|
|
90
|
+
]);
|
|
91
|
+
expect(detectRequestedContactTools('每天 18:00 排一个 ping 定时任务', tools)).toEqual([SCHEDULE_TASK_TOOL_NAME]);
|
|
92
|
+
expect(detectRequestedContactTools('建会话写一个文件', tools)).toEqual([ASSIGN_SESSION_TOOL_NAME]);
|
|
93
|
+
expect(detectRequestedContactTools('给 PeerQA 说一声 hello-from-assistant 写好了', tools)).toEqual([
|
|
94
|
+
MESSAGE_ASSISTANT_TOOL_NAME,
|
|
95
|
+
]);
|
|
96
|
+
expect(detectRequestedContactTools('开新对话', tools)).toEqual([NEW_CONVERSATION_TOOL_NAME]);
|
|
97
|
+
expect(detectRequestedContactTools('new conversation please', tools)).toEqual([NEW_CONVERSATION_TOOL_NAME]);
|
|
98
|
+
expect(detectRequestedContactTools('clear chat', tools)).toEqual([NEW_CONVERSATION_TOOL_NAME]);
|
|
99
|
+
expect(detectRequestedContactTools('找项目 openchamber yee', tools)).toEqual([LIST_PROJECTS_TOOL_NAME]);
|
|
100
|
+
expect(detectRequestedContactTools('看看现有对话', tools)).toEqual([LIST_SESSIONS_TOOL_NAME]);
|
|
101
|
+
expect(detectRequestedContactTools('list sessions in that project', tools)).toEqual([LIST_SESSIONS_TOOL_NAME]);
|
|
102
|
+
expect(detectRequestedContactTools('开新对话', tools)).not.toContain(ASSIGN_SESSION_TOOL_NAME);
|
|
103
|
+
expect(detectRequestedContactTools('不要开编码 session', tools)).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('ignores bash/edit tool fences', () => {
|
|
107
|
+
const text = '```openchamber-tool\n{"name":"bash","arguments":{"command":"ls"}}\n```';
|
|
108
|
+
expect(parseContactToolCalls(text, ['bash', ASSIGN_SESSION_TOOL_NAME]).toolCall).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('tells DeepSeek to call tools from natural language, not slash commands', () => {
|
|
112
|
+
const prompt = formatContactToolsPrompt(createContactTools());
|
|
113
|
+
expect(prompt).toContain('new_conversation');
|
|
114
|
+
expect(prompt).toContain('list_projects');
|
|
115
|
+
expect(prompt).toContain('list_sessions');
|
|
116
|
+
expect(prompt).toContain('create_assistant');
|
|
117
|
+
expect(prompt).toContain('schedule_task');
|
|
118
|
+
expect(prompt).toContain('message_assistant');
|
|
119
|
+
expect(prompt).toContain('assign_session');
|
|
120
|
+
expect(prompt).toContain('开新对话');
|
|
121
|
+
expect(prompt).toContain('找项目');
|
|
122
|
+
expect(prompt).toContain('现有对话');
|
|
123
|
+
expect(prompt).toContain('建助理');
|
|
124
|
+
expect(prompt).toContain('排定时任务');
|
|
125
|
+
expect(prompt).toContain('说一声');
|
|
126
|
+
expect(prompt).toContain('session/new');
|
|
127
|
+
expect(prompt).not.toContain('/card');
|
|
128
|
+
expect(prompt).not.toContain('/dm');
|
|
129
|
+
expect(prompt).toContain('A reply without the tool call does nothing');
|
|
130
|
+
expect(prompt).toContain('已创建');
|
|
131
|
+
expect(prompt).toContain('Never claim you cannot see projects');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('fuzzy-matches project labels like openchamber yee / openchamer yee', () => {
|
|
135
|
+
const project = { id: 'p1', path: '/Users/me/Code/openchamber-yee', label: 'OpenChamber Yee' };
|
|
136
|
+
expect(matchesProjectQuery(project, 'openchamber yee')).toBe(true);
|
|
137
|
+
expect(matchesProjectQuery(project, 'openchamer yee')).toBe(true);
|
|
138
|
+
expect(matchesProjectQuery(project, 'missing')).toBe(false);
|
|
139
|
+
expect(filterRegisteredProjects([project, { id: 'p2', path: '/other' }], 'openchamber')).toEqual([
|
|
140
|
+
{ id: 'p1', path: '/Users/me/Code/openchamber-yee', label: 'OpenChamber Yee' },
|
|
141
|
+
]);
|
|
142
|
+
expect(formatRegisteredProjectsPrompt([project])).toContain('OpenChamber Yee');
|
|
143
|
+
expect(formatRegisteredProjectsPrompt([project])).toContain('never say you cannot see registered projects');
|
|
144
|
+
expect(formatRegisteredProjectsPrompt([])).toContain('none');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('resolves provider/model from a combined OpenCode id', () => {
|
|
148
|
+
expect(resolveContactProviderModel({ model: 'opencode-go/deepseek-v4-flash' })).toEqual({
|
|
149
|
+
providerID: 'opencode-go',
|
|
150
|
+
modelID: 'deepseek-v4-flash',
|
|
151
|
+
});
|
|
152
|
+
expect(resolveContactProviderModel({}, { providerID: 'p', modelID: 'm' })).toEqual({
|
|
153
|
+
providerID: 'p',
|
|
154
|
+
modelID: 'm',
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('createContactTools', () => {
|
|
160
|
+
it('exposes create_assistant, schedule_task, and assign_session and returns success cards', async () => {
|
|
161
|
+
const onCard = vi.fn();
|
|
162
|
+
const resetContact = vi.fn(async () => ({ reset: true }));
|
|
163
|
+
const tools = createContactTools({
|
|
164
|
+
resetContact,
|
|
165
|
+
createAssistant: async (input) => ({
|
|
166
|
+
id: 'asst_flow',
|
|
167
|
+
name: input.name,
|
|
168
|
+
providerID: input.providerID,
|
|
169
|
+
modelID: input.modelID,
|
|
170
|
+
mode: 'continuous',
|
|
171
|
+
}),
|
|
172
|
+
scheduleTask: async (input) => ({
|
|
173
|
+
taskID: 'task_1',
|
|
174
|
+
projectID: 'proj_1',
|
|
175
|
+
name: input.name,
|
|
176
|
+
kind: 'daily',
|
|
177
|
+
time: input.time,
|
|
178
|
+
timezone: input.timezone,
|
|
179
|
+
prompt: input.prompt,
|
|
180
|
+
}),
|
|
181
|
+
assignWork: async () => ({
|
|
182
|
+
sessionID: 'ses_1',
|
|
183
|
+
directory: '/repo',
|
|
184
|
+
title: 'Login',
|
|
185
|
+
status: 'busy',
|
|
186
|
+
}),
|
|
187
|
+
deliverPeerMessage: async (input) => ({
|
|
188
|
+
admitted: true,
|
|
189
|
+
role: 'peer',
|
|
190
|
+
toAssistantID: input.toAssistantID,
|
|
191
|
+
}),
|
|
192
|
+
listAssistants: async () => [{ id: 'asst_peer', name: 'PeerQA' }],
|
|
193
|
+
currentAssistant: { id: 'asst_host', providerID: 'p', modelID: 'm' },
|
|
194
|
+
onCard,
|
|
195
|
+
});
|
|
196
|
+
expect(tools.map((tool) => tool.name)).toEqual([
|
|
197
|
+
NEW_CONVERSATION_TOOL_NAME,
|
|
198
|
+
LIST_PROJECTS_TOOL_NAME,
|
|
199
|
+
LIST_SESSIONS_TOOL_NAME,
|
|
200
|
+
CREATE_ASSISTANT_TOOL_NAME,
|
|
201
|
+
SCHEDULE_TASK_TOOL_NAME,
|
|
202
|
+
MESSAGE_ASSISTANT_TOOL_NAME,
|
|
203
|
+
ASSIGN_SESSION_TOOL_NAME,
|
|
204
|
+
]);
|
|
205
|
+
expect(tools.some((tool) => ['bash', 'edit', 'read', 'write'].includes(tool.name))).toBe(false);
|
|
206
|
+
|
|
207
|
+
const reset = await tools.find((tool) => tool.name === NEW_CONVERSATION_TOOL_NAME).execute('call_0', {});
|
|
208
|
+
expect(resetContact).toHaveBeenCalledTimes(1);
|
|
209
|
+
expect(reset.details.card).toBeUndefined();
|
|
210
|
+
expect(reset.details.reset).toBe(true);
|
|
211
|
+
expect(reset.terminate).toBe(true);
|
|
212
|
+
expect(reset.content[0].text).toBe(NEW_CONVERSATION_CONFIRM_BUBBLE);
|
|
213
|
+
|
|
214
|
+
const created = await tools.find((tool) => tool.name === CREATE_ASSISTANT_TOOL_NAME).execute('call_1', { name: 'FlowQA', model: 'opencode-go/deepseek-v4-flash' });
|
|
215
|
+
expect(created.details.card).toMatchObject({
|
|
216
|
+
type: 'card',
|
|
217
|
+
cardType: 'assistant',
|
|
218
|
+
assistantID: 'asst_flow',
|
|
219
|
+
name: 'FlowQA',
|
|
220
|
+
providerID: 'opencode-go',
|
|
221
|
+
modelID: 'deepseek-v4-flash',
|
|
222
|
+
});
|
|
223
|
+
expect(created.terminate).toBe(false);
|
|
224
|
+
|
|
225
|
+
const scheduled = await tools.find((tool) => tool.name === SCHEDULE_TASK_TOOL_NAME).execute('call_2', {
|
|
226
|
+
name: 'Daily ping',
|
|
227
|
+
prompt: 'ping',
|
|
228
|
+
time: '18:00',
|
|
229
|
+
timezone: 'Asia/Shanghai',
|
|
230
|
+
});
|
|
231
|
+
expect(scheduled.details.card).toMatchObject({
|
|
232
|
+
type: 'card',
|
|
233
|
+
cardType: 'schedule',
|
|
234
|
+
taskID: 'task_1',
|
|
235
|
+
name: 'Daily ping',
|
|
236
|
+
time: '18:00',
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const messaged = await tools.find((tool) => tool.name === MESSAGE_ASSISTANT_TOOL_NAME).execute('call_3', {
|
|
240
|
+
to: 'PeerQA',
|
|
241
|
+
text: 'hello-from-assistant 写好了',
|
|
242
|
+
});
|
|
243
|
+
expect(messaged.details.card).toBeUndefined();
|
|
244
|
+
expect(messaged.content[0].text).toContain('Sent to PeerQA');
|
|
245
|
+
expect(messaged.details.toAssistantID).toBe('asst_peer');
|
|
246
|
+
|
|
247
|
+
const assigned = await tools.find((tool) => tool.name === ASSIGN_SESSION_TOOL_NAME).execute('call_4', { prompt: 'Fix login' });
|
|
248
|
+
expect(assigned.details.card).toMatchObject({
|
|
249
|
+
type: 'card',
|
|
250
|
+
cardType: 'session',
|
|
251
|
+
sessionID: 'ses_1',
|
|
252
|
+
title: 'Login',
|
|
253
|
+
});
|
|
254
|
+
expect(onCard).toHaveBeenCalledTimes(3);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('resolves a peer by name and rejects a missing recipient', () => {
|
|
258
|
+
const listed = [{ id: 'asst_peer', name: 'PeerQA' }, { id: 'asst_host', name: 'DeepSeekQA' }];
|
|
259
|
+
expect(resolvePeerAssistant({ to: 'PeerQA' }, listed, { id: 'asst_host' })).toEqual(listed[0]);
|
|
260
|
+
expect(() => resolvePeerAssistant({ to: 'Missing' }, listed, { id: 'asst_host' })).toThrow(/No assistant named/);
|
|
261
|
+
expect(() => resolvePeerAssistant({ to: 'DeepSeekQA' }, listed, { id: 'asst_host' })).toThrow(/same assistant/);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('returns a clear project_required result instead of throwing', async () => {
|
|
265
|
+
const tools = createContactTools({
|
|
266
|
+
assignWork: async () => {
|
|
267
|
+
throw new AssignError(ASSIGN_CODES.PROJECT_REQUIRED, PROJECT_REQUIRED_MESSAGE);
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
const result = await tools.find((tool) => tool.name === ASSIGN_SESSION_TOOL_NAME).execute('call_1', { prompt: 'Fix login' });
|
|
271
|
+
expect(result.details.error).toBe('project_required');
|
|
272
|
+
expect(result.content[0].text).toContain('Add a project in Settings');
|
|
273
|
+
expect(result.content[0].text).toContain('assistant-workspaces');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('lists projects and sessions and surfaces list_sessions failure', async () => {
|
|
277
|
+
const tools = createContactTools({
|
|
278
|
+
listProjects: async () => [
|
|
279
|
+
{ id: 'proj_yee', path: '/repo/openchamber-yee', label: 'OpenChamber Yee' },
|
|
280
|
+
{ id: 'proj_other', path: '/repo/other', label: 'Other' },
|
|
281
|
+
],
|
|
282
|
+
listSessions: async () => ({
|
|
283
|
+
sessions: [
|
|
284
|
+
{ sessionID: 'ses_1', title: 'Login fix', directory: '/repo/openchamber-yee', updatedAt: 2 },
|
|
285
|
+
],
|
|
286
|
+
truncated: false,
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
289
|
+
const listed = await tools.find((tool) => tool.name === LIST_PROJECTS_TOOL_NAME).execute('call_p', {
|
|
290
|
+
query: 'openchamer yee',
|
|
291
|
+
});
|
|
292
|
+
expect(listed.details.projects).toEqual([
|
|
293
|
+
{ id: 'proj_yee', path: '/repo/openchamber-yee', label: 'OpenChamber Yee' },
|
|
294
|
+
]);
|
|
295
|
+
const sessions = await tools.find((tool) => tool.name === LIST_SESSIONS_TOOL_NAME).execute('call_s', {
|
|
296
|
+
projectPath: '/repo/openchamber-yee',
|
|
297
|
+
});
|
|
298
|
+
expect(sessions.details.sessions).toEqual([
|
|
299
|
+
expect.objectContaining({ sessionID: 'ses_1', title: 'Login fix' }),
|
|
300
|
+
]);
|
|
301
|
+
|
|
302
|
+
const failing = createContactTools({
|
|
303
|
+
listSessions: async () => {
|
|
304
|
+
throw new AssignError(ASSIGN_CODES.UPSTREAM, 'Session index is unavailable.');
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
const failed = await failing.find((tool) => tool.name === LIST_SESSIONS_TOOL_NAME).execute('call_fail', {});
|
|
308
|
+
expect(failed.details.error).toBe('upstream_error');
|
|
309
|
+
expect(failed.content[0].text).toContain('Session index is unavailable');
|
|
310
|
+
expect(failed.details.sessions).toBeUndefined();
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('confirmBubbleAfterContactReset', () => {
|
|
315
|
+
it('keeps the canonical confirm and drops leftover attachment bubbles', () => {
|
|
316
|
+
expect(confirmBubbleAfterContactReset([
|
|
317
|
+
NEW_CONVERSATION_CONFIRM_BUBBLE,
|
|
318
|
+
'I still see your dot.png and note.txt.',
|
|
319
|
+
'Those attachments are still in context.',
|
|
320
|
+
])).toEqual([NEW_CONVERSATION_CONFIRM_BUBBLE]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('uses the canonical confirm when leftover text is the only extracted bubble', () => {
|
|
324
|
+
expect(confirmBubbleAfterContactReset([
|
|
325
|
+
'I still see your dot.png and note.txt from earlier.',
|
|
326
|
+
])).toEqual([NEW_CONVERSATION_CONFIRM_BUBBLE]);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
export const assistantContractFixtures = Object.freeze({
|
|
2
|
-
assistant: Object.freeze({ id: 'assistant_fixture', revision: 4, enabled: true, name: 'Fixture', defaultPrompt: '', workspacePath: null, managedWorkspacePath: '/data/assistant-workspaces/assistant_fixture', effectiveWorkspacePath: '/workspace', providerID: 'provider_fixture', modelID: 'model_fixture', agent: null, variant: null, mode: 'continuous', sessionID: 'ses_fixture', sessionGeneration: 4, historySessionIDs: Object.freeze([]), historySessionCount: 0, createdAt: 1234, updatedAt: 1234, tombstoneAt: null }),
|
|
2
|
+
assistant: Object.freeze({ id: 'assistant_fixture', revision: 4, enabled: true, name: 'Fixture', defaultPrompt: '', workspacePath: null, managedWorkspacePath: '/data/assistant-workspaces/assistant_fixture', effectiveWorkspacePath: '/workspace', providerID: 'provider_fixture', modelID: 'model_fixture', agent: null, variant: null, mode: 'continuous', sessionID: 'ses_fixture', sessionGeneration: 4, historySessionIDs: Object.freeze([]), historySessionCount: 0, assignedSessionIDs: Object.freeze([]), working: false, createdAt: 1234, updatedAt: 1234, tombstoneAt: null }),
|
|
3
3
|
sessionBinding: Object.freeze({ sessionID: 'ses_fixture', directory: '/workspace', sessionGeneration: 4 }),
|
|
4
4
|
compactResponse: Object.freeze({ binding: Object.freeze({ sessionID: 'ses_fixture', directory: '/workspace', sessionGeneration: 4 }), summarized: true }),
|
|
5
5
|
messageAdmission: Object.freeze({ binding: Object.freeze({ sessionID: 'ses_fixture', directory: '/workspace', sessionGeneration: 4 }), messageID: 'msg_fixture', admitted: true }),
|
|
6
6
|
abortResponse: Object.freeze({ binding: Object.freeze({ sessionID: 'ses_fixture', directory: '/workspace', sessionGeneration: 4 }), aborted: true }),
|
|
7
7
|
shareOperation: Object.freeze({ operationID: 'share_fixture', assistantID: 'assistant_fixture', sessionID: 'ses_fixture', messageID: 'msg_fixture', state: 'running', phase: 'submitted', attempt: 1, leaseExpiresAt: 1234, errorCode: null }),
|
|
8
8
|
historicalMessages: Object.freeze({ entries: Object.freeze([Object.freeze({ sessionID: 'ses_fixture', directory: null, info: Object.freeze({ id: 'msg_fixture', sessionID: 'ses_fixture', role: 'assistant', time: Object.freeze({ created: 1234 }) }), parts: Object.freeze([]) })]), nextCursor: null, complete: true }),
|
|
9
|
+
assistantScheduledTasks: Object.freeze({
|
|
10
|
+
tasks: Object.freeze([
|
|
11
|
+
Object.freeze({
|
|
12
|
+
assistantID: 'assistant_fixture',
|
|
13
|
+
projectID: 'proj_fixture',
|
|
14
|
+
taskID: 'task_fixture',
|
|
15
|
+
createdAt: 1234,
|
|
16
|
+
projectPath: '/workspace',
|
|
17
|
+
projectLabel: 'Fixture',
|
|
18
|
+
task: Object.freeze({
|
|
19
|
+
id: 'task_fixture',
|
|
20
|
+
name: 'Daily ping',
|
|
21
|
+
enabled: true,
|
|
22
|
+
schedule: Object.freeze({ kind: 'daily', time: '18:00', timezone: 'Asia/Shanghai' }),
|
|
23
|
+
execution: Object.freeze({ prompt: 'ping', providerID: 'provider_fixture', modelID: 'model_fixture' }),
|
|
24
|
+
}),
|
|
25
|
+
}),
|
|
26
|
+
]),
|
|
27
|
+
}),
|
|
9
28
|
});
|