@openchambery/web 1.19.12 → 1.19.13-beta.2
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-CX0a1rKE.js → AgentAvatar-mbvX7YlW.js} +1 -1
- package/dist/assets/AssistantView-D5c557Oe.js +2 -0
- package/dist/assets/DiagramView-CjNUrHsU.js +1 -0
- package/dist/assets/FadeInOnReveal-moBbL8Ll.js +1 -0
- package/dist/assets/{Icon-BYM02QyU.js → Icon-CkzzhaLr.js} +1 -1
- package/dist/assets/MarkdownRenderer-BHfbAwVO.js +2 -0
- package/dist/assets/MarkdownRendererImpl-CkBoUibc.js +7 -0
- package/dist/assets/{MarkstreamRendererImpl-BFdJzxia.js → MarkstreamRendererImpl-BrXUOI9g.js} +1 -1
- package/dist/assets/MobileDetailNavigation-aY9-HwBN.js +1 -0
- package/dist/assets/MobileShareBridge-CRt_8DmM.js +4 -0
- package/dist/assets/MobileSurface-CIhtF-CU.js +2 -0
- package/dist/assets/{MultiRunWindow-C3p3lj3U.js → MultiRunWindow-DDq6C8t7.js} +1 -1
- package/dist/assets/OnboardingScreen-j0iCyH7f.js +1 -0
- package/dist/assets/{OverlayScrollbar-KUpvYHdv.js → OverlayScrollbar-CDC5B3RH.js} +1 -1
- package/dist/assets/SettingsView-DFWWMvAj.js +25 -0
- package/dist/assets/{SettingsWindow-8IDuPZA5.js → SettingsWindow-CnYfBplB.js} +1 -1
- package/dist/assets/TerminalView-D5CQFVVm.js +8 -0
- package/dist/assets/ToolOutputDialog-VMI7TQFT.js +10 -0
- package/dist/assets/{appThemeRegistry-B87OLIgc.js → appThemeRegistry-UIrP0-5h.js} +1 -1
- package/dist/assets/assistant-attachment-cache-C0aRnFkj.js +1 -0
- package/dist/assets/{button-C0cth9tl.js → button-xx1UOBA5.js} +1 -1
- package/dist/assets/{clipboard-DJ4nQEuY.js → clipboard-CUasgPiF.js} +1 -1
- package/dist/assets/{collapsible-DFT9cE80.js → collapsible-CACHBRYZ.js} +1 -1
- package/dist/assets/{context-menu-CAp74NfV.js → context-menu-CYbm4wBZ.js} +1 -1
- package/dist/assets/desktopHosts-DILvrAok.js +1 -0
- package/dist/assets/desktopRelayRestore-t8asCr3m.js +1 -0
- package/dist/assets/{diagram-BryzMuta.js → diagram-u0BC9uuY.js} +1 -1
- package/dist/assets/{dialog-CgbV5ljg.js → dialog-BwQ3rY_x.js} +1 -1
- package/dist/assets/dualLimitLru-D8Sphgua.js +1 -0
- package/dist/assets/{es-BHqnSjoZ.js → es-CRjOwvYM.js} +1 -1
- package/dist/assets/fileQueries-Bgly-E8f.js +1 -0
- package/dist/assets/{fr-BfrlJY9i.js → fr-D9taEN9L.js} +1 -1
- package/dist/assets/{gitApi-tTtHQVA3.js → gitApi-Bu4W3bgD.js} +1 -1
- package/dist/assets/imageSource-BlAJJUEI.js +1 -0
- package/dist/assets/{input-DFLpqjGR.js → input-CI730njs.js} +1 -1
- package/dist/assets/insertionBoundaries-B1AqxsBD.js +4 -0
- package/dist/assets/{ja-DP-7qt-x.js → ja-CYQ5aoWp.js} +1 -1
- package/dist/assets/{ko-E2B5vGQP.js → ko-BwASV0Q0.js} +1 -1
- package/dist/assets/main-CqcB6tKc.js +169 -0
- package/dist/assets/main-DG-k0j8A.js +2 -0
- package/dist/assets/markdown-worker-CptoICmq.js +2 -0
- package/dist/assets/markdownHeightCache-DEjGKLv8.js +3 -0
- package/dist/assets/markdownTheme-DOfGg-A7.js +1 -0
- package/dist/assets/miniChat-BQtYQrsh.js +2 -0
- package/dist/assets/mobile-8iyqtXkr.js +2 -0
- package/dist/assets/{multirun-B0V3hdnD.js → multirun-CG_FCb_Z.js} +1 -1
- package/dist/assets/persistence-YbQKzHUQ.js +3 -0
- package/dist/assets/{pl-r8X5gRM5.js → pl-DUI1vPil.js} +1 -1
- package/dist/assets/platform-DzsGqlGW.js +2 -0
- package/dist/assets/projectMeta-DMpMDWE1.js +3 -0
- package/dist/assets/{pt-BR-DcpdXCf5.js → pt-BR-CZQFJllP.js} +1 -1
- package/dist/assets/relay-image-stream-DM7atqJs.js +2 -0
- package/dist/assets/renderElectronMiniChatApp-DBzRdJl8.js +2 -0
- package/dist/assets/renderMobileApp-CIseqCyn.js +28 -0
- package/dist/assets/runtime-auth-D66yq6ie.js +1 -0
- package/dist/assets/runtime-fetch-zIalUnbb.js +1 -0
- package/dist/assets/runtime-switch-LvWDU-SL.js +1 -0
- package/dist/assets/runtimeConfig-D8nJYB-e.js +2 -0
- package/dist/assets/runtimeEndpointReset-CSub_ecV.js +147 -0
- package/dist/assets/{sessionChangeSummary-hVby3sJk.js → sessionChangeSummary-DQDZ5HW-.js} +1 -1
- package/dist/assets/{sessionLookup-B6P_aCun.js → sessionLookup-BiBD-4wC.js} +1 -1
- package/dist/assets/sortable-tabs-strip-Dom6Sthu.js +1 -0
- package/dist/assets/src-AA-5nXsF.css +2 -0
- package/dist/assets/{store-BDFdnk6l.js → store-CSnH_QXM.js} +2 -2
- package/dist/assets/terminalApi-noa4Cdbh.js +1 -0
- package/dist/assets/{textarea-Cgo_nWUU.js → textarea-BfNtUdJE.js} +1 -1
- package/dist/assets/{tooltip-BifLJ8o0.js → tooltip-DPxumadF.js} +1 -1
- package/dist/assets/{uk-Di0vl9V-.js → uk-C1FYZlmA.js} +1 -1
- package/dist/assets/{url-Pk6tQ1EZ.js → url-CVhsI9kR.js} +1 -1
- package/dist/assets/{useAppFontEffects-Dmes1chr.js → useAppFontEffects-qHt5uaFN.js} +32 -32
- package/dist/assets/useAssistantUIStore-CLPCNoj_.js +1 -0
- package/dist/assets/useDesktopWindowControlsLayout-BipyIXiK.js +1 -0
- package/dist/assets/{useEffectiveDirectory-CU2ChP0J.js → useEffectiveDirectory-DSNCCbCt.js} +1 -1
- package/dist/assets/useFontPreferences-CB-cGo3I.js +1 -0
- package/dist/assets/useMobileNavigationStore-C1NJGAik.js +413 -0
- package/dist/assets/useSessionAutoCleanup-Dcn6Unvd.js +2 -0
- package/dist/assets/useUIStore-CxOoCv7B.js +1 -0
- package/dist/assets/useWorkerHighlightedLines-CzpT4M7V.js +7 -0
- package/dist/assets/{utils-BiK3tBGM.js → utils-BHTVlVov.js} +1 -1
- package/dist/assets/{zh-CN-coFCfeQE.js → zh-CN-C96qP1ip.js} +1 -1
- package/dist/assets/{zh-TW-oDRZ1-yb.js → zh-TW-BsRk1Rbb.js} +1 -1
- package/dist/index.html +12 -9
- package/dist/mini-chat.html +12 -9
- package/dist/mobile.html +12 -9
- package/package.json +1 -1
- package/server/index.js +19 -0
- package/server/lib/assistant-delivery-parts.js +19 -1
- package/server/lib/assistants/DOCUMENTATION.md +56 -11
- package/server/lib/assistants/assign.js +455 -30
- package/server/lib/assistants/assign.test.js +351 -0
- package/server/lib/assistants/cards.js +16 -2
- package/server/lib/assistants/cards.test.js +16 -1
- package/server/lib/assistants/contact-active-turn.http.test.js +134 -0
- package/server/lib/assistants/contact-attachments.http.test.js +436 -0
- package/server/lib/assistants/contact-attachments.js +814 -0
- package/server/lib/assistants/contact-attachments.p1.test.js +307 -0
- package/server/lib/assistants/contact-attachments.test.js +249 -0
- package/server/lib/assistants/contact-store.js +552 -19
- package/server/lib/assistants/contact-store.test.js +415 -1
- package/server/lib/assistants/contact-tools.js +114 -14
- package/server/lib/assistants/contact-tools.test.js +135 -6
- package/server/lib/assistants/contracts.js +42 -2
- package/server/lib/assistants/harness.js +4 -1
- package/server/lib/assistants/harness.test.js +124 -0
- package/server/lib/assistants/routes.js +46 -1
- package/server/lib/assistants/service.js +569 -84
- package/server/lib/assistants/service.test.js +936 -37
- package/server/lib/fs/prompt-attachment-store.js +663 -0
- package/server/lib/fs/prompt-attachment-store.test.js +263 -0
- package/server/lib/fs/routes.js +28 -72
- package/server/lib/fs/routes.test.js +15 -30
- package/server/lib/llm/catalog.js +91 -16
- package/server/lib/llm/catalog.test.js +152 -11
- package/server/lib/notifications/APNS.md +20 -4
- package/server/lib/notifications/DOCUMENTATION.md +5 -0
- package/server/lib/notifications/apns-runtime.js +136 -18
- package/server/lib/notifications/apns-runtime.test.js +128 -0
- package/server/lib/notifications/apns-titles.js +21 -1
- package/server/lib/notifications/live-activity-refresh-runtime.js +185 -0
- package/server/lib/notifications/live-activity-refresh-runtime.test.js +242 -0
- package/server/lib/notifications/runtime.js +93 -2
- package/server/lib/notifications/runtime.test.js +167 -4
- package/server/lib/opencode/core-routes.js +2 -1
- package/server/lib/opencode/settings-helpers.js +12 -0
- package/server/lib/opencode/settings-runtime.js +16 -0
- package/server/lib/opencode/settings-runtime.test.js +21 -0
- package/server/lib/scheduled-tasks/runtime.js +21 -0
- package/server/lib/scheduled-tasks/runtime.test.js +24 -0
- package/dist/assets/AssistantView-DxIghtun.js +0 -2
- package/dist/assets/DiagramView-BeUgp_Y0.js +0 -1
- package/dist/assets/FadeInOnReveal-CTlNE4Hg.js +0 -1
- package/dist/assets/MarkdownRenderer-C4MGa_kQ.js +0 -2
- package/dist/assets/MarkdownRendererImpl-DB4Q3kGa.js +0 -7
- package/dist/assets/MobileDetailNavigation-B_RwWOwv.js +0 -1
- package/dist/assets/MobileShareBridge-UiOLx0-2.js +0 -4
- package/dist/assets/MobileSurface-CNChj_dX.js +0 -2
- package/dist/assets/OnboardingScreen-DS2nugLX.js +0 -1
- package/dist/assets/SettingsView-BXfKsz0J.js +0 -25
- package/dist/assets/TerminalView-BiKCueth.js +0 -8
- package/dist/assets/ToolOutputDialog-BC2F_Fdl.js +0 -10
- package/dist/assets/desktopHosts-Dc2PS66M.js +0 -1
- package/dist/assets/desktopRelayRestore-T9y0orqa.js +0 -1
- package/dist/assets/fileQueries-BY-9OWzp.js +0 -1
- package/dist/assets/insertionBoundaries-CCQpZ5zn.js +0 -4
- package/dist/assets/main-C_PUoT3E.js +0 -169
- package/dist/assets/main-Dkg31fRY.js +0 -2
- package/dist/assets/markdown-worker-CTsrtpPh.js +0 -2
- package/dist/assets/markdownHeightCache-CYDlisxr.js +0 -3
- package/dist/assets/markdownTheme-DyJ4Gfcj.js +0 -2
- package/dist/assets/miniChat-C9QMj8T8.js +0 -2
- package/dist/assets/mobile-Z_X7CbzB.js +0 -2
- package/dist/assets/persistence-DdUac14I.js +0 -3
- package/dist/assets/platform-B4KKLnZ5.js +0 -2
- package/dist/assets/projectMeta-DRW9MQ4z.js +0 -3
- package/dist/assets/renderElectronMiniChatApp-Dn1Su1dd.js +0 -2
- package/dist/assets/renderMobileApp-BG1JJt1Q.js +0 -28
- package/dist/assets/runtimeConfig-C6matIEB.js +0 -2
- package/dist/assets/runtimeEndpointReset-kgwmUJDb.js +0 -147
- package/dist/assets/sortable-tabs-strip-C8_9kHqu.js +0 -1
- package/dist/assets/src-B-P5qIBc.css +0 -2
- package/dist/assets/terminalApi-947l7Ra3.js +0 -1
- package/dist/assets/useAssistantUIStore-me6s63b2.js +0 -1
- package/dist/assets/useDesktopWindowControlsLayout-DuZ6URPR.js +0 -1
- package/dist/assets/useFontPreferences-BCOBlaAd.js +0 -1
- package/dist/assets/useMobileNavigationStore-C8_J-qTp.js +0 -413
- package/dist/assets/useSessionAutoCleanup-FOQeu1_T.js +0 -1
- package/dist/assets/useUIStore-CuaA8-Hp.js +0 -1
- package/dist/assets/useWorkerHighlightedLines-GBqKnki1.js +0 -7
|
@@ -3,6 +3,8 @@ import os from 'node:os'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { describe, expect, it, vi } from 'vitest'
|
|
5
5
|
import {
|
|
6
|
+
CLEAR_CHAT_HISTORY_CONFIRM_BUBBLE,
|
|
7
|
+
CLEAR_CHAT_HISTORY_TOOL_NAME,
|
|
6
8
|
CREATE_ASSISTANT_TOOL_NAME,
|
|
7
9
|
MESSAGE_ASSISTANT_TOOL_NAME,
|
|
8
10
|
MISSED_FENCE_RETRY_USER_TEXT,
|
|
@@ -875,4 +877,126 @@ describe('runContactTurn', () => {
|
|
|
875
877
|
expect(result.bubbles.join('')).not.toContain('dot.png')
|
|
876
878
|
expect(result.bubbles.join('')).not.toContain('note.txt')
|
|
877
879
|
})
|
|
880
|
+
|
|
881
|
+
it('real Agent loop: clear_chat_history fence runs once, terminates, no follow-up completion', async () => {
|
|
882
|
+
const clearContactMemory = vi.fn(async () => ({ reset: true, memoryCleared: true }))
|
|
883
|
+
const resetContact = vi.fn(async () => ({ reset: true, historyCleared: true }))
|
|
884
|
+
const tools = createContactTools({ clearContactMemory, resetContact })
|
|
885
|
+
let completions = 0
|
|
886
|
+
const createChatCompletion = vi.fn(async () => {
|
|
887
|
+
completions += 1
|
|
888
|
+
if (completions > 4) {
|
|
889
|
+
throw new Error('harness safety stop: clear_chat_history must terminate without follow-up LLM')
|
|
890
|
+
}
|
|
891
|
+
// Looping model would keep emitting wipe fences; terminate must stop after one tool run.
|
|
892
|
+
return {
|
|
893
|
+
completion: {
|
|
894
|
+
choices: [{
|
|
895
|
+
message: {
|
|
896
|
+
content: '```openchamber-tool\n{"name":"clear_chat_history","arguments":{}}\n```',
|
|
897
|
+
},
|
|
898
|
+
}],
|
|
899
|
+
},
|
|
900
|
+
}
|
|
901
|
+
})
|
|
902
|
+
const result = await runContactTurn({
|
|
903
|
+
assistant: { providerID: 'p', modelID: 'm', defaultPrompt: '' },
|
|
904
|
+
history: [
|
|
905
|
+
{ role: 'user', content: 'secret before wipe' },
|
|
906
|
+
{ role: 'assistant', content: 'remembered secret' },
|
|
907
|
+
],
|
|
908
|
+
userText: '清空聊天记录',
|
|
909
|
+
tools,
|
|
910
|
+
createChatCompletion,
|
|
911
|
+
// Default real Agent — only completion is stubbed (same pattern as assign terminate).
|
|
912
|
+
})
|
|
913
|
+
expect(resetContact).toHaveBeenCalledTimes(1)
|
|
914
|
+
expect(clearContactMemory).not.toHaveBeenCalled()
|
|
915
|
+
expect(result.reset).toBe(true)
|
|
916
|
+
expect(result.historyCleared).toBe(true)
|
|
917
|
+
expect(result.bubbles).toEqual([CLEAR_CHAT_HISTORY_CONFIRM_BUBBLE])
|
|
918
|
+
expect(result.bubbles.join('')).not.toMatch(/not cleared|denied|new_conversation instead|secret/i)
|
|
919
|
+
expect(result.cards).toEqual([])
|
|
920
|
+
expect(createChatCompletion).toHaveBeenCalledTimes(1)
|
|
921
|
+
expect(completions).toBe(1)
|
|
922
|
+
})
|
|
923
|
+
|
|
924
|
+
it('real Agent loop: clear_chat_history after cross-turn confirm terminates once with confirm only', async () => {
|
|
925
|
+
const clearContactMemory = vi.fn(async () => ({ reset: true, memoryCleared: true }))
|
|
926
|
+
const resetContact = vi.fn(async () => ({ reset: true, historyCleared: true }))
|
|
927
|
+
const tools = createContactTools({ clearContactMemory, resetContact })
|
|
928
|
+
let completions = 0
|
|
929
|
+
const createChatCompletion = vi.fn(async () => {
|
|
930
|
+
completions += 1
|
|
931
|
+
if (completions > 4) {
|
|
932
|
+
throw new Error('harness safety stop: wipe must terminate once')
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
completion: {
|
|
936
|
+
choices: [{
|
|
937
|
+
message: {
|
|
938
|
+
content: '```openchamber-tool\n{"name":"clear_chat_history","arguments":{}}\n```',
|
|
939
|
+
},
|
|
940
|
+
}],
|
|
941
|
+
},
|
|
942
|
+
}
|
|
943
|
+
})
|
|
944
|
+
const result = await runContactTurn({
|
|
945
|
+
assistant: { providerID: 'p', modelID: 'm', defaultPrompt: '' },
|
|
946
|
+
history: [
|
|
947
|
+
{ role: 'user', content: '能不能帮我清一下聊天?' },
|
|
948
|
+
{ role: 'assistant', content: '是要清空聊天记录吗?' },
|
|
949
|
+
],
|
|
950
|
+
userText: '对,清空聊天记录',
|
|
951
|
+
tools,
|
|
952
|
+
createChatCompletion,
|
|
953
|
+
})
|
|
954
|
+
expect(resetContact).toHaveBeenCalledTimes(1)
|
|
955
|
+
expect(clearContactMemory).not.toHaveBeenCalled()
|
|
956
|
+
expect(result.reset).toBe(true)
|
|
957
|
+
expect(result.historyCleared).toBe(true)
|
|
958
|
+
expect(result.bubbles).toEqual([CLEAR_CHAT_HISTORY_CONFIRM_BUBBLE])
|
|
959
|
+
expect(result.bubbles.join('')).not.toMatch(/not cleared|denied|explicit wipe|new_conversation instead/i)
|
|
960
|
+
expect(createChatCompletion).toHaveBeenCalledTimes(1)
|
|
961
|
+
expect(completions).toBe(1)
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
it('real Agent loop: new_conversation terminates once and does not call resetContact', async () => {
|
|
965
|
+
const clearContactMemory = vi.fn(async () => ({ reset: true, memoryCleared: true }))
|
|
966
|
+
const resetContact = vi.fn(async () => ({ reset: true, historyCleared: true }))
|
|
967
|
+
const tools = createContactTools({ clearContactMemory, resetContact })
|
|
968
|
+
let completions = 0
|
|
969
|
+
const createChatCompletion = vi.fn(async () => {
|
|
970
|
+
completions += 1
|
|
971
|
+
if (completions > 4) {
|
|
972
|
+
throw new Error('harness safety stop: new_conversation must terminate without follow-up LLM')
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
completion: {
|
|
976
|
+
choices: [{
|
|
977
|
+
message: {
|
|
978
|
+
content: '```openchamber-tool\n{"name":"new_conversation","arguments":{}}\n```',
|
|
979
|
+
},
|
|
980
|
+
}],
|
|
981
|
+
},
|
|
982
|
+
}
|
|
983
|
+
})
|
|
984
|
+
const result = await runContactTurn({
|
|
985
|
+
assistant: { providerID: 'p', modelID: 'm', defaultPrompt: '' },
|
|
986
|
+
history: [
|
|
987
|
+
{ role: 'user', content: 'transcript stays' },
|
|
988
|
+
{ role: 'assistant', content: 'yes it does' },
|
|
989
|
+
],
|
|
990
|
+
userText: '清除记忆',
|
|
991
|
+
tools,
|
|
992
|
+
createChatCompletion,
|
|
993
|
+
})
|
|
994
|
+
expect(clearContactMemory).toHaveBeenCalledTimes(1)
|
|
995
|
+
expect(resetContact).not.toHaveBeenCalled()
|
|
996
|
+
expect(result.reset).toBe(true)
|
|
997
|
+
expect(result.historyCleared).toBe(false)
|
|
998
|
+
expect(result.bubbles).toEqual([NEW_CONVERSATION_CONFIRM_BUBBLE])
|
|
999
|
+
expect(createChatCompletion).toHaveBeenCalledTimes(1)
|
|
1000
|
+
expect(completions).toBe(1)
|
|
1001
|
+
})
|
|
878
1002
|
})
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AssistantError, createAssistantsService } from './service.js';
|
|
2
|
+
import { isSafeInlineImageMime } from './contact-attachments.js';
|
|
2
3
|
import { createChatCompletion } from '../llm/completions.js';
|
|
3
4
|
import { ensureLlmTempDirectory } from '../llm/temp-directory.js';
|
|
4
5
|
import { setAssignedSessionSettleHandler } from '../session-goal/runtime.js';
|
|
5
6
|
import { createOpencodeClient } from '@opencode-ai/sdk/v2';
|
|
6
7
|
|
|
7
|
-
const respond = (res, work, success = 200) => Promise.resolve().then(work).then((body) => res.status(success).json(body)).catch((error) => { const code = error instanceof AssistantError ? error.code : error?.code || 'internal_error'; const status = code === 'not_found' ? 404 : ['revision_conflict', 'idempotency_conflict'].includes(code) ? 409 : code === 'assistant_disabled' ? 403 : code === 'no_provider' ? 400 : code === 'upstream_error' ? 502 : 400; res.status(status).json({ ok: false, error: code, message: typeof error?.message === 'string' && error.message.trim() ? error.message : code }); });
|
|
8
|
+
const respond = (res, work, success = 200) => Promise.resolve().then(work).then((body) => res.status(success).json(body)).catch((error) => { const code = error instanceof AssistantError ? error.code : error?.code || 'internal_error'; const status = code === 'not_found' ? 404 : ['revision_conflict', 'idempotency_conflict', 'contact_generation_conflict'].includes(code) ? 409 : code === 'assistant_disabled' ? 403 : code === 'no_provider' ? 400 : code === 'upstream_error' ? 502 : 400; res.status(status).json({ ok: false, error: code, message: typeof error?.message === 'string' && error.message.trim() ? error.message : code }); });
|
|
8
9
|
const gone = (_req, res) => res.status(410).json({ ok: false, error: 'assistant_topics_retired' });
|
|
9
10
|
|
|
10
11
|
export const registerAssistantRoutes = (app, dependencies) => {
|
|
@@ -49,6 +50,50 @@ export const registerAssistantRoutes = (app, dependencies) => {
|
|
|
49
50
|
app.post('/api/openchamber/assistants/:assistantID/contact/reset', (req, res) => respond(res, () => service.resetContact(req.params.assistantID)));
|
|
50
51
|
app.post('/api/openchamber/assistants/:assistantID/contact/cards', (req, res) => respond(res, () => service.appendContactCard(req.params.assistantID, req.body), 201));
|
|
51
52
|
app.post('/api/openchamber/assistants/:assistantID/contact/dm', (req, res) => respond(res, () => service.deliverPeerMessage(req.params.assistantID, req.body), 201));
|
|
53
|
+
// Contact attachments: raw-byte PUT + opaque GET (content-addressed under dataDir).
|
|
54
|
+
app.put('/api/openchamber/assistants/:assistantID/contact/attachments/:uploadID', (req, res) => {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
req.once?.('aborted', () => controller.abort());
|
|
57
|
+
respond(res, () => service.putAssistantContactAttachment(req.params.assistantID, req.params.uploadID, {
|
|
58
|
+
stream: req,
|
|
59
|
+
headers: req.headers,
|
|
60
|
+
signal: controller.signal,
|
|
61
|
+
}), 201);
|
|
62
|
+
});
|
|
63
|
+
app.get('/api/openchamber/assistants/:assistantID/contact/attachments/:attachmentID', (req, res) => {
|
|
64
|
+
Promise.resolve()
|
|
65
|
+
.then(() => service.getAssistantContactAttachment(req.params.assistantID, req.params.attachmentID))
|
|
66
|
+
.then((file) => {
|
|
67
|
+
res.status(200);
|
|
68
|
+
res.setHeader('Content-Type', file.mime || 'application/octet-stream');
|
|
69
|
+
res.setHeader('Content-Length', String(file.size));
|
|
70
|
+
res.setHeader('ETag', file.etag || `"${file.sha256}"`);
|
|
71
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
72
|
+
// App owns cache; do not let browsers/CDNs immutable-cache attachment bodies.
|
|
73
|
+
res.setHeader('Cache-Control', 'private, no-store');
|
|
74
|
+
const safeInline = isSafeInlineImageMime(file.mime);
|
|
75
|
+
const name = file.filename ? encodeURIComponent(file.filename) : 'attachment';
|
|
76
|
+
res.setHeader(
|
|
77
|
+
'Content-Disposition',
|
|
78
|
+
`${safeInline ? 'inline' : 'attachment'}; filename*=UTF-8''${name}`,
|
|
79
|
+
);
|
|
80
|
+
res.end(file.buffer);
|
|
81
|
+
})
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
const code = error instanceof AssistantError ? error.code : error?.code || 'internal_error';
|
|
84
|
+
const status = code === 'not_found' ? 404
|
|
85
|
+
: ['revision_conflict', 'idempotency_conflict', 'contact_generation_conflict'].includes(code) ? 409
|
|
86
|
+
: code === 'assistant_disabled' ? 403
|
|
87
|
+
: code === 'PROMPT_ATTACHMENT_TOO_LARGE' ? 413
|
|
88
|
+
: code === 'upstream_error' ? 502
|
|
89
|
+
: 400;
|
|
90
|
+
res.status(status).json({
|
|
91
|
+
ok: false,
|
|
92
|
+
error: code,
|
|
93
|
+
message: typeof error?.message === 'string' && error.message.trim() ? error.message : code,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
52
97
|
app.get('/api/openchamber/assistants/:assistantID/scheduled-tasks', (req, res) => respond(res, () => service.listAssistantScheduledTasks(req.params.assistantID)));
|
|
53
98
|
// Contact send admits the user message at 202; assistant bubbles stream on SSE.
|
|
54
99
|
app.post('/api/openchamber/assistants/:assistantID/messages', (req, res) => respond(res, () => service.send(req.params.assistantID, req.body), 202)); app.post('/api/openchamber/assistants/:assistantID/share', (req, res) => respond(res, () => service.share(req.params.assistantID, req.body), 202)); app.get('/api/openchamber/assistants/share-operations/:operationID', (req, res) => respond(res, () => { const operation = service.shareOperation(req.params.operationID); if (!operation) throw new AssistantError('not_found'); return operation; }));
|