@openchambery/web 1.19.20 → 2.0.0-beta.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 +13 -11
- package/bin/cli-opencode.test.js +208 -0
- package/bin/cli.js +62 -9
- package/bin/lib/cli-startup.js +105 -2
- package/bin/lib/cli-startup.test.js +165 -0
- package/dist/assets/{AgentAvatar-NT957MuI.js → AgentAvatar-CLWWNu_Q.js} +1 -1
- package/dist/assets/AssistantView-BTCkm8yk.js +3 -0
- package/dist/assets/{DiagramView-C_JfbxNl.js → DiagramView-xuSJTnv7.js} +1 -1
- package/dist/assets/{FadeInOnReveal-CilRl4gc.js → FadeInOnReveal-Ov0D96d0.js} +1 -1
- package/dist/assets/{Icon-i5GiQRLq.js → Icon-DD-BuMfZ.js} +1 -1
- package/dist/assets/MarkdownRenderer-bc_p1H7l.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-B1iUwzOz.js → MarkdownRendererImpl-SKi4oG0c.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-BPtMj3-X.js → MarkstreamRendererImpl-CnkodGJW.js} +1 -1
- package/dist/assets/MobileDetailNavigation-Bnx59U_J.js +1 -0
- package/dist/assets/{MobileShareBridge-CKClHsWN.js → MobileShareBridge-BQlP4J-F.js} +1 -1
- package/dist/assets/MobileSurface-BXLeiZ4d.js +2 -0
- package/dist/assets/{MultiRunWindow-DiA24bnN.js → MultiRunWindow-ChZGob0v.js} +1 -1
- package/dist/assets/{OnboardingScreen-BGRLk4Wi.js → OnboardingScreen-DtQJxZG-.js} +1 -1
- package/dist/assets/{OverlayScrollbar-NuQI58XD.js → OverlayScrollbar-pMwxaCca.js} +1 -1
- package/dist/assets/SettingsView-B5I0KDTZ.js +25 -0
- package/dist/assets/{SettingsWindow-XlzRSS7E.js → SettingsWindow-DfnsFa8y.js} +1 -1
- package/dist/assets/{TerminalView-DdIaaLba.js → TerminalView-Dh9MWCpS.js} +1 -1
- package/dist/assets/{ToolOutputDialog-BbwuLdXU.js → ToolOutputDialog-BejmjrRk.js} +1 -1
- package/dist/assets/{appThemeRegistry-ChzfD7mc.js → appThemeRegistry-COGfxTFV.js} +1 -1
- package/dist/assets/{button-DtzFDvij.js → button-D-rATN7w.js} +1 -1
- package/dist/assets/{collapsible-D-nzhcyF.js → collapsible-BXRd5_Hr.js} +1 -1
- package/dist/assets/{context-menu-BzVSzHLG.js → context-menu-jGtH6fR9.js} +1 -1
- package/dist/assets/{diagram-2YWalSb5.js → diagram-DQf7Y_uR.js} +1 -1
- package/dist/assets/{dialog-DGV7DsZC.js → dialog-bfbvR4VH.js} +1 -1
- package/dist/assets/es-c52LTHHe.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-DyjmxUEN.js → fileMentionTouchSelection-BvrNCroG.js} +1 -1
- package/dist/assets/{fr-DsBZeN8Y.js → fr-PisFbUE8.js} +3 -3
- package/dist/assets/gitApi-ButwzQ0N.js +9 -0
- package/dist/assets/{input-CbB2HdLg.js → input-8r6F_Fza.js} +1 -1
- package/dist/assets/insertionBoundaries-DGs7S5zv.js +1 -0
- package/dist/assets/ja-C8Ikun3Z.js +15 -0
- package/dist/assets/ko-ClFys5o2.js +15 -0
- package/dist/assets/main-CCE_4y5F.js +2 -0
- package/dist/assets/main-Dn9rpsgl.js +169 -0
- package/dist/assets/{markdownHeightCache-C9ym9Tsb.js → markdownHeightCache-1Qo6y6IJ.js} +1 -1
- package/dist/assets/miniChat-BpjxVqgg.js +2 -0
- package/dist/assets/mobile-nMJCaNq1.js +2 -0
- package/dist/assets/{multirun-8fkF0Ji_.js → multirun-BvSG94mW.js} +1 -1
- package/dist/assets/pl-CnaKWN5F.js +15 -0
- package/dist/assets/platform-CWTIp02q.js +4 -0
- package/dist/assets/{projectMeta--y6tyjrv.js → projectMeta-DOe49dM_.js} +2 -2
- package/dist/assets/pt-BR-s8OpfRI6.js +15 -0
- package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +2 -0
- package/dist/assets/renderMobileApp-y-S8y8v_.js +28 -0
- package/dist/assets/{runtimeConfig-DYWtBrkW.js → runtimeConfig-BMvqa5we.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-BSWg99jc.js → runtimeEndpointReset-B6BxhvR0.js} +5 -5
- package/dist/assets/{sessionChangeSummary-BwupJF-Q.js → sessionChangeSummary-VVBlEXuh.js} +1 -1
- package/dist/assets/{sessionLookup-DP2X1qRR.js → sessionLookup-gh-hVRIc.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CfgEJfB0.js → sortable-tabs-strip-CudoSgV4.js} +1 -1
- package/dist/assets/{src-DZ60kHBP.css → src-BzX5UI-c.css} +1 -1
- package/dist/assets/{store-B6dImdlY.js → store-DJczmczv.js} +4 -4
- package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-DiF6JmBP.js} +1 -1
- package/dist/assets/{textarea-BnD799LW.js → textarea-3XR8sUfy.js} +1 -1
- package/dist/assets/{tooltip-Djn7MSOX.js → tooltip-CQvpK-UV.js} +1 -1
- package/dist/assets/uk-CaX0iqkB.js +15 -0
- package/dist/assets/{useAppFontEffects-DWLxpWlS.js → useAppFontEffects-Cpmpo7Wb.js} +45 -45
- package/dist/assets/{useDesktopWindowControlsLayout-BGPR_rq6.js → useDesktopWindowControlsLayout-B1GX59-L.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-CbV9_r1s.js → useEffectiveDirectory-CN6JoCFw.js} +1 -1
- package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-DcbXQOkc.js} +1 -1
- package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +414 -0
- package/dist/assets/{useSessionAutoCleanup-Dnors7hB.js → useSessionAutoCleanup-BLjymaFt.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-DQqqrOBU.js → useWorkerHighlightedLines-Bqn4AiBL.js} +1 -1
- package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
- package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
- package/dist/assets/zh-CN-20MYEjha.js +15 -0
- package/dist/assets/{zh-TW-BsOQbrwT.js → zh-TW-DiN04qTw.js} +3 -3
- package/dist/index.html +6 -6
- package/dist/mini-chat.html +6 -6
- package/dist/mobile.html +6 -6
- package/package.json +2 -2
- package/server/index.js +107 -0
- package/server/lib/assistants/DOCUMENTATION.md +18 -18
- package/server/lib/assistants/assign.js +2 -1
- package/server/lib/assistants/contact-store.js +37 -19
- package/server/lib/assistants/projection-history.test.js +207 -0
- package/server/lib/assistants/routes.js +5 -5
- package/server/lib/assistants/service.js +493 -123
- package/server/lib/assistants/service.test.js +203 -133
- package/server/lib/conversations/DOCUMENTATION.md +4 -4
- package/server/lib/conversations/service.js +86 -57
- package/server/lib/conversations/service.test.js +116 -100
- package/server/lib/event-stream/global-hub.js +2 -1
- package/server/lib/llm/DOCUMENTATION.md +71 -84
- package/server/lib/llm/catalog.js +111 -45
- package/server/lib/llm/catalog.test.js +121 -59
- package/server/lib/llm/completions.js +37 -12
- package/server/lib/llm/completions.test.js +45 -4
- package/server/lib/llm/generate.js +421 -450
- package/server/lib/llm/generate.test.js +553 -487
- package/server/lib/llm/routes.js +6 -4
- package/server/lib/message-queue/DOCUMENTATION.md +5 -5
- package/server/lib/message-queue/opencode-adapter.js +191 -104
- package/server/lib/message-queue/opencode-adapter.test.js +328 -58
- package/server/lib/message-queue/runtime.test.js +6 -6
- package/server/lib/message-queue/worker.js +7 -1
- package/server/lib/opencode/DOCUMENTATION.md +28 -5
- package/server/lib/opencode/config-entity-routes.js +84 -13
- package/server/lib/opencode/config-entity-routes.test.js +41 -10
- package/server/lib/opencode/ensure-cli.js +239 -0
- package/server/lib/opencode/ensure-cli.test.js +119 -0
- package/server/lib/opencode/env-runtime.js +193 -110
- package/server/lib/opencode/env-runtime.test.js +221 -17
- package/server/lib/opencode/feature-routes-runtime.js +37 -1
- package/server/lib/opencode/lifecycle.js +204 -70
- package/server/lib/opencode/lifecycle.test.js +376 -6
- package/server/lib/opencode/network-runtime.js +28 -16
- package/server/lib/opencode/network-runtime.test.js +68 -4
- package/server/lib/opencode/opencode2-pin.js +139 -0
- package/server/lib/opencode/opencode2-pin.test.js +87 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +203 -0
- package/server/lib/opencode/plugin-compatibility.js +15 -0
- package/server/lib/opencode/plugin-compatibility.test.js +21 -0
- package/server/lib/opencode/plugins.js +4 -3
- package/server/lib/opencode/plugins.test.js +12 -0
- package/server/lib/opencode/proxy.js +153 -16
- package/server/lib/opencode/proxy.test.js +120 -0
- package/server/lib/opencode/routes.js +25 -32
- package/server/lib/opencode/routes.test.js +51 -52
- package/server/lib/opencode/server-utils-runtime.js +2 -0
- package/server/lib/opencode/skill-routes.js +6 -7
- package/server/lib/opencode/skill-routes.test.js +36 -5
- package/server/lib/opencode/v1-migration-gate.js +157 -0
- package/server/lib/opencode/v1-migration-gate.test.js +111 -0
- package/server/lib/opencode/v2-client.js +49 -0
- package/server/lib/opencode/watcher.js +2 -1
- package/server/lib/opencode/watcher.test.js +1 -1
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +71 -39
- package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
- package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
- package/server/lib/scheduled-tasks/routes.js +27 -0
- package/server/lib/scheduled-tasks/routes.test.js +94 -0
- package/server/lib/scheduled-tasks/runtime.js +331 -308
- package/server/lib/scheduled-tasks/runtime.test.js +530 -318
- package/server/lib/session-goal/DOCUMENTATION.md +65 -14
- package/server/lib/session-goal/capability.js +57 -0
- package/server/lib/session-goal/capability.test.js +33 -0
- package/server/lib/session-goal/routes.js +231 -2
- package/server/lib/session-goal/routes.test.js +211 -0
- package/server/lib/session-goal/runtime.js +46 -4
- package/server/lib/session-goal/runtime.test.js +62 -1
- package/server/lib/session-index/sync-runtime.js +20 -17
- package/server/lib/session-index/sync-runtime.test.js +9 -8
- package/server/lib/session-metadata/DOCUMENTATION.md +41 -0
- package/server/lib/session-metadata/routes.js +94 -0
- package/server/lib/session-metadata/routes.test.js +111 -0
- package/server/lib/session-metadata/session-metadata-store.js +235 -0
- package/server/lib/session-metadata/session-metadata-store.test.js +184 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +3 -3
- package/server/lib/session-turn-pages/reconcile.service.js +1 -1
- package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
- package/server/lib/session-turn-pages/routes.js +204 -71
- package/server/lib/session-turn-pages/routes.test.js +285 -0
- package/server/lib/session-turn-pages/service.js +6 -2
- package/server/lib/session-turn-pages/service.test.js +34 -0
- package/server/lib/small-model/DOCUMENTATION.md +5 -4
- package/server/lib/small-model/catalog.js +56 -6
- package/server/lib/small-model/catalog.test.js +47 -0
- package/server/lib/small-model/index.js +3 -3
- package/server/lib/small-model/opencode-session.js +50 -206
- package/server/lib/small-model/opencode-session.test.js +58 -88
- package/server/opencode-proxy.test.js +61 -12
- package/dist/assets/AssistantView-BjvfkSRS.js +0 -3
- package/dist/assets/MarkdownRenderer-Cg82L3fN.js +0 -2
- package/dist/assets/MobileDetailNavigation-s_vvUD_F.js +0 -1
- package/dist/assets/MobileSurface-DHpXHbJp.js +0 -2
- package/dist/assets/SettingsView-l0J7pfYo.js +0 -25
- package/dist/assets/es-rqMiRP7-.js +0 -15
- package/dist/assets/gitApi-CHpibtno.js +0 -7
- package/dist/assets/insertionBoundaries-Yd1Lorjw.js +0 -1
- package/dist/assets/ja-Bgr8-dOg.js +0 -15
- package/dist/assets/ko-B_Gxh-0l.js +0 -15
- package/dist/assets/main-BoRXxmB3.js +0 -2
- package/dist/assets/main-DCDcdV5o.js +0 -169
- package/dist/assets/miniChat-12fxk5KR.js +0 -2
- package/dist/assets/mobile-CkZem9Ad.js +0 -2
- package/dist/assets/pl-DW2XJGC4.js +0 -15
- package/dist/assets/platform-0w_Q9_ji.js +0 -2
- package/dist/assets/pt-BR-CIsanf4T.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-COFc0ZDJ.js +0 -2
- package/dist/assets/renderMobileApp-BKzx7JL3.js +0 -28
- package/dist/assets/uk-BPmPEUYJ.js +0 -15
- package/dist/assets/useMobileNavigationStore-DrB_awgk.js +0 -413
- package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
- package/dist/assets/zh-CN-R8-mDcIp.js +0 -15
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { createAssistantsService } from './service.js';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const root = () => fs.mkdtempSync(path.join(os.tmpdir(), 'assistants-proj-'));
|
|
10
|
+
|
|
11
|
+
const v2Error = (result) => {
|
|
12
|
+
if (!result?.error) return result;
|
|
13
|
+
const error = new Error(result.error.code || 'upstream');
|
|
14
|
+
error.status = result.error.status ?? result.error.statusCode ?? result.status;
|
|
15
|
+
error.cause = { status: error.status };
|
|
16
|
+
error.reason = 'UnexpectedStatus';
|
|
17
|
+
throw error;
|
|
18
|
+
};
|
|
19
|
+
const createV2Client = (overrides = {}) => {
|
|
20
|
+
const create = overrides.create ?? (async () => ({ id: crypto.randomUUID() }));
|
|
21
|
+
const get = overrides.get ?? (async () => ({ id: 'present' }));
|
|
22
|
+
const prompt = overrides.prompt ?? overrides.promptAsync ?? (async () => ({ id: 'inbox_1', type: 'user' }));
|
|
23
|
+
const compact = overrides.compact ?? overrides.summarize ?? (async () => ({ id: 'inbox_compact', type: 'compaction' }));
|
|
24
|
+
const interrupt = overrides.interrupt ?? overrides.abort ?? (async () => {});
|
|
25
|
+
const rename = overrides.rename ?? (async () => {});
|
|
26
|
+
const remove = overrides.remove ?? (async () => {});
|
|
27
|
+
const list = overrides.messages ?? overrides.list ?? (async () => ({ data: [], cursor: {} }));
|
|
28
|
+
const switchAgent = overrides.switchAgent ?? (async () => {});
|
|
29
|
+
const switchModel = overrides.switchModel ?? (async () => {});
|
|
30
|
+
const putInstruction = overrides.putInstruction ?? (async () => {});
|
|
31
|
+
return {
|
|
32
|
+
session: {
|
|
33
|
+
create: async (input) => create(input),
|
|
34
|
+
get: async (input) => get(input),
|
|
35
|
+
rename,
|
|
36
|
+
remove,
|
|
37
|
+
prompt: async (input) => prompt(input),
|
|
38
|
+
compact: async (input) => compact(input),
|
|
39
|
+
interrupt: async (input) => interrupt(input),
|
|
40
|
+
switchAgent,
|
|
41
|
+
switchModel,
|
|
42
|
+
instructions: { entry: { put: putInstruction } },
|
|
43
|
+
},
|
|
44
|
+
message: {
|
|
45
|
+
list: async (input) => {
|
|
46
|
+
const result = await list({ ...input, before: input?.cursor });
|
|
47
|
+
if (result?.error) return result;
|
|
48
|
+
if (Array.isArray(result?.data) || result?.cursor || result?.response) return result;
|
|
49
|
+
if (Array.isArray(result)) return { data: result, cursor: {} };
|
|
50
|
+
return result;
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const setup = (directory = root(), client = {}, options = {}) => {
|
|
56
|
+
const { enabled = true, fetchImpl, ...serviceOptions } = options;
|
|
57
|
+
const service = createAssistantsService({
|
|
58
|
+
dbPath: path.join(directory, 'assistants.sqlite'),
|
|
59
|
+
dataDir: directory,
|
|
60
|
+
getAllowedRoots: () => [directory],
|
|
61
|
+
buildOpenCodeUrl: () => 'http://opencode.test',
|
|
62
|
+
getOpenCodeAuthHeaders: () => ({ authorization: 'Bearer test' }),
|
|
63
|
+
fetchImpl,
|
|
64
|
+
clientFactory: () => createV2Client(client),
|
|
65
|
+
...serviceOptions,
|
|
66
|
+
});
|
|
67
|
+
if (enabled) {
|
|
68
|
+
const snapshot = service.snapshot();
|
|
69
|
+
if (!snapshot.enabled) service.setEnabled({ enabled: true, expectedRevision: snapshot.revision });
|
|
70
|
+
}
|
|
71
|
+
return service;
|
|
72
|
+
};
|
|
73
|
+
const assistantInput = { name: 'A', providerID: 'p', modelID: 'm' };
|
|
74
|
+
|
|
75
|
+
const projectionEntry = (sessionID, id, created, text = id) => ({
|
|
76
|
+
info: { id, sessionID, role: 'assistant', time: { created } },
|
|
77
|
+
parts: [{ id: `prt_${id}`, sessionID, messageID: id, type: 'text', text }],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('ticket 11 assistants read from OpenCode projections', () => {
|
|
81
|
+
it('does not persist message bodies after send, share, or live events', async () => {
|
|
82
|
+
const directory = root();
|
|
83
|
+
const service = setup(directory, {
|
|
84
|
+
create: async () => ({ data: { id: 'ses_1' } }),
|
|
85
|
+
prompt: async () => ({ id: 'inbox_1', type: 'user' }),
|
|
86
|
+
});
|
|
87
|
+
const assistant = service.createAssistant(assistantInput);
|
|
88
|
+
const binding = await service.ensure(assistant.id);
|
|
89
|
+
await service.send(assistant.id, { ...binding, messageID: 'msg_send', parts: [{ type: 'text', text: 'hello' }] });
|
|
90
|
+
await service.share(assistant.id, { operationID: 'share_1', payload: { messageID: 'msg_share', parts: [{ type: 'text', text: 'shared' }] } });
|
|
91
|
+
service.processEvent({ type: 'message.updated', properties: { info: { id: 'msg_event', sessionID: binding.sessionID, role: 'assistant', time: { created: 1 } } } });
|
|
92
|
+
service.processEvent({ type: 'message.part.updated', properties: { sessionID: binding.sessionID, part: { id: 'prt_1', messageID: 'msg_event', type: 'text', text: 'live' } } });
|
|
93
|
+
const db = new (require('better-sqlite3'))(path.join(directory, 'assistants.sqlite'));
|
|
94
|
+
expect(db.prepare('SELECT COUNT(*) AS count FROM assistant_message_mirror').get().count).toBe(0);
|
|
95
|
+
expect(db.prepare('SELECT COUNT(*) AS count FROM assistant_message_part_mirror').get().count).toBe(0);
|
|
96
|
+
expect(db.prepare('SELECT COUNT(*) AS count FROM assistant_share_operation').get().count).toBe(1);
|
|
97
|
+
expect(db.prepare('SELECT session_id, message_id FROM assistant_share_operation').get()).toMatchObject({ session_id: binding.sessionID, message_id: 'msg_share' });
|
|
98
|
+
db.close();
|
|
99
|
+
service.close();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('opens history from OpenCode projections per binding and ignores leftover mirrors', async () => {
|
|
103
|
+
const directory = root();
|
|
104
|
+
let creates = 0;
|
|
105
|
+
const calls = [];
|
|
106
|
+
const service = setup(directory, {
|
|
107
|
+
create: async () => ({ data: { id: `ses_${++creates}` } }),
|
|
108
|
+
messages: async ({ sessionID, limit, order }) => {
|
|
109
|
+
calls.push({ sessionID, limit, order });
|
|
110
|
+
if (sessionID === 'ses_1') return { data: [projectionEntry('ses_1', 'msg_old', 1, 'from-projection')] };
|
|
111
|
+
return { data: [projectionEntry(sessionID, 'msg_live', 2, 'current')] };
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const assistant = service.createAssistant({ ...assistantInput, mode: 'stateless' });
|
|
115
|
+
const first = await service.ensure(assistant.id);
|
|
116
|
+
await service.createNew(assistant.id);
|
|
117
|
+
const Database = require('better-sqlite3');
|
|
118
|
+
const seed = new Database(path.join(directory, 'assistants.sqlite'));
|
|
119
|
+
seed.prepare('INSERT INTO assistant_message_mirror(assistant_id,session_id,message_id,info_json,ordinal,covered,updated_at) VALUES (?,?,?,?,?,?,?)').run(
|
|
120
|
+
assistant.id,
|
|
121
|
+
first.sessionID,
|
|
122
|
+
'msg_mirror',
|
|
123
|
+
JSON.stringify({ id: 'msg_mirror', sessionID: first.sessionID, role: 'user', time: { created: 99 }, openchamberAssistantAdmission: true }),
|
|
124
|
+
99,
|
|
125
|
+
1,
|
|
126
|
+
1,
|
|
127
|
+
);
|
|
128
|
+
seed.close();
|
|
129
|
+
const page = await service.historicalMessages(assistant.id, { limit: 10 });
|
|
130
|
+
expect(page.entries.map((entry) => entry.info.id)).toEqual(['msg_old', 'msg_live']);
|
|
131
|
+
expect(page.entries[0]?.parts[0]?.text).toBe('from-projection');
|
|
132
|
+
expect(page.entries.some((entry) => entry.info.id === 'msg_mirror')).toBe(false);
|
|
133
|
+
expect(calls.every((call) => call.order === 'desc')).toBe(true);
|
|
134
|
+
expect(page.complete).toBe(true);
|
|
135
|
+
service.close();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('keeps complete sessions when one projection page fails and does not treat total failure as empty success', async () => {
|
|
139
|
+
const directory = root();
|
|
140
|
+
let creates = 0;
|
|
141
|
+
const service = setup(directory, {
|
|
142
|
+
create: async () => ({ data: { id: `ses_${++creates}` } }),
|
|
143
|
+
messages: async ({ sessionID }) => {
|
|
144
|
+
if (sessionID === 'ses_2') return { error: { status: 503 } };
|
|
145
|
+
return { data: [projectionEntry(sessionID, `msg_${sessionID}`, 1)] };
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
const assistant = service.createAssistant({ ...assistantInput, mode: 'stateless' });
|
|
149
|
+
await service.ensure(assistant.id);
|
|
150
|
+
await service.createNew(assistant.id);
|
|
151
|
+
await service.createNew(assistant.id);
|
|
152
|
+
const page = await service.historicalMessages(assistant.id, { limit: 10 });
|
|
153
|
+
expect(page.entries.map((entry) => [entry.sessionID, entry.info.id])).toEqual([
|
|
154
|
+
['ses_1', 'msg_ses_1'],
|
|
155
|
+
['ses_3', 'msg_ses_3'],
|
|
156
|
+
]);
|
|
157
|
+
expect(page.entries.some((entry) => entry.sessionID === 'ses_2')).toBe(false);
|
|
158
|
+
|
|
159
|
+
const isolated = setup(root(), {
|
|
160
|
+
create: async () => ({ data: { id: 'ses_only' } }),
|
|
161
|
+
messages: async () => ({ error: { status: 500 } }),
|
|
162
|
+
});
|
|
163
|
+
const lonely = isolated.createAssistant(assistantInput);
|
|
164
|
+
await isolated.ensure(lonely.id);
|
|
165
|
+
await expect(isolated.historicalMessages(lonely.id, { limit: 10 })).rejects.toMatchObject({ code: 'upstream_error' });
|
|
166
|
+
isolated.close();
|
|
167
|
+
service.close();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('uses ticket 03 projection GET /session/:id/message when fetchImpl is provided', async () => {
|
|
171
|
+
const directory = root();
|
|
172
|
+
const urls = [];
|
|
173
|
+
const service = setup(directory, {
|
|
174
|
+
create: async () => ({ data: { id: 'ses_proj' } }),
|
|
175
|
+
}, {
|
|
176
|
+
fetchImpl: async (input) => {
|
|
177
|
+
const url = new URL(String(input));
|
|
178
|
+
urls.push(url);
|
|
179
|
+
return new Response(JSON.stringify({ data: [projectionEntry('ses_proj', 'msg_fetch', 1)] }), {
|
|
180
|
+
status: 200,
|
|
181
|
+
headers: { 'content-type': 'application/json' },
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
const assistant = service.createAssistant(assistantInput);
|
|
186
|
+
await service.ensure(assistant.id);
|
|
187
|
+
const page = await service.historicalMessages(assistant.id, { limit: 20 });
|
|
188
|
+
expect(page.entries.map((entry) => entry.info.id)).toEqual(['msg_fetch']);
|
|
189
|
+
expect(urls).toHaveLength(1);
|
|
190
|
+
expect(urls[0].pathname).toBe('/session/ses_proj/message');
|
|
191
|
+
expect(urls[0].searchParams.get('limit')).toBe('20');
|
|
192
|
+
expect(urls[0].searchParams.get('order')).toBe('desc');
|
|
193
|
+
expect(urls[0].searchParams.get('directory')).toEqual(expect.any(String));
|
|
194
|
+
service.close();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('exposes V2 sharing as unavailable and never reads mirrors to fake a share', async () => {
|
|
198
|
+
const service = setup();
|
|
199
|
+
expect(await service.capability()).toMatchObject({ sharingAvailable: false, archiveMetadataAvailable: false, sessionMetadataAvailable: false });
|
|
200
|
+
const source = fs.readFileSync(new URL('./service.js', import.meta.url), 'utf8');
|
|
201
|
+
expect(source).toContain('sharingAvailable: false');
|
|
202
|
+
expect(/historicalMessages[\s\S]*JOIN assistant_message_mirror/.test(source)).toBe(false);
|
|
203
|
+
expect(/sendWithConfig[\s\S]*mirrorAdmittedUserMessage\(/.test(source)).toBe(false);
|
|
204
|
+
expect(/submitClaim[\s\S]*mirrorAdmittedUserMessage\(/.test(source)).toBe(false);
|
|
205
|
+
service.close();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -3,9 +3,9 @@ import { isSafeInlineImageMime } from './contact-attachments.js';
|
|
|
3
3
|
import { createChatCompletion } from '../llm/completions.js';
|
|
4
4
|
import { ensureLlmTempDirectory } from '../llm/temp-directory.js';
|
|
5
5
|
import { setAssignedSessionSettleHandler } from '../session-goal/runtime.js';
|
|
6
|
-
import {
|
|
6
|
+
import { makeOpenCodeV2Client } from '../opencode/v2-client.js';
|
|
7
7
|
|
|
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
|
+
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' || code === 'llm_attachment_generation_unavailable' ? 502 : 400; res.status(status).json({ ok: false, error: code, message: typeof error?.message === 'string' && error.message.trim() ? error.message : code }); });
|
|
9
9
|
const gone = (_req, res) => res.status(410).json({ ok: false, error: 'assistant_topics_retired' });
|
|
10
10
|
|
|
11
11
|
export const registerAssistantRoutes = (app, dependencies) => {
|
|
@@ -13,12 +13,12 @@ export const registerAssistantRoutes = (app, dependencies) => {
|
|
|
13
13
|
...input,
|
|
14
14
|
buildOpenCodeUrl: dependencies.buildOpenCodeUrl,
|
|
15
15
|
getOpenCodeAuthHeaders: dependencies.getOpenCodeAuthHeaders,
|
|
16
|
-
clientFactory: () =>
|
|
16
|
+
clientFactory: () => makeOpenCodeV2Client({
|
|
17
17
|
baseUrl: dependencies.buildOpenCodeUrl('/', '').replace(/\/$/, ''),
|
|
18
|
-
|
|
18
|
+
authHeaders: dependencies.getOpenCodeAuthHeaders(),
|
|
19
19
|
}),
|
|
20
20
|
ensureTempDirectory: ensureLlmTempDirectory,
|
|
21
|
-
//
|
|
21
|
+
// Attachment-session generate may subscribe for session.text.delta (contact harness).
|
|
22
22
|
globalEventHub: input?.globalEventHub ?? dependencies.globalEventHub,
|
|
23
23
|
});
|
|
24
24
|
const service = createAssistantsService({
|