@openchambery/web 2.0.0-beta.9 → 2.0.0
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 +14 -0
- package/dist/assets/{AgentAvatar-CzTtVMnI.js → AgentAvatar-C1MGZ7qC.js} +1 -1
- package/dist/assets/AssistantView-vqCFvVo0.js +3 -0
- package/dist/assets/{DiagramView-oeXj4YHn.js → DiagramView-CYO5iVVm.js} +1 -1
- package/dist/assets/{FadeInOnReveal-DCqrQkkz.js → FadeInOnReveal-BMSk_tY-.js} +1 -1
- package/dist/assets/Icon-BdzPRCSy.js +1 -0
- package/dist/assets/MarkdownRenderer-FMynjkGR.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-CahUctxC.js → MarkdownRendererImpl-DoWvw5jj.js} +3 -3
- package/dist/assets/{MarkstreamRendererImpl-5JUaTSdl.js → MarkstreamRendererImpl-DoeYajmw.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-agQ-Yhpd.js → MobileDetailNavigation-BzBz-co_.js} +1 -1
- package/dist/assets/{MobileShareBridge-yCN95dYg.js → MobileShareBridge-D1Uoj6l_.js} +3 -3
- package/dist/assets/MobileSurface-8YpZ51Qo.js +2 -0
- package/dist/assets/{MultiRunWindow-C0HqMFpp.js → MultiRunWindow-BxcYUPta.js} +1 -1
- package/dist/assets/{OnboardingScreen-O4cGdz5p.js → OnboardingScreen-yRdQTR1M.js} +1 -1
- package/dist/assets/{OverlayScrollbar-CRfGtcmG.js → OverlayScrollbar-BUjtqnxn.js} +1 -1
- package/dist/assets/SettingsView-D7KrvqvH.js +25 -0
- package/dist/assets/{SettingsWindow-B_d5Aqq3.js → SettingsWindow-DX2w-cDc.js} +1 -1
- package/dist/assets/{TerminalView-DdNQQb8W.js → TerminalView-CiUcCQPm.js} +1 -1
- package/dist/assets/{ToolOutputDialog-4DC5OE1J.js → ToolOutputDialog-DQvXtK71.js} +1 -1
- package/dist/assets/appThemeRegistry-BXC6cIDE.js +1 -0
- package/dist/assets/{assistant-attachment-cache-DphJleHr.js → assistant-attachment-cache-sG8YQsVs.js} +1 -1
- package/dist/assets/{button-DIIrsMjW.js → button-CUwjpv-i.js} +1 -1
- package/dist/assets/{collapsible-CnvUeYst.js → collapsible-paItOH3A.js} +1 -1
- package/dist/assets/{context-menu-BGBUpZEQ.js → context-menu-K6zNumqN.js} +1 -1
- package/dist/assets/{desktopHosts-JGFqhlaM.js → desktopHosts-DuftEj8B.js} +1 -1
- package/dist/assets/desktopRelayRestore-g378BdZf.js +1 -0
- package/dist/assets/{diagram-rvn7_qBM.js → diagram-D6nY5hbD.js} +1 -1
- package/dist/assets/dialog-PjKWayIU.js +1 -0
- package/dist/assets/dropdown-menu.styles-ayLWk2gf.js +1 -0
- package/dist/assets/{es-Dsmc0A_3.js → es-BBbon7SL.js} +4 -4
- package/dist/assets/fileMentionTouchSelection-78v5mNTE.js +2 -0
- package/dist/assets/{fileQueries-DN6gbLhM.js → fileQueries-DuF89nFO.js} +1 -1
- package/dist/assets/{fr-C_ZlZDI2.js → fr-BsKj2CTi.js} +4 -4
- package/dist/assets/{gitApi-da8etTTe.js → gitApi-eoKSJcvW.js} +4 -4
- package/dist/assets/{imageSource-BMztRu0c.js → imageSource-BpUsiwFd.js} +1 -1
- package/dist/assets/{input-BVu-iykl.js → input-BeVw4D0W.js} +1 -1
- package/dist/assets/insertionBoundaries-BI3RHOpf.js +1 -0
- package/dist/assets/{ja-BqqTaWTi.js → ja-1mgxunoM.js} +4 -4
- package/dist/assets/{ko-DP2XPM37.js → ko-Ra9znPcL.js} +4 -4
- package/dist/assets/main-CQpUPjPK.js +169 -0
- package/dist/assets/{main-KwlAomA8.js → main-MN0YKJ0_.js} +2 -2
- package/dist/assets/main-qSeeJRW1.css +1 -0
- package/dist/assets/{markdownHeightCache-D9-B1wdY.js → markdownHeightCache-CGCWmHgq.js} +1 -1
- package/dist/assets/{mcpQueries-3GTWeMU-.js → mcpQueries-X424Iq2z.js} +1 -1
- package/dist/assets/miniChat-DFIl76ku.js +2 -0
- package/dist/assets/mobile-C3fWXGQJ.js +2 -0
- package/dist/assets/multirun-DuSX3YRs.js +3 -0
- package/dist/assets/persistence-Dnj1QOdX.js +3 -0
- package/dist/assets/pl-B714sdql.js +15 -0
- package/dist/assets/platform-jCZ3KfMQ.js +6 -0
- package/dist/assets/projectMeta-Cn08pTNi.js +1 -0
- package/dist/assets/{pt-BR-W09Q2pvn.js → pt-BR-DVoT_I7_.js} +4 -4
- package/dist/assets/{relay-image-stream-DrdVj99c.js → relay-image-stream-D2XooXKZ.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-CRPdctIn.js +2 -0
- package/dist/assets/renderMobileApp-CVURQbeh.js +28 -0
- package/dist/assets/{runtime-fetch-Fs9ZV2V8.js → runtime-fetch-CiqQd__Y.js} +1 -1
- package/dist/assets/runtime-switch-BJM2k_uH.js +1 -0
- package/dist/assets/runtimeConfig-xivE1wxC.js +2 -0
- package/dist/assets/runtimeEndpointReset-B8DzKzwg.js +148 -0
- package/dist/assets/runtimeEndpointReset-D_vfNOQz.css +1 -0
- package/dist/assets/sessionChangeSummary-yBsR2pyd.js +2 -0
- package/dist/assets/{sessionLookup-0Z4e-J1g.js → sessionLookup-1Z1FWZRB.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CE7W7lJ9.js → sortable-tabs-strip-D611LPyd.js} +1 -1
- package/dist/assets/src-TBM7scxy.css +2 -0
- package/dist/assets/{store-jaP79mqI.js → store-l_X_jQNz.js} +4 -4
- package/dist/assets/{terminalApi-mDR2jXTo.js → terminalApi-BZW_rAc3.js} +1 -1
- package/dist/assets/{textarea-XD2oX_Ee.js → textarea-QtZjf4L8.js} +1 -1
- package/dist/assets/{tooltip-DYeQ_Hvy.js → tooltip-DDNnmcXI.js} +1 -1
- package/dist/assets/{uk-BgFzz8m2.js → uk-ChNnTgKP.js} +4 -4
- package/dist/assets/{url-CGbJbiJq.js → url-DNRMq_0R.js} +1 -1
- package/dist/assets/{useAppFontEffects-y_GTVXhJ.js → useAppFontEffects-DJy8P5By.js} +55 -55
- package/dist/assets/{useAssistantUIStore-Bz4tRflF.js → useAssistantUIStore-DBwfg9nX.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-DlwSomVK.js +1 -0
- package/dist/assets/{useEffectiveDirectory-BW9jNyuK.js → useEffectiveDirectory-eJorVTT9.js} +1 -1
- package/dist/assets/{useFontPreferences-Bjx8zkGM.js → useFontPreferences-Bjgt_lzl.js} +1 -1
- package/dist/assets/useMobileNavigationStore-A_ZNTfpl.js +415 -0
- package/dist/assets/useSessionAutoCleanup-CqC3dEou.js +2 -0
- package/dist/assets/{useUIStore-syVmbuDA.js → useUIStore-Dfx6TKvi.js} +1 -1
- package/dist/assets/{useWorkerHighlightedLines-BUcSLKlL.js → useWorkerHighlightedLines-B0vSL6jr.js} +1 -1
- package/dist/assets/vendor-opencode-client-MF2GBrFx.js +12 -0
- package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-DzRqcymP.js} +1 -1
- package/dist/assets/{zh-CN-BhqSLgUY.js → zh-CN-DHEFf4u1.js} +4 -4
- package/dist/assets/{zh-TW-BsaBgY-p.js → zh-TW-rdSa8atW.js} +4 -4
- package/dist/index.html +13 -13
- package/dist/mini-chat.html +13 -13
- package/dist/mobile.html +13 -13
- package/package.json +3 -3
- package/server/index.js +8 -20
- package/server/lib/client-auth/remote-clients.js +15 -0
- package/server/lib/client-auth/remote-clients.test.js +42 -1
- package/server/lib/git/DOCUMENTATION.md +1 -0
- package/server/lib/git/routes.js +7 -4
- package/server/lib/git/routes.test.js +34 -0
- package/server/lib/git/service.js +3 -1
- package/server/lib/llm/DOCUMENTATION.md +6 -4
- package/server/lib/llm/generate.js +8 -9
- package/server/lib/llm/generate.test.js +33 -6
- package/server/lib/message-queue/service.js +1 -1
- package/server/lib/opencode/DOCUMENTATION.md +33 -22
- package/server/lib/opencode/agents.js +13 -22
- package/server/lib/opencode/auth-state-runtime.js +3 -3
- package/server/lib/opencode/auth-state-runtime.test.js +59 -0
- package/server/lib/opencode/auth.d.ts +13 -0
- package/server/lib/opencode/auth.js +74 -64
- package/server/lib/opencode/auth.test.js +57 -0
- package/server/lib/opencode/commands.js +1 -1
- package/server/lib/opencode/config-entities-layering.test.js +72 -0
- package/server/lib/opencode/config-entity-routes.js +10 -123
- package/server/lib/opencode/config-entity-routes.test.js +23 -11
- package/server/lib/opencode/config-files.d.ts +17 -0
- package/server/lib/opencode/config-files.js +187 -0
- package/server/lib/opencode/config-files.test.js +201 -0
- package/server/lib/opencode/core-routes.js +17 -1
- package/server/lib/opencode/core-routes.test.js +43 -15
- package/server/lib/opencode/credential-boundary.d.ts +1 -0
- package/server/lib/opencode/credential-boundary.js +11 -0
- package/server/lib/opencode/ensure-cli.js +4 -1
- package/server/lib/opencode/env-config.js +4 -1
- package/server/lib/opencode/env-config.test.js +28 -0
- package/server/lib/opencode/env-runtime.test.js +18 -1
- package/server/lib/opencode/feature-routes-runtime.js +9 -7
- package/server/lib/opencode/hmr-state-runtime.js +4 -4
- package/server/lib/opencode/lifecycle.js +1 -49
- package/server/lib/opencode/lifecycle.test.js +30 -4
- package/server/lib/opencode/location-release.d.ts +8 -0
- package/server/lib/opencode/location-release.js +32 -0
- package/server/lib/opencode/location-release.test.js +43 -0
- package/server/lib/opencode/mcp.js +1 -1
- package/server/lib/opencode/opencode2-pin.js +2 -1
- package/server/lib/opencode/owned-runtime-upgrade.test.js +5 -5
- package/server/lib/opencode/plugin-routes.test.js +4 -2
- package/server/lib/opencode/plugins.js +12 -60
- package/server/lib/opencode/plugins.test.js +5 -3
- package/server/lib/opencode/providers.js +6 -3
- package/server/lib/opencode/proxy.js +7 -0
- package/server/lib/opencode/proxy.test.js +13 -0
- package/server/lib/opencode/remote-upgrade.integration.test.js +66 -0
- package/server/lib/opencode/routes.js +10 -8
- package/server/lib/opencode/routes.test.js +51 -10
- package/server/lib/opencode/runtime-contract.js +3 -2
- package/server/lib/opencode/runtime-contract.test.js +12 -12
- package/server/lib/opencode/settings-normalization-runtime.js +3 -1
- package/server/lib/opencode/settings-normalization-runtime.test.js +36 -0
- package/server/lib/opencode/settings-runtime.test.js +58 -1
- package/server/lib/opencode/shared.js +6 -175
- package/server/lib/opencode/update-discovery.js +39 -0
- package/server/lib/opencode/update-discovery.test.js +39 -0
- package/server/lib/opencode/upgrade-screen.js +4 -11
- package/server/lib/opencode/upgrade-screen.test.js +25 -25
- package/server/lib/opencode/version-alignment.test.js +40 -0
- package/server/lib/question-auto-delegate/DOCUMENTATION.md +21 -6
- package/server/lib/question-auto-delegate/core.d.ts +5 -1
- package/server/lib/question-auto-delegate/core.js +21 -13
- package/server/lib/question-auto-delegate/form-io.d.ts +6 -0
- package/server/lib/question-auto-delegate/form-io.js +39 -0
- package/server/lib/question-auto-delegate/runtime.js +4 -24
- package/server/lib/question-auto-delegate/runtime.test.js +138 -0
- package/server/lib/quota/DOCUMENTATION.md +9 -0
- package/server/lib/quota/provider-credentials.test.js +29 -0
- package/server/lib/quota/providers/claude.js +4 -4
- package/server/lib/quota/providers/codex.js +4 -4
- package/server/lib/quota/providers/codex.test.js +1 -1
- package/server/lib/quota/providers/copilot.js +5 -5
- package/server/lib/quota/providers/google/auth.js +3 -3
- package/server/lib/quota/providers/google/index.js +2 -2
- package/server/lib/quota/providers/index.js +3 -7
- package/server/lib/quota/providers/index.test.js +2 -2
- package/server/lib/quota/providers/kimi.js +4 -4
- package/server/lib/quota/providers/minimax-shared.js +4 -4
- package/server/lib/quota/providers/nanogpt.js +4 -4
- package/server/lib/quota/providers/openai.js +4 -4
- package/server/lib/quota/providers/openrouter.js +4 -4
- package/server/lib/quota/providers/wafer.js +4 -4
- package/server/lib/quota/providers/zai.js +4 -4
- package/server/lib/quota/providers/zhipuai-coding-plan.js +6 -20
- package/server/lib/quota/routes.js +1 -1
- package/server/lib/relay/DOCUMENTATION.md +3 -1
- package/server/lib/relay/service.js +1 -0
- package/server/lib/relay/service.test.js +13 -0
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -1
- package/server/lib/scheduled-tasks/runtime.js +12 -4
- package/server/lib/scheduled-tasks/runtime.test.js +81 -6
- package/server/lib/session-goal/DOCUMENTATION.md +11 -0
- package/server/lib/session-goal/runtime.js +4 -4
- package/server/lib/session-goal/runtime.test.js +32 -3
- package/server/lib/session-index/DOCUMENTATION.md +7 -0
- package/server/lib/session-index/event-ingest.js +13 -0
- package/server/lib/session-index/event-ingest.test.js +7 -0
- package/server/lib/session-index/service.js +45 -0
- package/server/lib/session-index/service.test.js +47 -0
- package/server/lib/session-metadata/DOCUMENTATION.md +6 -0
- package/server/lib/session-metadata/session-metadata-store.test.js +23 -0
- package/server/lib/session-metadata/session-projection.d.ts +1 -0
- package/server/lib/session-metadata/session-projection.js +23 -0
- package/server/lib/session-metadata/session-projection.test.js +35 -0
- package/server/lib/session-title/DOCUMENTATION.md +52 -14
- package/server/lib/session-title/native-runtime.test.js +59 -5
- package/server/lib/session-title/runtime.js +63 -17
- package/server/lib/session-title/session-access.js +108 -3
- package/server/lib/session-title/session-access.test.js +125 -7
- package/server/lib/tts/DOCUMENTATION.md +7 -5
- package/server/lib/tts/credential-source.test.js +23 -0
- package/server/lib/tts/routes.js +2 -2
- package/server/lib/tts/service.js +16 -37
- package/dist/assets/AssistantView-CLFPj5Ki.js +0 -3
- package/dist/assets/Icon-B8fSTLdP.js +0 -1
- package/dist/assets/MarkdownRenderer-BK69MEHQ.js +0 -2
- package/dist/assets/MobileSurface-D0HP_lTS.js +0 -2
- package/dist/assets/SettingsView-xLYb22wV.js +0 -25
- package/dist/assets/appThemeRegistry-LkVH62uF.js +0 -1
- package/dist/assets/desktopRelayRestore-CHEwrI93.js +0 -1
- package/dist/assets/dialog-CQ63sc9E.js +0 -1
- package/dist/assets/dropdown-menu.styles-Bzo6L3FH.js +0 -1
- package/dist/assets/fileMentionTouchSelection-CBzDuQxP.js +0 -3
- package/dist/assets/insertionBoundaries-DQrE5qG2.js +0 -1
- package/dist/assets/main-D2BzpnUt.css +0 -1
- package/dist/assets/main-DYISNvWY.js +0 -169
- package/dist/assets/miniChat-DYIqdWi7.js +0 -2
- package/dist/assets/mobile-CwiCcmWt.js +0 -2
- package/dist/assets/multirun-C3snjTFs.js +0 -4
- package/dist/assets/persistence-D0y7226-.js +0 -3
- package/dist/assets/pl-BCf5uLlK.js +0 -15
- package/dist/assets/platform-CurqO28D.js +0 -6
- package/dist/assets/projectMeta-BkhYFMaM.js +0 -3
- package/dist/assets/renderElectronMiniChatApp-7iyh_419.js +0 -2
- package/dist/assets/renderMobileApp-y0_NeckR.js +0 -28
- package/dist/assets/runtime-switch-LvWDU-SL.js +0 -1
- package/dist/assets/runtimeConfig-12C5j02f.js +0 -2
- package/dist/assets/runtimeEndpointReset-DTY2ZXXb.js +0 -149
- package/dist/assets/sessionChangeSummary-CwPBIb3Z.js +0 -2
- package/dist/assets/src-lS1keDVV.css +0 -2
- package/dist/assets/useDesktopWindowControlsLayout-CR_DcY77.js +0 -1
- package/dist/assets/useMobileNavigationStore-DwupvLSA.js +0 -414
- package/dist/assets/useSessionAutoCleanup-DkGO_usj.js +0 -2
- package/dist/assets/vendor-opencode-client-C12gfjag.js +0 -12
- package/server/lib/opencode/snippets.js +0 -242
- package/server/lib/opencode/snippets.test.js +0 -97
|
@@ -38,6 +38,15 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
|
|
|
38
38
|
- `providers/openai.js` exists for logic parity/reuse but is intentionally not registered for dispatcher ID routing.
|
|
39
39
|
|
|
40
40
|
## Response contract
|
|
41
|
+
Provider credentials are read asynchronously from the current OpenCode service via
|
|
42
|
+
`opencode/auth.js`; only the selected credential is used. Account switching and
|
|
43
|
+
logout are observed on the next request, without file fallback or persisted secret
|
|
44
|
+
cache. Credential-list transport/auth/schema failure rejects the configured-provider
|
|
45
|
+
list instead of returning an authoritative empty list; per-provider quota requests
|
|
46
|
+
return `ok: false` with a sanitized error. VS Code uses the same credential adapter.
|
|
47
|
+
OpenChamber-owned quota cookies and Grok/Antigravity-specific stores retain their
|
|
48
|
+
separate explicit sources; they are not legacy OpenCode `auth.json` fallbacks.
|
|
49
|
+
|
|
41
50
|
All providers should return results via shared helpers to preserve API shape:
|
|
42
51
|
- Required fields: `providerId`, `providerName`, `ok`, `configured`, `usage`, `fetchedAt`
|
|
43
52
|
- Optional field: `error`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { afterEach, expect, it, vi } from 'vitest';
|
|
2
|
+
import { configureOpenCodeCredentials } from '../opencode/auth.js';
|
|
3
|
+
import { fetchQuota as fetchCodexQuota } from './providers/codex.js';
|
|
4
|
+
import { fetchQuotaForProvider, listConfiguredQuotaProviders } from './providers/index.js';
|
|
5
|
+
|
|
6
|
+
afterEach(() => { configureOpenCodeCredentials(null); vi.unstubAllGlobals(); });
|
|
7
|
+
|
|
8
|
+
it('quota requests follow the current active account, then stop sending after logout', async () => {
|
|
9
|
+
let access = 'first-account';
|
|
10
|
+
configureOpenCodeCredentials(() => ({ credential: { list: async () => access ? [{ id: 'selected', integrationID: 'openai', active: true, value: { type: 'oauth', access, refresh: 'refresh', expires: 123 } }] : [] } }));
|
|
11
|
+
const fetchMock = vi.fn(async () => Response.json({ rate_limit: { primary_window: { used_percent: 20 } } }));
|
|
12
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
13
|
+
await fetchCodexQuota();
|
|
14
|
+
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe('Bearer first-account');
|
|
15
|
+
access = 'second-account';
|
|
16
|
+
await fetchCodexQuota();
|
|
17
|
+
expect(fetchMock.mock.calls[1][1].headers.Authorization).toBe('Bearer second-account');
|
|
18
|
+
access = '';
|
|
19
|
+
await expect(fetchCodexQuota()).resolves.toMatchObject({ ok: false, configured: false });
|
|
20
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('quota source failure remains an error with no upstream secret in the response', async () => {
|
|
24
|
+
configureOpenCodeCredentials(() => ({ credential: { list: async () => { throw new Error('secret-from-upstream'); } } }));
|
|
25
|
+
await expect(listConfiguredQuotaProviders()).rejects.toThrow('Unable to read OpenCode credentials');
|
|
26
|
+
const result = await fetchQuotaForProvider('codex');
|
|
27
|
+
expect(result).toMatchObject({ ok: false, error: 'Unable to read OpenCode credentials' });
|
|
28
|
+
expect(JSON.stringify(result)).not.toContain('secret-from-upstream');
|
|
29
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -12,14 +12,14 @@ export const providerId = 'claude';
|
|
|
12
12
|
export const providerName = 'Claude';
|
|
13
13
|
const aliases = ['anthropic', 'claude'];
|
|
14
14
|
|
|
15
|
-
export const isConfigured = () => {
|
|
16
|
-
const auth =
|
|
15
|
+
export const isConfigured = async () => {
|
|
16
|
+
const auth = await readOpenCodeCredentials();
|
|
17
17
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
18
|
return Boolean(entry?.access || entry?.token);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const fetchQuota = async () => {
|
|
22
|
-
const auth =
|
|
22
|
+
const auth = await readOpenCodeCredentials();
|
|
23
23
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
24
|
const accessToken = entry?.access ?? entry?.token;
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'codex';
|
|
|
14
14
|
export const providerName = 'Codex';
|
|
15
15
|
const aliases = ['openai', 'codex', 'chatgpt'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.access || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const accessToken = entry?.access ?? entry?.token;
|
|
27
27
|
const accountId = entry?.accountId;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
3
|
vi.mock('../../opencode/auth.js', () => ({
|
|
4
|
-
|
|
4
|
+
readOpenCodeCredentials: async () => ({ openai: { access: 'test-token' } }),
|
|
5
5
|
}));
|
|
6
6
|
|
|
7
7
|
import { fetchQuota } from './codex.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -42,14 +42,14 @@ export const providerId = 'github-copilot';
|
|
|
42
42
|
export const providerName = 'GitHub Copilot';
|
|
43
43
|
const aliases = ['github-copilot', 'copilot'];
|
|
44
44
|
|
|
45
|
-
export const isConfigured = () => {
|
|
46
|
-
const auth =
|
|
45
|
+
export const isConfigured = async () => {
|
|
46
|
+
const auth = await readOpenCodeCredentials();
|
|
47
47
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
48
48
|
return Boolean(entry?.access || entry?.token);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
export const fetchQuota = async () => {
|
|
52
|
-
const auth =
|
|
52
|
+
const auth = await readOpenCodeCredentials();
|
|
53
53
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
54
54
|
const accessToken = entry?.access ?? entry?.token;
|
|
55
55
|
|
|
@@ -107,7 +107,7 @@ export const providerIdAddon = 'github-copilot-addon';
|
|
|
107
107
|
export const providerNameAddon = 'GitHub Copilot Add-on';
|
|
108
108
|
|
|
109
109
|
export const fetchQuotaAddon = async () => {
|
|
110
|
-
const auth =
|
|
110
|
+
const auth = await readOpenCodeCredentials();
|
|
111
111
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
112
112
|
const accessToken = entry?.access ?? entry?.token;
|
|
113
113
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
asNonEmptyString,
|
|
15
15
|
toTimestamp
|
|
16
16
|
} from '../../utils/index.js';
|
|
17
|
-
import {
|
|
17
|
+
import { readOpenCodeCredentials } from '../../../opencode/auth.js';
|
|
18
18
|
import { parseGoogleRefreshToken } from './transforms.js';
|
|
19
19
|
|
|
20
20
|
const ANTIGRAVITY_GOOGLE_CLIENT_ID =
|
|
@@ -90,8 +90,8 @@ const resolveAntigravityAuth = () => {
|
|
|
90
90
|
return null;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
export const resolveGoogleAuthSources = () => {
|
|
94
|
-
const auth =
|
|
93
|
+
export const resolveGoogleAuthSources = async () => {
|
|
94
|
+
const auth = await readOpenCodeCredentials();
|
|
95
95
|
const sources = [];
|
|
96
96
|
|
|
97
97
|
const geminiAuth = resolveGeminiCliAuth(auth);
|
|
@@ -17,10 +17,10 @@ export const providerId = 'google';
|
|
|
17
17
|
export const providerName = 'Google';
|
|
18
18
|
export const aliases = ['google', 'google.oauth'];
|
|
19
19
|
|
|
20
|
-
export const isConfigured = () => resolveGoogleAuthSources().length > 0;
|
|
20
|
+
export const isConfigured = async () => (await resolveGoogleAuthSources()).length > 0;
|
|
21
21
|
|
|
22
22
|
export const fetchGoogleQuota = async () => {
|
|
23
|
-
const authSources = resolveGoogleAuthSources();
|
|
23
|
+
const authSources = await resolveGoogleAuthSources();
|
|
24
24
|
if (!authSources.length) {
|
|
25
25
|
return buildResult({
|
|
26
26
|
providerId,
|
|
@@ -130,16 +130,12 @@ const registry = {
|
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
132
|
|
|
133
|
-
export const listConfiguredQuotaProviders = () => {
|
|
133
|
+
export const listConfiguredQuotaProviders = async () => {
|
|
134
134
|
const configured = [];
|
|
135
135
|
|
|
136
136
|
for (const [id, provider] of Object.entries(registry)) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
configured.push(id);
|
|
140
|
-
}
|
|
141
|
-
} catch {
|
|
142
|
-
// Ignore provider-specific config errors in list API.
|
|
137
|
+
if (await provider.isConfigured()) {
|
|
138
|
+
configured.push(id);
|
|
143
139
|
}
|
|
144
140
|
}
|
|
145
141
|
|
|
@@ -11,7 +11,7 @@ describe('quota provider registry', () => {
|
|
|
11
11
|
expect(typeof google.resolveGoogleAuthSources).toBe('function');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it('
|
|
15
|
-
expect(
|
|
14
|
+
it('reports unavailable credentials instead of an empty configured-provider list', async () => {
|
|
15
|
+
await expect(listConfiguredQuotaProviders()).rejects.toThrow('Unable to read OpenCode credentials');
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'kimi-for-coding';
|
|
|
14
14
|
export const providerName = 'Kimi for Coding';
|
|
15
15
|
const aliases = ['kimi-for-coding', 'kimi'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.key || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const apiKey = entry?.key ?? entry?.token;
|
|
27
27
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -138,14 +138,14 @@ const calculateUsage = (model, isTokenPlan) => {
|
|
|
138
138
|
};
|
|
139
139
|
|
|
140
140
|
export const createMiniMaxCodingPlanProvider = ({ providerId, providerName, aliases, tokenPlanUrl, codingPlanUrl }) => {
|
|
141
|
-
const isConfigured = () => {
|
|
142
|
-
const auth =
|
|
141
|
+
const isConfigured = async () => {
|
|
142
|
+
const auth = await readOpenCodeCredentials();
|
|
143
143
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
144
144
|
return Boolean(entry?.key || entry?.token);
|
|
145
145
|
};
|
|
146
146
|
|
|
147
147
|
const fetchQuota = async () => {
|
|
148
|
-
const auth =
|
|
148
|
+
const auth = await readOpenCodeCredentials();
|
|
149
149
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
150
150
|
const apiKey = entry?.key ?? entry?.token;
|
|
151
151
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -14,14 +14,14 @@ export const providerId = 'nano-gpt';
|
|
|
14
14
|
export const providerName = 'NanoGPT';
|
|
15
15
|
const aliases = ['nano-gpt', 'nanogpt', 'nano_gpt'];
|
|
16
16
|
|
|
17
|
-
export const isConfigured = () => {
|
|
18
|
-
const auth =
|
|
17
|
+
export const isConfigured = async () => {
|
|
18
|
+
const auth = await readOpenCodeCredentials();
|
|
19
19
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
20
|
return Boolean(entry?.key || entry?.token);
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const fetchQuota = async () => {
|
|
24
|
-
const auth =
|
|
24
|
+
const auth = await readOpenCodeCredentials();
|
|
25
25
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
26
|
const apiKey = entry?.key ?? entry?.token;
|
|
27
27
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -12,14 +12,14 @@ const providerId = 'openai';
|
|
|
12
12
|
const providerName = 'OpenAI';
|
|
13
13
|
const aliases = ['openai', 'codex', 'chatgpt'];
|
|
14
14
|
|
|
15
|
-
const isConfigured = () => {
|
|
16
|
-
const auth =
|
|
15
|
+
const isConfigured = async () => {
|
|
16
|
+
const auth = await readOpenCodeCredentials();
|
|
17
17
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
18
|
return Boolean(entry?.access || entry?.token);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const fetchQuota = async () => {
|
|
22
|
-
const auth =
|
|
22
|
+
const auth = await readOpenCodeCredentials();
|
|
23
23
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
24
|
const accessToken = entry?.access ?? entry?.token;
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -12,14 +12,14 @@ export const providerId = 'openrouter';
|
|
|
12
12
|
export const providerName = 'OpenRouter';
|
|
13
13
|
const aliases = ['openrouter'];
|
|
14
14
|
|
|
15
|
-
export const isConfigured = () => {
|
|
16
|
-
const auth =
|
|
15
|
+
export const isConfigured = async () => {
|
|
16
|
+
const auth = await readOpenCodeCredentials();
|
|
17
17
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
18
|
return Boolean(entry?.key || entry?.token);
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const fetchQuota = async () => {
|
|
22
|
-
const auth =
|
|
22
|
+
const auth = await readOpenCodeCredentials();
|
|
23
23
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
24
|
const apiKey = entry?.key ?? entry?.token;
|
|
25
25
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -17,14 +17,14 @@ const aliases = ['wafer', 'wafer-ai', 'wafer_ai', 'wafer.ai'];
|
|
|
17
17
|
const WAFER_QUOTA_URL = 'https://pass.wafer.ai/v1/inference/quota';
|
|
18
18
|
const WAFER_WINDOW_SECONDS = 5 * 3600;
|
|
19
19
|
|
|
20
|
-
export const isConfigured = () => {
|
|
21
|
-
const auth =
|
|
20
|
+
export const isConfigured = async () => {
|
|
21
|
+
const auth = await readOpenCodeCredentials();
|
|
22
22
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
23
23
|
return Boolean(entry?.key || entry?.token);
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export const fetchQuota = async () => {
|
|
27
|
-
const auth =
|
|
27
|
+
const auth = await readOpenCodeCredentials();
|
|
28
28
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
29
29
|
const apiKey = entry?.key ?? entry?.token;
|
|
30
30
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
2
2
|
import {
|
|
3
3
|
getAuthEntry,
|
|
4
4
|
normalizeAuthEntry,
|
|
@@ -15,14 +15,14 @@ export const providerId = 'zai-coding-plan';
|
|
|
15
15
|
export const providerName = 'z.ai';
|
|
16
16
|
const aliases = ['zai-coding-plan', 'zai', 'z.ai'];
|
|
17
17
|
|
|
18
|
-
export const isConfigured = () => {
|
|
19
|
-
const auth =
|
|
18
|
+
export const isConfigured = async () => {
|
|
19
|
+
const auth = await readOpenCodeCredentials();
|
|
20
20
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
21
21
|
return Boolean(entry?.key || entry?.token);
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export const fetchQuota = async () => {
|
|
25
|
-
const auth =
|
|
25
|
+
const auth = await readOpenCodeCredentials();
|
|
26
26
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
27
27
|
const apiKey = entry?.key ?? entry?.token;
|
|
28
28
|
|
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
* @property {number} [nextResetTime]
|
|
26
26
|
* @property {Array<{modelCode: string, usage: number}>} [usageDetails]
|
|
27
27
|
*/
|
|
28
|
-
import {
|
|
29
|
-
import { readConfigLayers } from '../../opencode/shared.js';
|
|
28
|
+
import { readOpenCodeCredentials } from '../../opencode/auth.js';
|
|
30
29
|
import {
|
|
31
30
|
getAuthEntry,
|
|
32
31
|
normalizeAuthEntry,
|
|
@@ -40,8 +39,8 @@ export const providerId = 'zhipuai-coding-plan';
|
|
|
40
39
|
export const providerName = 'Zhipu AI Coding Plan';
|
|
41
40
|
const aliases = ['zhipuai-coding-plan', 'zhipuai', 'zhipu'];
|
|
42
41
|
|
|
43
|
-
function getApiKey() {
|
|
44
|
-
const auth =
|
|
42
|
+
async function getApiKey() {
|
|
43
|
+
const auth = await readOpenCodeCredentials();
|
|
45
44
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
46
45
|
const apiKeyFromAuth = entry?.key ?? entry?.token;
|
|
47
46
|
|
|
@@ -49,28 +48,15 @@ function getApiKey() {
|
|
|
49
48
|
return apiKeyFromAuth;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
try {
|
|
53
|
-
const { mergedConfig } = readConfigLayers();
|
|
54
|
-
|
|
55
|
-
for (const alias of aliases) {
|
|
56
|
-
const providerConfig = mergedConfig?.provider?.[alias];
|
|
57
|
-
if (providerConfig?.options?.apiKey) {
|
|
58
|
-
return providerConfig.options.apiKey;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} catch {
|
|
62
|
-
// Ignore config read errors; the provider will be treated as not configured.
|
|
63
|
-
}
|
|
64
|
-
|
|
65
51
|
return null;
|
|
66
52
|
}
|
|
67
53
|
|
|
68
|
-
export const isConfigured = () => {
|
|
69
|
-
return Boolean(getApiKey());
|
|
54
|
+
export const isConfigured = async () => {
|
|
55
|
+
return Boolean(await getApiKey());
|
|
70
56
|
};
|
|
71
57
|
|
|
72
58
|
export const fetchQuota = async () => {
|
|
73
|
-
const apiKey = getApiKey();
|
|
59
|
+
const apiKey = await getApiKey();
|
|
74
60
|
|
|
75
61
|
if (!apiKey) {
|
|
76
62
|
return buildResult({
|
|
@@ -28,7 +28,7 @@ export function registerQuotaRoutes(app, { getQuotaProviders }) {
|
|
|
28
28
|
app.get('/api/quota/providers', async (_req, res) => {
|
|
29
29
|
try {
|
|
30
30
|
const { listConfiguredQuotaProviders } = await getQuotaProviders();
|
|
31
|
-
res.json({ providers: listConfiguredQuotaProviders() });
|
|
31
|
+
res.json({ providers: await listConfiguredQuotaProviders() });
|
|
32
32
|
} catch (error) {
|
|
33
33
|
console.error('Failed to list quota providers:', error);
|
|
34
34
|
res.status(500).json({ error: error.message || 'Failed to list quota providers' });
|
|
@@ -39,7 +39,8 @@ Relay is not a separate link format: it is one transport candidate inside the un
|
|
|
39
39
|
## What travels the tunnel
|
|
40
40
|
|
|
41
41
|
Everything a client normally sends to the single OpenChamber origin:
|
|
42
|
-
- **HTTP** — REST endpoints and proxied OpenCode SDK calls under `/api/*`, plus `/auth/*` and `/health`.
|
|
42
|
+
- **HTTP** — REST endpoints and proxied OpenCode SDK calls under `/api/*`, plus `/auth/*` and `/health`. On a packaged desktop client the UI origin is `openchamber-ui://` (`location.origin` is `"null"`) while the SDK still addresses the injected loopback. Relay mode tunnels those shell-local absolute `/api` `/auth` `/health` URLs; they must not fall through to the local OpenCode process.
|
|
43
|
+
- **Events** — relay `auto` uses SSE (bearer), the same path mobile uses. Desktop WebSocket upgrades over the tunnel close and drop assistant deltas; an explicit `ws` setting is unchanged.
|
|
43
44
|
- **SSE** — streamed responses opened through `runtimeFetch`, including `/api/openchamber/events` and SDK global SSE. Relay carries these HTTP response body frames through the tunnel.
|
|
44
45
|
- **WebSocket** — the endpoints that use a real socket (the global event stream on platforms that support WS, terminal I/O, dictation, and Preview/Browser HMR under `/api/preview/proxy/<id>/...`).
|
|
45
46
|
|
|
@@ -117,6 +118,7 @@ Catalog loaders (`loadProviders` / `loadAgents`) and assistant Query keys gate w
|
|
|
117
118
|
- The tunnel is transparent to the app: adding relay support to a feature should not require the feature to know the relay exists — it goes through the shared runtime transport helpers.
|
|
118
119
|
- The two implementations stay byte-compatible and the wire format is versioned/negotiated so mixed client/host app versions degrade gracefully rather than break. The Layer 1 Relay does not inspect application versions; unsupported Host/Client features are omitted at the endpoints.
|
|
119
120
|
- Host-control reconnect storms are bounded at both ends: the Host does not reset backoff on a brief `open`, and the Relay rate-limits host-control upgrades per IP and per `serverId` (`4029`) without replacing a live control socket.
|
|
121
|
+
- Concurrent service starts recheck the endpoint's active Host after awaiting identity, so each endpoint has one managed Host and stopping the service closes every started Host.
|
|
120
122
|
- Tunneled HTTP/WS always dial `getLocalPort()` on loopback; there is no client-selected target-port override on the host dispatcher.
|
|
121
123
|
|
|
122
124
|
For the operational rules that keep future changes (new WebSocket endpoints, transport refactors, terminal/voice porting) from breaking this, load the `relay-transport` skill.
|
|
@@ -313,6 +313,7 @@ export const createRelayService = ({
|
|
|
313
313
|
}
|
|
314
314
|
const identity = await identityRuntime.getRelayIdentity();
|
|
315
315
|
for (const relayUrl of pending) {
|
|
316
|
+
if (hostClients.has(relayUrl)) continue;
|
|
316
317
|
const hostClient = startRelayHost({
|
|
317
318
|
relayUrl,
|
|
318
319
|
identity,
|
|
@@ -123,6 +123,19 @@ describe('relay service pairing endpoint', () => {
|
|
|
123
123
|
delete process.env.OPENCHAMBER_RELAY_URL;
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
+
it('starts only one host per endpoint during concurrent startup and stops every started host', async () => {
|
|
127
|
+
const { service, settings } = createTestRelayService({ hasRelayDemand: async () => true });
|
|
128
|
+
await settings.write({ privateRelay: { enabled: true, relayUrl: DEFAULT_RELAY_URL } });
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await Promise.all([service.reconcile(), service.reconcile()]);
|
|
132
|
+
expect(relayMocks.starts).toEqual([DEFAULT_RELAY_URL]);
|
|
133
|
+
} finally {
|
|
134
|
+
service.stop();
|
|
135
|
+
}
|
|
136
|
+
expect(relayMocks.stops).toEqual(relayMocks.starts);
|
|
137
|
+
});
|
|
138
|
+
|
|
126
139
|
it('appends a new pairing endpoint (multi-relay) while keeping the primary', async () => {
|
|
127
140
|
const { service, settings } = createTestRelayService();
|
|
128
141
|
|
|
@@ -8,6 +8,7 @@ Server-owned scheduled task runtime and routes for OpenChamber-only automation.
|
|
|
8
8
|
- Runtime orchestration and execution is owned by this module.
|
|
9
9
|
- Durable run history is owned by an independent SQLite store in this module.
|
|
10
10
|
- This module is OpenChamber feature logic; it is intentionally separate from OpenCode proxy/runtime internals.
|
|
11
|
+
- Prompt and goal execution preserve hash-prefixed text literally; no snippet discovery or expansion occurs before dispatch.
|
|
11
12
|
|
|
12
13
|
## Files
|
|
13
14
|
|
|
@@ -215,7 +216,13 @@ Every actual run (timer or manual):
|
|
|
215
216
|
`message.list({ limit: 50, order: 'desc' })` (newest first) — never
|
|
216
217
|
`order: 'asc'` page-0 + `at(-1)`, which reads an old page once the
|
|
217
218
|
session exceeds the limit.
|
|
218
|
-
|
|
219
|
+
- OpenCode 2.0.16 emits a native `idle` tail whose `outcome` is authoritative:
|
|
220
|
+
`succeeded` → success; `failed` / `interrupted` → error. An unknown idle
|
|
221
|
+
outcome keeps polling. Read it only after `session.active` confirms the
|
|
222
|
+
session is inactive; an older terminal record cannot settle live work.
|
|
223
|
+
This also applies to post-run continuation snapshots. Earlier servers
|
|
224
|
+
without idle records use the assistant-tail rules below.
|
|
225
|
+
- Assistant-tail terminal success requires `time.completed`
|
|
219
226
|
and finish not `tool-calls` (prefer `finish: 'stop'`). `finish:
|
|
220
227
|
'tool-calls'`, incomplete assistants (no `time.completed`), and
|
|
221
228
|
non-assistant tails such as `model-switched` never succeed — including
|
|
@@ -2,7 +2,6 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import { DateTime } from 'luxon';
|
|
3
3
|
import parser from 'cron-parser';
|
|
4
4
|
import { makeOpenCodeV2Client } from '../opencode/v2-client.js';
|
|
5
|
-
import { expandSnippets } from '../opencode/snippets.js';
|
|
6
5
|
import {
|
|
7
6
|
getSessionGoalCapability,
|
|
8
7
|
isSessionGoalSupported,
|
|
@@ -304,7 +303,7 @@ const isUserMessage = (info) => (
|
|
|
304
303
|
* - finish tool-calls is mid-loop, never success
|
|
305
304
|
* - incomplete (no time.completed) is never success
|
|
306
305
|
* - model-switched / non-assistant tails are not success
|
|
307
|
-
* -
|
|
306
|
+
* - native idle outcomes settle the execution; older servers use assistant finish
|
|
308
307
|
*/
|
|
309
308
|
const classifyMessageTail = (messages) => {
|
|
310
309
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
@@ -317,6 +316,15 @@ const classifyMessageTail = (messages) => {
|
|
|
317
316
|
if (!latest) {
|
|
318
317
|
return { outcome: 'unknown' };
|
|
319
318
|
}
|
|
319
|
+
if (latest.type === 'idle') {
|
|
320
|
+
if (latest.outcome === 'succeeded') {
|
|
321
|
+
return { outcome: 'success' };
|
|
322
|
+
}
|
|
323
|
+
if (latest.outcome === 'failed' || latest.outcome === 'interrupted') {
|
|
324
|
+
return { outcome: 'error', error: `session execution ${latest.outcome}` };
|
|
325
|
+
}
|
|
326
|
+
return { outcome: 'unknown' };
|
|
327
|
+
}
|
|
320
328
|
if (isAssistantMessage(latest)) {
|
|
321
329
|
if (latest.error) {
|
|
322
330
|
return {
|
|
@@ -849,7 +857,7 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
849
857
|
};
|
|
850
858
|
|
|
851
859
|
const buildPromptText = (task, projectPath) => {
|
|
852
|
-
const promptText =
|
|
860
|
+
const promptText = task.execution.prompt;
|
|
853
861
|
if (!task.execution.goalEnabled) {
|
|
854
862
|
return promptText;
|
|
855
863
|
}
|
|
@@ -874,7 +882,7 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
874
882
|
// back to an inline objective so parseGoalMetadata still accepts the
|
|
875
883
|
// record. Oversized prompts are distilled into audit criteria by the
|
|
876
884
|
// small model; on distill failure a head+tail excerpt keeps intent.
|
|
877
|
-
let objectiveText =
|
|
885
|
+
let objectiveText = task.execution.prompt;
|
|
878
886
|
if (objectiveText.length > 5000) {
|
|
879
887
|
let distilled = null;
|
|
880
888
|
try {
|