@openchambery/web 2.1.8-beta.2 → 2.1.8-beta.4
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-ndlnVkjO.js → AgentAvatar-qSjSZvIG.js} +1 -1
- package/dist/assets/{AssistantView-6ta2Y6q7.js → AssistantView-Dx-mzVKK.js} +3 -3
- package/dist/assets/{DiagramView-DvZU3Yxr.js → DiagramView-CDmixuZQ.js} +1 -1
- package/dist/assets/{FadeInOnReveal-CcvpP9n1.js → FadeInOnReveal-CD5d1k_u.js} +1 -1
- package/dist/assets/{Icon-2Obqh9Fi.js → Icon-QASb6r3F.js} +1 -1
- package/dist/assets/{ImageLoadingPlaceholder-D916bbhl.js → ImageLoadingPlaceholder-BuGiEI7w.js} +1 -1
- package/dist/assets/{LoadingImage-BeOr4G0q.js → LoadingImage-FRySi1BM.js} +1 -1
- package/dist/assets/{MarkdownRenderer-Cl5V8BKz.js → MarkdownRenderer-CggK1I6D.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-losB1IeW.js → MarkdownRendererImpl-Cms-VuSl.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-PHsV91V_.js → MarkstreamRendererImpl-CrC7_3Y7.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-W-DaFFg5.js → MobileDetailNavigation-CunQ1OIb.js} +1 -1
- package/dist/assets/{MobileShareBridge-Bjzb3TK6.js → MobileShareBridge-CoRbF_fc.js} +1 -1
- package/dist/assets/MobileTabPageHeader-Bj_Lu8tt.js +2 -0
- package/dist/assets/{MultiRunWindow-BbfM6ISu.js → MultiRunWindow-BgNee8Nh.js} +1 -1
- package/dist/assets/{OnboardingScreen-DEolMFq_.js → OnboardingScreen-CHa767GT.js} +1 -1
- package/dist/assets/{OverlayScrollbar-DB7fWZUw.js → OverlayScrollbar-C7Ls30Hy.js} +1 -1
- package/dist/assets/{SettingsView-Bjo-fXIC.js → SettingsView-D0Rk6ERT.js} +10 -9
- package/dist/assets/{SettingsWindow-DdN8IePL.js → SettingsWindow-8i92GNln.js} +1 -1
- package/dist/assets/{TerminalView-BUHkhkkZ.js → TerminalView-mLwQ0UU9.js} +1 -1
- package/dist/assets/{ToolOutputDialog-DGY_JzQg.js → ToolOutputDialog-DH6ASkUk.js} +1 -1
- package/dist/assets/{appThemeRegistry-DiuhF7sJ.js → appThemeRegistry-CHeMJdPY.js} +1 -1
- package/dist/assets/{button-Bdq9SUvZ.js → button-DOrwfkre.js} +1 -1
- package/dist/assets/{collapsible-C7PP5l16.js → collapsible-BJwPZMpo.js} +1 -1
- package/dist/assets/{diagram-BBlwWp3m.js → diagram-BFevUcKu.js} +1 -1
- package/dist/assets/{dialog-DHmUjm6M.js → dialog-FSXwOR2i.js} +1 -1
- package/dist/assets/{dropdown-menu.styles-XSr3iA4S.js → dropdown-menu.styles-CfTQwJHe.js} +1 -1
- package/dist/assets/{es-BxLn_G_Q.js → es-VbQQdCdi.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-BHA_5cBo.js → fileMentionTouchSelection-CSelAE3Z.js} +1 -1
- package/dist/assets/{fr-LROiZho7.js → fr-BotRbS9t.js} +1 -1
- package/dist/assets/{gitApi-BdyssA-O.js → gitApi-B123oHQk.js} +1 -1
- package/dist/assets/{input-De00oOal.js → input-Ctgb06ot.js} +1 -1
- package/dist/assets/{insertionBoundaries-DIohQnVE.js → insertionBoundaries-cfka-ff_.js} +1 -1
- package/dist/assets/{ja-DiErdLtA.js → ja-l_gq6Coh.js} +1 -1
- package/dist/assets/{ko-srYeEs4u.js → ko-yhU5rfe5.js} +1 -1
- package/dist/assets/{main-BETi-fsf.js → main-DJhLxkOQ.js} +10 -10
- package/dist/assets/{main-DUrFgqZL.js → main-IO37JIWf.js} +2 -2
- package/dist/assets/{markdownHeightCache-D44E3PrO.js → markdownHeightCache-B6drDnVO.js} +1 -1
- package/dist/assets/{mcpQueries-qCvadKyt.js → mcpQueries-Digedyij.js} +1 -1
- package/dist/assets/miniChat-mE8pRELI.js +2 -0
- package/dist/assets/mobile-Cia2889g.js +2 -0
- package/dist/assets/{multirun-C2aiem4S.js → multirun-C1I9Attc.js} +1 -1
- package/dist/assets/{pl-CzKfWMm9.js → pl-BkynZgOJ.js} +1 -1
- package/dist/assets/{projectMeta-DV6HExaM.js → projectMeta-C2WeOasQ.js} +1 -1
- package/dist/assets/{pt-BR-CUmrnVae.js → pt-BR-Dw_Dokez.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-FpoAJUKI.js +2 -0
- package/dist/assets/{renderMobileApp-Ds-6h0OM.js → renderMobileApp-DrT0ZwHw.js} +6 -6
- package/dist/assets/{runtimeConfig-CBy91BoB.js → runtimeConfig-yW48patn.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-C7wwKdBg.js → runtimeEndpointReset-CMmN7tH1.js} +5 -5
- package/dist/assets/{scheduledTaskNavigation-D7yhTPgz.js → scheduledTaskNavigation-DEMPeoot.js} +1 -1
- package/dist/assets/sessionChangeSummary-D1YrkHbB.js +1 -0
- package/dist/assets/{sessionLookup-D0uMihAO.js → sessionLookup-DrKLmlMv.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-aIcw2DJh.js → sortable-tabs-strip-BVnwfMAa.js} +1 -1
- package/dist/assets/speechSettingsQueries-BNPsqHp5.js +4 -0
- package/dist/assets/{store-BYLl4QL2.js → store-COtDAfPx.js} +2 -2
- package/dist/assets/{textarea-DNadM0DL.js → textarea-DJ52TDJj.js} +1 -1
- package/dist/assets/{tooltip-nHGca5md.js → tooltip-JgW0lJaV.js} +1 -1
- package/dist/assets/{uk-DGZ3Hl33.js → uk-DSl09YqU.js} +1 -1
- package/dist/assets/{useAppFontEffects-ByC3DE9D.js → useAppFontEffects-BUJapti0.js} +51 -53
- package/dist/assets/{useDesktopWindowControlsLayout-CbrmthFZ.js → useDesktopWindowControlsLayout-Dlh9xNoE.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-BEpQGzay.js → useEffectiveDirectory-2dkaAaxr.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-Cm0CuwA9.js → useMobileNavigationStore-BRQBoMI1.js} +6 -6
- package/dist/assets/{useWorkerHighlightedLines-D5mEoRi2.js → useWorkerHighlightedLines-BRAfFOVn.js} +1 -1
- package/dist/assets/vendor-codemirror-autocomplete-7c2L_4dM.js +12 -0
- package/dist/assets/vendor-codemirror-commands-B8KrBQ9B.js +1 -0
- package/dist/assets/vendor-codemirror-lang-angular-DXOgWEVj.js +23 -0
- package/dist/assets/{vendor-codemirror-lang-cpp-C3UKgJaQ.js → vendor-codemirror-lang-cpp-Dh8C3xqP.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-go-Cv5NYdGm.js → vendor-codemirror-lang-go-BXAJ1_dh.js} +5 -5
- package/dist/assets/{vendor-codemirror-lang-java-D6hBTIgD.js → vendor-codemirror-lang-java-DAyCHRhk.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-jinja-ehGXhAov.js → vendor-codemirror-lang-jinja-0TICrMfO.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-json-tHviF6YN.js → vendor-codemirror-lang-json-mLulNbwB.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-less-B-yDfSDo.js → vendor-codemirror-lang-less-CNvptX3P.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-liquid-BdoLH4KN.js → vendor-codemirror-lang-liquid-8g5seytq.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-markdown-CV1IO6Qs.js +6 -0
- package/dist/assets/{vendor-codemirror-lang-php-Ch8GW4Xz.js → vendor-codemirror-lang-php-xP7rf28_.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-python-YM2WVLHF.js +9 -0
- package/dist/assets/{vendor-codemirror-lang-rust-BEUlElU6.js → vendor-codemirror-lang-rust-CtafR0gt.js} +1 -1
- package/dist/assets/vendor-codemirror-lang-sass-B4iav_Ps.js +1 -0
- package/dist/assets/{vendor-codemirror-lang-sql-BnZql454.js → vendor-codemirror-lang-sql-C6q09UUZ.js} +2 -2
- package/dist/assets/{vendor-codemirror-lang-vue-BCwQBvLR.js → vendor-codemirror-lang-vue-DIB78UsR.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-wast-mrtA2eML.js → vendor-codemirror-lang-wast-C_JVdOCF.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-xml-ClAh-G2D.js → vendor-codemirror-lang-xml-DIuzY_d4.js} +1 -1
- package/dist/assets/{vendor-codemirror-lang-yaml-CjL8XEf2.js → vendor-codemirror-lang-yaml-BO0hIfQM.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-DGDYune4.js → vendor-codemirror-language-data-Ctd4WxgW.js} +2 -2
- package/dist/assets/vendor-codemirror-search-ZqtrPLjU.js +2 -0
- package/dist/assets/{vendor-replit-codemirror-vim-CEibx3EP.js → vendor-replit-codemirror-vim-DDj0dSVY.js} +6 -6
- package/dist/assets/{zh-CN-UwxgbjmI.js → zh-CN-NtO1bA00.js} +1 -1
- package/dist/assets/{zh-TW-Bc9XoqY8.js → zh-TW-CF0z_L0k.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/mini-chat.html +2 -2
- package/dist/mobile.html +2 -2
- package/package.json +1 -1
- package/server/lib/config-sync/DOCUMENTATION.md +10 -5
- package/server/lib/config-sync/engine.js +3 -2
- package/server/lib/config-sync/magpie-auth.js +111 -0
- package/server/lib/config-sync/magpie-auth.test.js +79 -0
- package/server/lib/config-sync/magpie.js +81 -9
- package/server/lib/config-sync/magpie.test.js +120 -5
- package/server/lib/config-sync/receiver.js +3 -1
- package/server/lib/config-sync/routes.test.js +3 -2
- package/server/lib/opencode/DOCUMENTATION.md +1 -0
- package/server/lib/opencode/lifecycle.js +4 -0
- package/server/lib/opencode/lifecycle.test.js +7 -2
- package/dist/assets/MobileTabPageHeader-qbGYMq9u.js +0 -2
- package/dist/assets/miniChat-jC1_fkBZ.js +0 -2
- package/dist/assets/mobile-D-5-hKu6.js +0 -2
- package/dist/assets/renderElectronMiniChatApp-CI-5mVAG.js +0 -2
- package/dist/assets/sessionChangeSummary-BSFJiKHQ.js +0 -2
- package/dist/assets/speechSettingsQueries-Bd3icbFp.js +0 -4
- package/dist/assets/vendor-codemirror-autocomplete-DHomsvW7.js +0 -12
- package/dist/assets/vendor-codemirror-commands-C9DCaJ0C.js +0 -1
- package/dist/assets/vendor-codemirror-lang-angular-BDXnzWgq.js +0 -23
- package/dist/assets/vendor-codemirror-lang-markdown-Bt-vLz9k.js +0 -6
- package/dist/assets/vendor-codemirror-lang-python-B4naLXMo.js +0 -9
- package/dist/assets/vendor-codemirror-lang-sass-ibtp2iUB.js +0 -1
- package/dist/assets/vendor-codemirror-search-D2SOgzph.js +0 -2
|
@@ -23,13 +23,13 @@ const write = async (file, content = '{}') => {
|
|
|
23
23
|
afterEach(async () => { await Promise.all(dirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); });
|
|
24
24
|
|
|
25
25
|
describe('Magpie discovery and native transfer', () => {
|
|
26
|
-
it('
|
|
26
|
+
it('hides config-only sources until Magpie is installed, without installing during preview', async () => {
|
|
27
27
|
const { home, options } = await machine();
|
|
28
28
|
const fetchImpl = vi.fn();
|
|
29
29
|
const sync = createMagpieSync({ ...options, fetchImpl });
|
|
30
30
|
expect(await sync.inventory()).toEqual({ available: false, installed: false });
|
|
31
31
|
await write(path.join(home, '.config', 'magpie', 'providers.json'));
|
|
32
|
-
expect(await sync.inventory()).toEqual({ available:
|
|
32
|
+
expect(await sync.inventory()).toEqual({ available: false, installed: false });
|
|
33
33
|
expect(fetchImpl).not.toHaveBeenCalled();
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -55,6 +55,7 @@ describe('Magpie discovery and native transfer', () => {
|
|
|
55
55
|
await write(path.join(source.home, '.config', 'magpie', 'providers.json'));
|
|
56
56
|
const runSource = vi.fn(async (_binary, args, ctx) => {
|
|
57
57
|
if (args[0] === '--version') return 'magpie 0.1.0';
|
|
58
|
+
if (args[0] === 'accounts') return '[]';
|
|
58
59
|
expect(args[0]).toBe('backup');
|
|
59
60
|
expect(ctx.input).toMatch(/^[a-f0-9]{64}\n$/);
|
|
60
61
|
expect(args).not.toContain(ctx.input.trim());
|
|
@@ -70,14 +71,17 @@ describe('Magpie discovery and native transfer', () => {
|
|
|
70
71
|
} } }) : new Response(bytes));
|
|
71
72
|
const runTarget = vi.fn(async (_binary, args, ctx) => {
|
|
72
73
|
if (args[0] === '--version') return 'magpie 0.1.0';
|
|
74
|
+
if (args[0] === 'accounts') return '[]';
|
|
73
75
|
expect(args[0]).toBe('restore');
|
|
74
76
|
expect(ctx.input.trim()).toBe(JSON.parse(payload).passphrase);
|
|
75
77
|
expect(JSON.parse(await fs.readFile(args[1], 'utf8')).format).toBe('magpie-backup');
|
|
78
|
+
await write(path.join(target.home, '.config', 'magpie', 'providers.json'), '{"providers":[]}');
|
|
76
79
|
return 'restored';
|
|
77
80
|
});
|
|
78
81
|
const destination = createMagpieSync({ ...target.options, fetchImpl, run: runTarget });
|
|
79
82
|
await expect(destination.import(payload)).resolves.toEqual({ ok: true });
|
|
80
83
|
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
|
84
|
+
expect(await fs.realpath(path.join(target.home, '.local/bin/magpie'))).toBe(await fs.realpath(path.join(target.home, '.config/openchamber/tools/magpie/magpie')));
|
|
81
85
|
await expect(destination.import(payload)).resolves.toEqual({ ok: true });
|
|
82
86
|
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
|
83
87
|
for (const item of [source, target]) {
|
|
@@ -85,6 +89,98 @@ describe('Magpie discovery and native transfer', () => {
|
|
|
85
89
|
}
|
|
86
90
|
});
|
|
87
91
|
|
|
92
|
+
it('repairs a previously managed installation without downloading or overwriting a user command', async () => {
|
|
93
|
+
const { home, options } = await machine();
|
|
94
|
+
const managed = path.join(home, '.config/openchamber/tools/magpie/magpie');
|
|
95
|
+
const entry = path.join(home, '.local/bin/magpie');
|
|
96
|
+
const fetchImpl = vi.fn();
|
|
97
|
+
const run = vi.fn(async () => 'magpie 0.1.1319');
|
|
98
|
+
const sync = createMagpieSync({ ...options, fetchImpl, run });
|
|
99
|
+
await sync.repairInstallation();
|
|
100
|
+
expect(run).not.toHaveBeenCalled();
|
|
101
|
+
await write(managed, 'fixture');
|
|
102
|
+
await sync.repairInstallation();
|
|
103
|
+
await sync.repairInstallation();
|
|
104
|
+
expect(await fs.realpath(entry)).toBe(await fs.realpath(managed));
|
|
105
|
+
await fs.unlink(entry);
|
|
106
|
+
await write(entry, 'user installation');
|
|
107
|
+
await sync.repairInstallation();
|
|
108
|
+
expect(await fs.readFile(entry, 'utf8')).toBe('user installation');
|
|
109
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('round-trips plugin accounts and Codex through v2 and verifies the final account inventory', async () => {
|
|
113
|
+
const source = await machine();
|
|
114
|
+
const target = await machine();
|
|
115
|
+
for (const item of [source, target]) await write(path.join(item.home, '.local/bin/magpie'), 'fixture');
|
|
116
|
+
const sourceDir = path.join(source.home, '.config/magpie');
|
|
117
|
+
const targetDir = path.join(target.home, '.config/magpie');
|
|
118
|
+
await write(path.join(sourceDir, 'providers.json'), '{"providers":[{"id":"astra"},{"id":"codex"}]}');
|
|
119
|
+
await write(path.join(sourceDir, 'plugin-auth.json'), JSON.stringify({ 'astra#one': { type: 'api', key: 'fixture-key' } }));
|
|
120
|
+
await write(path.join(sourceDir, 'logins.json'), JSON.stringify([
|
|
121
|
+
{ agent: 'codex', user: 'fixture@example.invalid', auth: { tokens: { access_token: 'fixture-token' } } },
|
|
122
|
+
]));
|
|
123
|
+
const payload = await createMagpieSync({ ...source.options, run: async (_binary, args) => {
|
|
124
|
+
if (args[0] === '--version') return 'magpie 0.1.1319';
|
|
125
|
+
if (args[0] === 'accounts') return '[{"agent":"astra"},{"agent":"codex","user":"fixture@example.invalid"}]';
|
|
126
|
+
await write(args[1], '{"format":"magpie-backup"}');
|
|
127
|
+
return '';
|
|
128
|
+
} }).download();
|
|
129
|
+
expect(JSON.parse(payload).format).toBe('magpie-sync-v2');
|
|
130
|
+
await write(path.join(targetDir, 'plugin-auth.json'), '{"destination-only":{"type":"api","key":"keep"}}');
|
|
131
|
+
let recognized = true;
|
|
132
|
+
const run = vi.fn(async (_binary, args) => {
|
|
133
|
+
if (args[0] === '--version') return 'magpie 0.1.1319';
|
|
134
|
+
if (args[0] === 'restore') {
|
|
135
|
+
await write(path.join(targetDir, 'providers.json'), await fs.readFile(path.join(sourceDir, 'providers.json')));
|
|
136
|
+
return 'not signed in on this computer';
|
|
137
|
+
}
|
|
138
|
+
if (args[1] === 'import') {
|
|
139
|
+
expect(args).not.toContain('fixture-token');
|
|
140
|
+
expect(JSON.parse(await fs.readFile(args[3], 'utf8')).tokens.access_token).toBe('fixture-token');
|
|
141
|
+
expect((await fs.stat(args[3])).mode & 0o777).toBe(0o600);
|
|
142
|
+
return '';
|
|
143
|
+
}
|
|
144
|
+
return JSON.stringify(recognized ? [{ agent: 'astra' }, { agent: 'codex', user: 'fixture@example.invalid' }] : []);
|
|
145
|
+
});
|
|
146
|
+
const sync = createMagpieSync({ ...target.options, run });
|
|
147
|
+
await expect(sync.import(payload)).resolves.toEqual({ ok: true });
|
|
148
|
+
await expect(sync.import(payload)).resolves.toEqual({ ok: true });
|
|
149
|
+
const stored = JSON.parse(await fs.readFile(path.join(targetDir, 'plugin-auth.json'), 'utf8'));
|
|
150
|
+
expect(Object.keys(stored)).toEqual(['destination-only', 'astra#one']);
|
|
151
|
+
recognized = false;
|
|
152
|
+
await expect(sync.import(payload)).rejects.toThrow('authorization sync or verification failed');
|
|
153
|
+
expect((await fs.readdir(target.home)).filter((name) => name.startsWith('openchamber-magpie-'))).toEqual([]);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('rejects malformed v2 authorization before installing or restoring', async () => {
|
|
157
|
+
const { options } = await machine();
|
|
158
|
+
const run = vi.fn();
|
|
159
|
+
const fetchImpl = vi.fn();
|
|
160
|
+
const payload = Buffer.from(JSON.stringify({ format: 'magpie-sync-v2', passphrase: 'a'.repeat(64),
|
|
161
|
+
backup: Buffer.from('{"format":"magpie-backup"}').toString('base64'),
|
|
162
|
+
authorization: { plugins: {}, logins: [{ agent: 'codex', home: '/source/path', auth: {} }], incomplete: false } }));
|
|
163
|
+
await expect(createMagpieSync({ ...options, run, fetchImpl }).import(payload)).rejects.toThrow('Invalid Magpie sync payload');
|
|
164
|
+
expect(run).not.toHaveBeenCalled();
|
|
165
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('retries agent settings after authorizations instead of keeping a pre-login skip warning', async () => {
|
|
169
|
+
const { home, options } = await machine();
|
|
170
|
+
await write(path.join(home, '.local/bin/magpie'), 'fixture');
|
|
171
|
+
let restores = 0;
|
|
172
|
+
const run = async (_binary, args) => {
|
|
173
|
+
if (args[0] === '--version') return 'magpie 0.1.1319';
|
|
174
|
+
if (args[0] === 'accounts') return '[]';
|
|
175
|
+
return ++restores === 1 ? 'Left as they are' : 'restored';
|
|
176
|
+
};
|
|
177
|
+
const payload = Buffer.from(JSON.stringify({ format: 'magpie-sync-v2', passphrase: 'a'.repeat(64),
|
|
178
|
+
backup: Buffer.from('{"format":"magpie-backup"}').toString('base64'),
|
|
179
|
+
authorization: { plugins: {}, logins: [], incomplete: false } }));
|
|
180
|
+
await expect(createMagpieSync({ ...options, run }).import(payload)).resolves.toEqual({ ok: true });
|
|
181
|
+
expect(restores).toBe(2);
|
|
182
|
+
});
|
|
183
|
+
|
|
88
184
|
it('does not install on malformed payload and refuses partial restore without leaking CLI output', async () => {
|
|
89
185
|
const { home, options } = await machine();
|
|
90
186
|
const fetchImpl = vi.fn();
|
|
@@ -100,6 +196,18 @@ describe('Magpie discovery and native transfer', () => {
|
|
|
100
196
|
expect((await fs.readdir(home)).filter((name) => name.startsWith('openchamber-magpie-'))).toEqual([]);
|
|
101
197
|
});
|
|
102
198
|
|
|
199
|
+
it('reports excluded sign-ins as a warning after a successful native restore', async () => {
|
|
200
|
+
const { home, options } = await machine();
|
|
201
|
+
await write(path.join(home, '.local', 'bin', 'magpie'), 'fixture-cli');
|
|
202
|
+
const run = async (_binary, args) => args[0] === '--version' ? 'magpie 0.1.1319'
|
|
203
|
+
: "restored providers: 0 added, 8 replaced\nThe backup's settings use these, not signed in on this computer (a backup carries settings, not sign-ins; to use one here: magpie accounts add <id>): private-fixture-account\n";
|
|
204
|
+
const payload = Buffer.from(JSON.stringify({ format: 'magpie-sync-v1', passphrase: 'a'.repeat(64),
|
|
205
|
+
backup: Buffer.from('{"format":"magpie-backup"}').toString('base64') }));
|
|
206
|
+
await expect(createMagpieSync({ ...options, run }).import(payload)).resolves.toEqual({
|
|
207
|
+
ok: true, magpieWarnings: ['sign_in_required'],
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
103
211
|
it('requires current source discovery and supports a Magpie-only plan', async () => {
|
|
104
212
|
const { home } = await machine();
|
|
105
213
|
const selections = { fileGroups: [], singleFiles: [], directories: [], agentsRoot: false, providerCredentials: false, magpie: true };
|
|
@@ -107,9 +215,14 @@ describe('Magpie discovery and native transfer', () => {
|
|
|
107
215
|
const plan = planOpenCodeConfigSync(home, { selections, magpie: { available: true, installed: true } });
|
|
108
216
|
expect(plan.magpie).toEqual({ bytes: 0 });
|
|
109
217
|
const received = [];
|
|
110
|
-
const executor = { prepare: vi.fn(), putTar: async (value) => received.push(value), finalize: vi.fn() };
|
|
111
|
-
await applyConfigSyncPlan({ plan, executor, syncRunId: 'test', sourceHomedir: home, collectMagpie: async () => Buffer.from('fixture') });
|
|
218
|
+
const executor = { prepare: vi.fn(), putTar: async (value) => received.push(value), finalize: vi.fn(async () => ({ magpieWarnings: ['sign_in_required'] })) };
|
|
219
|
+
const result = await applyConfigSyncPlan({ plan, executor, syncRunId: 'test', sourceHomedir: home, collectMagpie: async () => Buffer.from('fixture') });
|
|
220
|
+
expect(result.magpieWarnings).toEqual(['sign_in_required']);
|
|
112
221
|
expect(received).toEqual([{ kind: 'magpie', payload: Buffer.from('fixture') }]);
|
|
222
|
+
received.length = 0;
|
|
223
|
+
await applyConfigSyncPlan({ plan: { ...plan, files: [{ path: 'opencode.json' }] }, executor, syncRunId: 'ordered', sourceHomedir: home,
|
|
224
|
+
collectTar: async () => Buffer.from('config'), collectMagpie: async () => Buffer.from('fixture') });
|
|
225
|
+
expect(received.map((entry) => entry.kind)).toEqual(['magpie', 'config']);
|
|
113
226
|
const pull = planOpenCodeConfigSyncFromInventory({ files: [], directories: [], magpie: { available: true } }, { selections });
|
|
114
227
|
expect(pull.magpieAvailable).toBe(true);
|
|
115
228
|
expect(pull.magpie).toEqual({ bytes: 0 });
|
|
@@ -141,9 +254,11 @@ describe('Magpie discovery and native transfer', () => {
|
|
|
141
254
|
}
|
|
142
255
|
await write(path.join(source.home, '.config', 'magpie', 'providers.json'), JSON.stringify({ providers: [
|
|
143
256
|
{ id: 'sync-fixture', name: 'Sync fixture', chat: 'https://provider.invalid/v1', key: 'fixture-not-a-real-key' },
|
|
257
|
+
{ id: 'fixture-subscription' },
|
|
144
258
|
] }));
|
|
145
259
|
const payload = await createMagpieSync(source.options).download();
|
|
146
|
-
await createMagpieSync(target.options).import(payload);
|
|
260
|
+
const receipt = await createMagpieSync(target.options).import(payload);
|
|
261
|
+
expect(receipt).toEqual({ ok: true, magpieWarnings: ['sign_in_required'] });
|
|
147
262
|
const restored = JSON.parse(await fs.readFile(path.join(target.home, '.config', 'magpie', 'providers.json'), 'utf8'));
|
|
148
263
|
expect(restored.providers).toContainEqual(expect.objectContaining({ id: 'sync-fixture', key: 'fixture-not-a-real-key' }));
|
|
149
264
|
}, 60_000);
|
|
@@ -195,7 +195,8 @@ export const createConfigSyncReceiver = (deps = {}) => {
|
|
|
195
195
|
await credentials.import(buffer);
|
|
196
196
|
} else if (kind === 'magpie') {
|
|
197
197
|
if (!flight.plan?.magpie) throw new Error('Magpie was not selected');
|
|
198
|
-
await magpie.import(buffer);
|
|
198
|
+
const result = await magpie.import(buffer);
|
|
199
|
+
flight.magpieWarnings = result.magpieWarnings;
|
|
199
200
|
} else {
|
|
200
201
|
const error = new Error(`Unsupported put kind: ${String(kind)}`);
|
|
201
202
|
error.code = 'sync_invalid_kind';
|
|
@@ -248,6 +249,7 @@ export const createConfigSyncReceiver = (deps = {}) => {
|
|
|
248
249
|
syncRunId: flight.syncRunId,
|
|
249
250
|
kinds: [...flight.kinds],
|
|
250
251
|
endedAt: new Date().toISOString(),
|
|
252
|
+
...(flight.magpieWarnings?.length ? { magpieWarnings: flight.magpieWarnings } : {}),
|
|
251
253
|
};
|
|
252
254
|
clearInflight(syncRunId);
|
|
253
255
|
return receipt;
|
|
@@ -52,7 +52,7 @@ describe('config-sync HTTP routes', () => {
|
|
|
52
52
|
const { app } = await createApp(undefined, {
|
|
53
53
|
inventory: async () => ({ available: true, installed: false }),
|
|
54
54
|
download: async () => payload,
|
|
55
|
-
import: async (buffer) => { received.push(buffer); return { ok: true }; },
|
|
55
|
+
import: async (buffer) => { received.push(buffer); return { ok: true, magpieWarnings: ['sign_in_required'] }; },
|
|
56
56
|
});
|
|
57
57
|
const probe = await request(app).post('/api/openchamber/config-sync/probe').send({ plan: {} });
|
|
58
58
|
expect(probe.body.inventory.magpie).toEqual({ available: true, installed: false });
|
|
@@ -67,7 +67,8 @@ describe('config-sync HTTP routes', () => {
|
|
|
67
67
|
await request(app).post('/api/openchamber/config-sync/finalize').send({ syncRunId: 'magpie-run' }).expect(500);
|
|
68
68
|
await request(app).put('/api/openchamber/config-sync/put/magpie?syncRunId=magpie-run')
|
|
69
69
|
.set('Content-Type', 'application/octet-stream').send(payload).expect(200);
|
|
70
|
-
await request(app).post('/api/openchamber/config-sync/finalize').send({ syncRunId: 'magpie-run' }).expect(200);
|
|
70
|
+
const receipt = await request(app).post('/api/openchamber/config-sync/finalize').send({ syncRunId: 'magpie-run' }).expect(200);
|
|
71
|
+
expect(receipt.body.magpieWarnings).toEqual(['sign_in_required']);
|
|
71
72
|
expect(received).toEqual([payload, payload]);
|
|
72
73
|
});
|
|
73
74
|
|
|
@@ -27,6 +27,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
27
27
|
- `packages/web/server/lib/opencode/cli-entry-runtime.js`: CLI entrypoint runtime that detects direct execution, forces `OPENCHAMBER_RUNTIME=web` unless already `ssh-remote` (so `dev:server` cannot inherit a leftover desktop env and host a relay, while SSH-managed remotes may), parses CLI options, and starts server bootstrap.
|
|
28
28
|
- `packages/web/server/lib/opencode/routes.js`: OpenCode/provider settings and auth-related route registration.
|
|
29
29
|
- `packages/web/server/lib/opencode/lifecycle.js`: OpenCode process lifecycle runtime (startup, restart, readiness, health monitoring). Managed child env inherits the user shell environment unchanged; experimental flags such as `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` follow the user's own environment and are never injected by OpenChamber.
|
|
30
|
+
- Before shared-service/private startup, config-sync's Magpie adapter repairs the discoverable command entry for an existing OpenChamber-managed Magpie CLI. No download occurs at startup. Errors remain explicit rather than starting OpenCode with an undiscoverable managed dependency. External services are not modified.
|
|
30
31
|
- `packages/web/server/lib/opencode/v1-migration-gate.js`: pure V1→v2 migration admission gate (`GET /api/experimental/migration/v1` → whether transcript may be fetched).
|
|
31
32
|
- `packages/web/server/lib/opencode/managed-capabilities-runtime.js`: managed-child scheduled-task resources, config injection, rotating bridge identity, and bridge authorization.
|
|
32
33
|
- `packages/web/server/lib/opencode/env-runtime.js`: OpenCode CLI/binary resolution and shell environment runtime.
|
|
@@ -6,6 +6,7 @@ import { readOpenCode2BinaryVersion } from './ensure-cli.js';
|
|
|
6
6
|
import { createRevokedRuntimeContract, evaluateRuntimeContract } from './runtime-contract.js';
|
|
7
7
|
import { OPENCODE_V1_MIGRATION_PATH, fetchV1MigrationGate } from './v1-migration-gate.js';
|
|
8
8
|
import { isProcessAlive } from './shared-service.js';
|
|
9
|
+
import { createMagpieSync } from '../config-sync/magpie.js';
|
|
9
10
|
|
|
10
11
|
const parsePositiveInt = (value, fallback) => {
|
|
11
12
|
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
@@ -71,6 +72,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
71
72
|
buildAugmentedPath,
|
|
72
73
|
buildManagedOpenCodePath,
|
|
73
74
|
getManagedOpenCodeShellEnvSnapshot,
|
|
75
|
+
prepareMagpieInstallation = () => createMagpieSync().repairInstallation(),
|
|
74
76
|
managedCapabilitiesRuntime = null,
|
|
75
77
|
getActiveSessionCount = () => 0,
|
|
76
78
|
// Official background service (shared-service.js). null keeps the private
|
|
@@ -691,6 +693,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
691
693
|
* or the registration is missing/malformed; the caller decides rollback.
|
|
692
694
|
*/
|
|
693
695
|
const connectSharedOpenCodeService = async ({ binaryPath } = {}) => {
|
|
696
|
+
await prepareMagpieInstallation();
|
|
694
697
|
if (!binaryPath) {
|
|
695
698
|
await applyOpencodeBinaryFromSettings({ strict: true });
|
|
696
699
|
if (typeof ensurePinnedOpenCode2CliEnv === 'function') {
|
|
@@ -793,6 +796,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
793
796
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
794
797
|
|
|
795
798
|
const startOpenCodeOnce = async () => {
|
|
799
|
+
await prepareMagpieInstallation();
|
|
796
800
|
// New managed instance — drop any prior permit before spawn/probes.
|
|
797
801
|
const generationAtStart = revokeRuntimeExecutionPermit('managed-start');
|
|
798
802
|
const requestedPort = env.ENV_CONFIGURED_OPENCODE_PORT;
|
|
@@ -101,6 +101,7 @@ const createRuntime = (overrides = {}, stateRef = null) => {
|
|
|
101
101
|
normalizeApiPrefix: vi.fn(() => ''),
|
|
102
102
|
applyOpencodeBinaryFromSettings: vi.fn(async () => null),
|
|
103
103
|
ensureOpencodeCliEnv: vi.fn(),
|
|
104
|
+
prepareMagpieInstallation: vi.fn(async () => {}),
|
|
104
105
|
ensureLocalOpenCodeServerPassword: vi.fn(async () => 'password'),
|
|
105
106
|
resolveManagedOpenCodeLaunchSpec: vi.fn((binary) => ({ binary, args: [], wrapperType: null })),
|
|
106
107
|
setOpenCodePort: vi.fn((port) => {
|
|
@@ -195,14 +196,16 @@ describe('OpenCode lifecycle', () => {
|
|
|
195
196
|
it('launches managed OpenCode with the managed PATH', async () => {
|
|
196
197
|
delete process.env.OPENCODE_BINARY;
|
|
197
198
|
const child = createMockChild();
|
|
199
|
+
const prepareMagpieInstallation = vi.fn(async () => {});
|
|
198
200
|
spawnMock.mockImplementationOnce(() => {
|
|
201
|
+
expect(prepareMagpieInstallation).toHaveBeenCalledTimes(1);
|
|
199
202
|
queueMicrotask(() => {
|
|
200
203
|
child.stdout.emit('data', 'opencode server listening on http://127.0.0.1:45678\n');
|
|
201
204
|
});
|
|
202
205
|
return child;
|
|
203
206
|
});
|
|
204
207
|
|
|
205
|
-
const runtime = createRuntime();
|
|
208
|
+
const runtime = createRuntime({ prepareMagpieInstallation });
|
|
206
209
|
const server = await runtime.startOpenCode();
|
|
207
210
|
const [binary, args, options] = spawnMock.mock.calls[0];
|
|
208
211
|
|
|
@@ -1100,13 +1103,15 @@ describe('shared official OpenCode service', () => {
|
|
|
1100
1103
|
const sharedService = createSharedService();
|
|
1101
1104
|
const adoptOpenCodeServerPassword = vi.fn();
|
|
1102
1105
|
const stateRef = {};
|
|
1103
|
-
const
|
|
1106
|
+
const prepareMagpieInstallation = vi.fn(async () => {});
|
|
1107
|
+
const runtime = createRuntime({ env: UNCONFIGURED_ENV, sharedService, adoptOpenCodeServerPassword, prepareMagpieInstallation }, stateRef);
|
|
1104
1108
|
const fetchMock = stubSharedFetch();
|
|
1105
1109
|
|
|
1106
1110
|
await runtime.bootstrapOpenCodeAtStartup();
|
|
1107
1111
|
|
|
1108
1112
|
expect(spawnMock).not.toHaveBeenCalled();
|
|
1109
1113
|
expect(sharedService.start).toHaveBeenCalledTimes(1);
|
|
1114
|
+
expect(prepareMagpieInstallation.mock.invocationCallOrder[0]).toBeLessThan(sharedService.start.mock.invocationCallOrder[0]);
|
|
1110
1115
|
const startInput = sharedService.start.mock.calls[0][0];
|
|
1111
1116
|
expect(startInput.binary).toBe('/mock/opencode');
|
|
1112
1117
|
expect(startInput.env.OPENCODE_SERVER_PASSWORD).toBeUndefined();
|