ompchamber 1.2.1 → 2.0.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 +143 -147
- package/dist/client/index.html +4 -2
- package/dist/client/static/css/index.5f630421b7.css +2 -0
- package/dist/client/static/js/77.0d81c74cf0.js +139 -0
- package/dist/client/static/js/async/1k.973e8e21fc.js +2 -0
- package/dist/client/static/js/async/{3y.3186ecdd63.js → 3y.3530b824ad.js} +6 -6
- package/dist/client/static/js/async/{q.b453193538.js → 4m.928a1dde85.js} +10 -10
- package/dist/client/static/js/async/66.5dfa29f957.js +93 -0
- package/dist/client/static/js/async/7g.a7814350c3.js +1 -0
- package/dist/client/static/js/async/ar.9303e5f2e8.js +2 -0
- package/dist/client/static/js/async/{cx.9c8268d542.js → cx.3cb0242a3a.js} +1 -1
- package/dist/client/static/js/async/e.93214d622b.js +79 -0
- package/dist/client/static/js/async/g.cb81980498.js +2 -0
- package/dist/client/static/js/async/h1.393f066d09.js +1 -0
- package/dist/client/static/js/async/jl.874d352278.js +1 -0
- package/dist/client/static/js/async/{k.d16cd09ae7.js → k.3210286401.js} +1 -1
- package/dist/client/static/js/async/{m.2e1ea3db9e.js → m.eb2e6752e2.js} +3 -3
- package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/mt.0352e976d8.js +1 -0
- package/dist/client/static/js/async/{oc.eb22093f05.js → oc.b73042e8bd.js} +1 -1
- package/dist/client/static/js/async/{og.53f068804c.js → og.8eb577893a.js} +1 -1
- package/dist/client/static/js/async/px.fc6877924b.js +1 -0
- package/dist/client/static/js/async/q.22588845a9.js +21 -0
- package/dist/client/static/js/async/{q5.88b50ffa95.js → q5.97dc1481b6.js} +1 -1
- package/dist/client/static/js/async/s.0f395cdda5.js +94 -0
- package/dist/client/static/js/async/{tr.2a0d2b7600.js → tr.e878658140.js} +1 -1
- package/dist/client/static/js/async/vt.42416b707d.js +1 -0
- package/dist/client/static/js/async/x1.da8687159b.js +90 -0
- package/dist/client/static/js/async/xe.da8dcc2015.js +2 -0
- package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{xn.6360379f71.js → xn.6510c0abe2.js} +2 -2
- package/dist/client/static/js/async/{yx.af2936c368.js → xn1.83a7784fe1.js} +3 -3
- package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/yg.35bebd6d0c.js +149 -0
- package/dist/client/static/js/async/yv.d99d829136.js +1 -0
- package/dist/client/static/js/index.1fd9e1c543.js +38 -0
- package/package.json +3 -3
- package/src/client/components/common/ComposerTextarea.tsx +9 -1
- package/src/client/components/common/MarkdownRenderer.tsx +50 -12
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +9 -3
- package/src/client/components/settings/Modal.tsx +11 -2
- package/src/client/components/settings/categories/AppearanceSettings.tsx +7 -30
- package/src/client/components/settings/categories/ProviderSettings.tsx +2 -0
- package/src/client/components/settings/categories/appearance-settings/ThemeCard.tsx +61 -0
- package/src/client/components/settings/categories/appearance-settings/ThemeGrid.tsx +80 -0
- package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +1 -1
- package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +43 -6
- package/src/client/components/settings/categories/provider-settings/ModelConfigModal.tsx +54 -54
- package/src/client/components/workspace/SearchPanel.tsx +16 -3
- package/src/client/components/workspace/btw-panel/AskCard.tsx +11 -6
- package/src/client/components/workspace/btw-panel/Messages.tsx +105 -51
- package/src/client/components/workspace/btw-panel/index.tsx +124 -45
- package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +30 -14
- package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +10 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +8 -2
- package/src/client/components/workspace/chat-timeline/MessageItem.tsx +27 -16
- package/src/client/components/workspace/chat-timeline/MessageList.tsx +11 -0
- package/src/client/components/workspace/chat-timeline/RunFooter.tsx +8 -0
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +12 -1
- package/src/client/components/workspace/chat-timeline/index.tsx +7 -2
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +42 -15
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +13 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index.tsx +13 -4
- package/src/client/components/workspace/diff-panel/SplitView.tsx +4 -1
- package/src/client/components/workspace/diff-panel/UnifiedView.tsx +70 -65
- package/src/client/components/workspace/diff-panel/index.tsx +8 -1
- package/src/client/components/workspace/editor/index.tsx +18 -2
- package/src/client/components/workspace/file-explorer/GitRepoDropdown.tsx +15 -41
- package/src/client/components/workspace/file-explorer/index.tsx +86 -16
- package/src/client/components/workspace/git-panel/index.tsx +60 -78
- package/src/client/components/workspace/terminal-panel/Header.tsx +16 -1
- package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +5 -5
- package/src/client/components/workspace/terminal-panel/index.tsx +6 -2
- package/src/client/components/workspace/terminal-panel/mount.ts +2 -2
- package/src/client/data/theme/terminal.ts +50 -52
- package/src/client/hooks/chat/btw/index.ts +139 -18
- package/src/client/hooks/chat/btw/intercept.ts +8 -8
- package/src/client/hooks/chat/btw/mode.ts +111 -16
- package/src/client/hooks/chat/composer/index.ts +10 -4
- package/src/client/hooks/chat/timeline/access-mode.ts +41 -0
- package/src/client/hooks/chat/timeline/ask-frames.test.ts +87 -0
- package/src/client/hooks/chat/timeline/ask-frames.ts +20 -1
- package/src/client/hooks/chat/timeline/auto-scroll.ts +13 -4
- package/src/client/hooks/chat/timeline/index.ts +8 -20
- package/src/client/hooks/chat/timeline/queue.ts +66 -9
- package/src/client/hooks/chat/timeline/scroll.test.ts +260 -0
- package/src/client/hooks/chat/timeline/scroll.ts +93 -20
- package/src/client/hooks/chat/timeline/session-load.ts +7 -1
- package/src/client/hooks/chat/timeline/session-pagination.test.ts +167 -0
- package/src/client/hooks/chat/timeline/session-pagination.ts +37 -11
- package/src/client/hooks/editor/save-file.ts +62 -0
- package/src/client/hooks/editor/use-file-editor.test.ts +61 -5
- package/src/client/hooks/editor/use-file-editor.ts +38 -29
- package/src/client/hooks/editor/use-file-transfer.ts +5 -2
- package/src/client/hooks/settings/providers.ts +34 -3
- package/src/client/hooks/ui/open-file.ts +7 -1
- package/src/client/hooks/ui/theme.ts +82 -57
- package/src/client/hooks/workspace/file-tabs.ts +5 -1
- package/src/client/hooks/workspace/repo-scope/index.ts +119 -0
- package/src/client/hooks/workspace/repo-scope/store.test.ts +161 -0
- package/src/client/hooks/workspace/repo-scope/store.ts +201 -0
- package/src/client/hooks/workspace/search-stream.ts +24 -5
- package/src/client/main.tsx +6 -0
- package/src/client/tailwind.css +19 -56
- package/src/server/lib/btw/child.server.ts +41 -0
- package/src/server/lib/btw/lifecycle.server.ts +254 -0
- package/src/server/lib/btw/prompt.ts +13 -5
- package/src/server/lib/btw/registry.server.ts +13 -4
- package/src/server/lib/btw/runtime.server.ts +149 -156
- package/src/server/lib/btw/schema.server.ts +27 -0
- package/src/server/lib/btw/service.server.ts +106 -5
- package/src/server/lib/btw/store.server.ts +57 -6
- package/src/server/lib/db/schema.ts +4 -0
- package/src/server/lib/db/settings-store.test.ts +65 -0
- package/src/server/lib/db/settings-store.ts +25 -0
- package/src/server/lib/fs/atomic-write.ts +42 -4
- package/src/server/lib/fs/config-lock.ts +74 -0
- package/src/server/lib/fs/git-ignore.ts +55 -5
- package/src/server/lib/fs/ripgrep.ts +119 -0
- package/src/server/lib/lifecycle/identity.ts +10 -64
- package/src/server/lib/lifecycle/proc/darwin.ts +238 -0
- package/src/server/lib/lifecycle/proc/index.ts +38 -0
- package/src/server/lib/lifecycle/proc/linux.ts +80 -0
- package/src/server/lib/lifecycle/proc/ps.ts +86 -0
- package/src/server/lib/lifecycle/proc/types.ts +33 -0
- package/src/server/lib/lifecycle/proc/win32.ts +53 -0
- package/src/server/lib/models/provider-registry.server.ts +2 -2
- package/src/server/lib/models/registry.server.ts +1 -1
- package/src/server/lib/omp/config/behavior.ts +17 -17
- package/src/server/lib/omp/config/disabled-providers.ts +84 -35
- package/src/server/lib/omp/config/document.ts +128 -0
- package/src/server/lib/omp/config/extensions.ts +11 -10
- package/src/server/lib/omp/config/mcp.ts +4 -57
- package/src/server/lib/omp/config/model-overrides.ts +123 -0
- package/src/server/lib/omp/config/models-config.ts +92 -0
- package/src/server/lib/omp/config/models-validation.ts +159 -0
- package/src/server/lib/omp/config/path-scoped.ts +74 -0
- package/src/server/lib/omp/config/providers.ts +241 -211
- package/src/server/lib/omp/config/roles.ts +6 -14
- package/src/server/lib/omp/core/auth-credentials.ts +1 -1
- package/src/server/lib/omp/rpc/frame-fold.test.ts +124 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +43 -24
- package/src/server/lib/omp/rpc/lifecycle.test.ts +41 -0
- package/src/server/lib/omp/rpc/manager.ts +21 -21
- package/src/server/lib/omp/rpc/pending-ui-dialogs.ts +11 -7
- package/src/server/lib/omp/rpc/session-commands.ts +17 -29
- package/src/server/lib/omp/rpc/session-registry.ts +5 -66
- package/src/server/lib/omp/rpc/web-state.ts +0 -6
- package/src/server/lib/omp/session/auto-title.server.test.ts +109 -11
- package/src/server/lib/omp/session/auto-title.server.ts +95 -20
- package/src/server/lib/omp/session/sidebar-data.server.ts +11 -6
- package/src/server/lib/queue/delivery-dispatch.server.test.ts +162 -0
- package/src/server/lib/queue/delivery-harness.ts +88 -0
- package/src/server/lib/queue/delivery.server.test.ts +78 -149
- package/src/server/lib/queue/delivery.server.ts +105 -13
- package/src/server/lib/queue/store.server.ts +30 -5
- package/src/server/lib/terminal/foreground.test.ts +164 -0
- package/src/server/lib/terminal/foreground.ts +34 -6
- package/src/server/lib/terminal/history.ts +8 -7
- package/src/server/plugins/compress.ts +33 -5
- package/src/server/plugins/ssr.ts +14 -7
- package/src/server/routes/btw/topics.ts +89 -2
- package/src/server/routes/fs/action.test.ts +64 -0
- package/src/server/routes/fs/action.ts +10 -1
- package/src/server/routes/fs/git.ts +5 -6
- package/src/server/routes/fs/search.ts +17 -26
- package/src/server/routes/sessions/queue.ts +17 -4
- package/src/server/routes/settings/index.ts +2 -0
- package/src/server/routes/settings/model-override.ts +69 -0
- package/src/server/routes/settings/provider-models.ts +35 -12
- package/src/server/routes/settings/providers.ts +31 -1
- package/src/server/routes/settings/root.ts +8 -3
- package/src/shared/lib/chat/btw/client.ts +34 -0
- package/src/shared/lib/chat/timeline/tool-phrases.ts +7 -0
- package/src/shared/lib/code/line-endings.test.ts +38 -0
- package/src/shared/lib/code/line-endings.ts +56 -0
- package/src/shared/lib/code/shiki-themes.ts +9 -5
- package/src/shared/lib/fs/file-tree.ts +17 -12
- package/src/shared/lib/markdown/autolink.test.ts +32 -0
- package/src/shared/lib/markdown/autolink.ts +50 -0
- package/src/shared/lib/markdown/document-urls.test.ts +68 -0
- package/src/shared/lib/markdown/document-urls.ts +118 -0
- package/src/shared/lib/markdown/marked.ts +27 -15
- package/src/shared/lib/markdown/mermaid.ts +2 -2
- package/src/shared/lib/models/catalog.ts +1 -1
- package/src/shared/lib/omp/session/schema-migrations.ts +30 -0
- package/src/shared/lib/omp/session/stream-state.server.test.ts +54 -0
- package/src/shared/lib/omp/session/stream-state.server.ts +70 -21
- package/src/shared/lib/settings/client.ts +10 -4
- package/src/shared/lib/settings/diff.test.ts +31 -0
- package/src/shared/lib/settings/diff.ts +37 -0
- package/src/shared/lib/theme/catalog.test.ts +168 -0
- package/src/shared/lib/theme/catalog.ts +98 -0
- package/src/shared/lib/theme/color.ts +88 -0
- package/src/shared/lib/theme/css.ts +70 -0
- package/src/shared/lib/theme/palettes/chamber.ts +61 -0
- package/src/shared/lib/theme/palettes/dark.ts +313 -0
- package/src/shared/lib/theme/palettes/light.ts +313 -0
- package/src/shared/types/btw.ts +83 -4
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/settings/provider.ts +8 -1
- package/src/shared/types/settings/state.ts +10 -4
- package/src/shared/types/theme.ts +48 -0
- package/dist/client/static/css/index.b938f4a693.css +0 -2
- package/dist/client/static/js/77.8022a800b5.js +0 -139
- package/dist/client/static/js/async/1k.a411fe5b43.js +0 -2
- package/dist/client/static/js/async/66.86544ad741.js +0 -93
- package/dist/client/static/js/async/7g.db82aca7ba.js +0 -1
- package/dist/client/static/js/async/ar.ad5c8822a8.js +0 -2
- package/dist/client/static/js/async/e.6443e739a2.js +0 -79
- package/dist/client/static/js/async/g.ee41bfed7f.js +0 -2
- package/dist/client/static/js/async/h1.d7091cef78.js +0 -1
- package/dist/client/static/js/async/jl.4911fa3383.js +0 -1
- package/dist/client/static/js/async/m.2e1ea3db9e.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/mt.853be2b5aa.js +0 -1
- package/dist/client/static/js/async/ot.84c71b308f.js +0 -2
- package/dist/client/static/js/async/px.783b6f91f2.js +0 -1
- package/dist/client/static/js/async/q8.7f1b48e9cd.js +0 -21
- package/dist/client/static/js/async/q8.7f1b48e9cd.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/s.aad2ecebc6.js +0 -94
- package/dist/client/static/js/async/vt.20406a5604.js +0 -1
- package/dist/client/static/js/async/x1.fe8af17016.js +0 -90
- package/dist/client/static/js/async/xn.6360379f71.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/yg.5a94e1fc8a.js +0 -149
- package/dist/client/static/js/async/yv.2fa22f009a.js +0 -1
- package/dist/client/static/js/index.5fe7a2cb64.js +0 -34
- package/src/client/components/workspace/btw-panel/Composer.tsx +0 -183
- package/src/client/components/workspace/btw-panel/attachments.ts +0 -80
- /package/dist/client/static/js/{77.8022a800b5.js.LICENSE.txt → 77.0d81c74cf0.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.ee41bfed7f.js.LICENSE.txt → 1k.973e8e21fc.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.3186ecdd63.js.LICENSE.txt → 3y.3530b824ad.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ot.84c71b308f.js.LICENSE.txt → 4m.928a1dde85.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.b453193538.js.LICENSE.txt → ar.9303e5f2e8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{yx.af2936c368.js.LICENSE.txt → g.cb81980498.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{1k.a411fe5b43.js.LICENSE.txt → q.22588845a9.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ar.ad5c8822a8.js.LICENSE.txt → xn.6510c0abe2.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.5fe7a2cb64.js.LICENSE.txt → index.1fd9e1c543.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Nested-repo discovery store, keyed by workspace root.
|
|
8
|
+
*
|
|
9
|
+
* The leak this module exists to prevent: a repo is a path RELATIVE to a root
|
|
10
|
+
* (`projects/jns6_5/token` only names a directory under `~/JatisMobile/Workspace`),
|
|
11
|
+
* so a list discovered for one workspace is meaningless under another. Keying
|
|
12
|
+
* every entry by the root it was discovered for makes that structural — a new
|
|
13
|
+
* root has no entry and starts from {@link UNKNOWN_LIST} instead of inheriting
|
|
14
|
+
* the previous list.
|
|
15
|
+
*
|
|
16
|
+
* A store rather than a fetch per hook: the right-panel views ask the same
|
|
17
|
+
* question about the same root, and independent copies meant one request per
|
|
18
|
+
* panel per poll interval plus lists that could disagree.
|
|
19
|
+
*
|
|
20
|
+
* Both platform dependencies are injected — `fetch` and the retry scheduler —
|
|
21
|
+
* so the invalidation rules are testable without a DOM and without wall-clock
|
|
22
|
+
* timers.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { REPO_DISCOVERY_POLL_MS } from '@/shared/lib/workspace/refresh-cadence';
|
|
26
|
+
import { isRecord } from '@/shared/lib/util/guards';
|
|
27
|
+
|
|
28
|
+
export interface RepoListSnapshot {
|
|
29
|
+
repos: string[];
|
|
30
|
+
/** Discovery is still running server-side, or a forced rescan is. */
|
|
31
|
+
scanning: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The list before discovery has answered: the workspace root alone. */
|
|
35
|
+
export const UNKNOWN_LIST: RepoListSnapshot = { repos: ['.'], scanning: false };
|
|
36
|
+
|
|
37
|
+
/** `fetch` seam: resolves the parsed `?reposOnly=1` payload, rejects on error. */
|
|
38
|
+
export type RepoFetch = (url: string) => Promise<unknown>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retry seam. Runs `run` once after `delayMs` and returns a cancel; the default
|
|
42
|
+
* is a one-shot timer, and the caller re-arms it per attempt.
|
|
43
|
+
*/
|
|
44
|
+
export type RepoScheduler = (run: () => void, delayMs: number) => () => void;
|
|
45
|
+
|
|
46
|
+
interface RootEntry {
|
|
47
|
+
snapshot: RepoListSnapshot;
|
|
48
|
+
/** True once a discovery response was accepted for this root. Drives the
|
|
49
|
+
* "start discovery on first subscriber" decision — object identity of
|
|
50
|
+
* `snapshot` cannot, because clearing the spinner mints a new one. */
|
|
51
|
+
loaded: boolean;
|
|
52
|
+
listeners: Set<() => void>;
|
|
53
|
+
controller: AbortController | null;
|
|
54
|
+
cancelRetry: (() => void) | null;
|
|
55
|
+
/** The discovery currently in flight, so a caller can await its settlement. */
|
|
56
|
+
pending: Promise<void> | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface RepoStore {
|
|
60
|
+
/** Repos discovered for `root`; {@link UNKNOWN_LIST} until one is. */
|
|
61
|
+
snapshot: (root: string) => RepoListSnapshot;
|
|
62
|
+
/** Await the discovery in flight for `root` (a no-op when there is none). */
|
|
63
|
+
settled: (root: string) => Promise<void>;
|
|
64
|
+
/** Observe `root`; the first subscriber starts discovery. Returns a cancel. */
|
|
65
|
+
subscribe: (root: string, listener: () => void) => () => void;
|
|
66
|
+
/** Re-run discovery for `root` from scratch, ignoring any cached result. */
|
|
67
|
+
rescan: (root: string) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface RepoStoreOptions {
|
|
71
|
+
fetch: RepoFetch;
|
|
72
|
+
scheduler?: RepoScheduler;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const defaultScheduler: RepoScheduler = (run, delayMs) => {
|
|
76
|
+
const id = setTimeout(run, delayMs);
|
|
77
|
+
return () => clearTimeout(id);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export function createRepoStore({ fetch: fetchImpl, scheduler = defaultScheduler }: RepoStoreOptions): RepoStore {
|
|
81
|
+
// Per-store, not module-level: two stores must not share state, and a test
|
|
82
|
+
// store must not see the app store's roots.
|
|
83
|
+
const entries = new Map<string, RootEntry>();
|
|
84
|
+
|
|
85
|
+
function entryFor(root: string): RootEntry {
|
|
86
|
+
let entry = entries.get(root);
|
|
87
|
+
if (!entry) {
|
|
88
|
+
entry = {
|
|
89
|
+
snapshot: UNKNOWN_LIST,
|
|
90
|
+
loaded: false,
|
|
91
|
+
listeners: new Set(),
|
|
92
|
+
controller: null,
|
|
93
|
+
cancelRetry: null,
|
|
94
|
+
pending: null,
|
|
95
|
+
};
|
|
96
|
+
entries.set(root, entry);
|
|
97
|
+
}
|
|
98
|
+
return entry;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function publish(root: string, patch: Partial<RepoListSnapshot>): void {
|
|
102
|
+
const entry = entries.get(root);
|
|
103
|
+
if (!entry) return;
|
|
104
|
+
const next = { ...entry.snapshot, ...patch };
|
|
105
|
+
if (next.repos === entry.snapshot.repos && next.scanning === entry.snapshot.scanning) return;
|
|
106
|
+
entry.snapshot = next;
|
|
107
|
+
for (const listener of entry.listeners) listener();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function cancelRetry(entry: RootEntry): void {
|
|
111
|
+
entry.cancelRetry?.();
|
|
112
|
+
entry.cancelRetry = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function load(root: string, rescan: boolean): Promise<void> {
|
|
116
|
+
const entry = entryFor(root);
|
|
117
|
+
// A newer load supersedes this one: its response must not overwrite what
|
|
118
|
+
// the newer request is already about to replace.
|
|
119
|
+
entry.controller?.abort();
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
entry.controller = controller;
|
|
122
|
+
publish(root, { scanning: true });
|
|
123
|
+
|
|
124
|
+
const params = new URLSearchParams({ reposOnly: '1' });
|
|
125
|
+
if (root) params.set('root', root);
|
|
126
|
+
if (rescan) params.set('rescan', '1');
|
|
127
|
+
params.set('t', String(Date.now()));
|
|
128
|
+
|
|
129
|
+
const pending = (async () => {
|
|
130
|
+
const data = await fetchImpl(`/api/fs/git?${params.toString()}`).catch(() => null);
|
|
131
|
+
if (entry.controller !== controller) return;
|
|
132
|
+
entry.controller = null;
|
|
133
|
+
|
|
134
|
+
const repos = isRecord(data) && Array.isArray(data.repos) ? data.repos : null;
|
|
135
|
+
if (!repos) {
|
|
136
|
+
// A failed read leaves the snapshot as it was — for a root nobody has
|
|
137
|
+
// successfully listed yet that is still `UNKNOWN_LIST`, so the next
|
|
138
|
+
// subscriber retries instead of sitting on an empty list.
|
|
139
|
+
publish(root, { scanning: false });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const scanning = isRecord(data) && data.reposPending === true;
|
|
143
|
+
entry.loaded = true;
|
|
144
|
+
publish(root, { repos: repos.filter((r): r is string => typeof r === 'string'), scanning });
|
|
145
|
+
|
|
146
|
+
// Discovery runs in the background server-side — the loader returns at
|
|
147
|
+
// once with `reposPending` — so retry until it settles.
|
|
148
|
+
if (scanning) {
|
|
149
|
+
cancelRetry(entry);
|
|
150
|
+
entry.cancelRetry = scheduler(() => {
|
|
151
|
+
entry.cancelRetry = null;
|
|
152
|
+
if (entry.listeners.size === 0) return;
|
|
153
|
+
void load(root, false);
|
|
154
|
+
}, REPO_DISCOVERY_POLL_MS);
|
|
155
|
+
} else {
|
|
156
|
+
cancelRetry(entry);
|
|
157
|
+
}
|
|
158
|
+
})();
|
|
159
|
+
|
|
160
|
+
entry.pending = pending;
|
|
161
|
+
return pending;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function subscribe(root: string, listener: () => void): () => void {
|
|
165
|
+
const entry = entryFor(root);
|
|
166
|
+
entry.listeners.add(listener);
|
|
167
|
+
// First subscriber for this root — or the first since everyone left —
|
|
168
|
+
// starts (or resumes) discovery, unless a settled list is already known for
|
|
169
|
+
// this same root: a right-panel view switch must not re-run the server's
|
|
170
|
+
// `find`.
|
|
171
|
+
if (entry.controller === null && entry.cancelRetry === null && (!entry.loaded || entry.snapshot.scanning)) {
|
|
172
|
+
void load(root, false);
|
|
173
|
+
}
|
|
174
|
+
return () => {
|
|
175
|
+
entry.listeners.delete(listener);
|
|
176
|
+
if (entry.listeners.size > 0) return;
|
|
177
|
+
// Nobody is showing this root's list any more: abandon the read in flight
|
|
178
|
+
// and drop the retry. The snapshot keeps its `scanning` flag, so the next
|
|
179
|
+
// subscriber resumes discovery rather than inheriting a spinner that
|
|
180
|
+
// nothing is driving.
|
|
181
|
+
entry.controller?.abort();
|
|
182
|
+
entry.controller = null;
|
|
183
|
+
cancelRetry(entry);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
snapshot: (root) => entries.get(root)?.snapshot ?? UNKNOWN_LIST,
|
|
189
|
+
settled: (root) => entries.get(root)?.pending ?? Promise.resolve(),
|
|
190
|
+
subscribe,
|
|
191
|
+
rescan: (root) => void load(root, true),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** The app's single store, over the browser's `fetch` and real timers. */
|
|
196
|
+
export const repoStore = createRepoStore({
|
|
197
|
+
fetch: async (url) => {
|
|
198
|
+
const response = await fetch(url);
|
|
199
|
+
return response.json();
|
|
200
|
+
},
|
|
201
|
+
});
|
|
@@ -16,14 +16,21 @@ interface SearchStreamState {
|
|
|
16
16
|
* encoding the old `useFetcher.submit` used) and the response is consumed
|
|
17
17
|
* incrementally via the shared SSE reader. A new `start` call aborts the
|
|
18
18
|
* previous run — matching the fetcher semantics the panel relied on.
|
|
19
|
+
*
|
|
20
|
+
* Results carry the `scope` they were searched under (the workspace root plus
|
|
21
|
+
* the selected repo). A workspace switch therefore shows no hits at all rather
|
|
22
|
+
* than the previous workspace's — a match's path is relative to the root it was
|
|
23
|
+
* found under, so the same string names a different file, or none, here.
|
|
19
24
|
*/
|
|
20
|
-
export function useSearchStream(): SearchStreamState & {
|
|
25
|
+
export function useSearchStream(scope: string): SearchStreamState & {
|
|
21
26
|
start: (body: Record<string, string>) => Promise<void>;
|
|
22
27
|
} {
|
|
23
|
-
const [
|
|
28
|
+
const [state, setState] = useState<{ scope: string; results: SearchResultItem[] }>({ scope, results: [] });
|
|
24
29
|
const [isSearching, setIsSearching] = useState(false);
|
|
25
30
|
const abortRef = useRef<AbortController | null>(null);
|
|
26
31
|
const aliveRef = useRef(true);
|
|
32
|
+
const scopeRef = useRef(scope);
|
|
33
|
+
scopeRef.current = scope;
|
|
27
34
|
|
|
28
35
|
useEffect(() => {
|
|
29
36
|
aliveRef.current = true;
|
|
@@ -38,8 +45,9 @@ export function useSearchStream(): SearchStreamState & {
|
|
|
38
45
|
abortRef.current?.abort();
|
|
39
46
|
const controller = new AbortController();
|
|
40
47
|
abortRef.current = controller;
|
|
48
|
+
const requested = scopeRef.current;
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
setState({ scope: requested, results: [] });
|
|
43
51
|
setIsSearching(true);
|
|
44
52
|
try {
|
|
45
53
|
const response = await fetch('/api/fs/search', {
|
|
@@ -53,9 +61,15 @@ export function useSearchStream(): SearchStreamState & {
|
|
|
53
61
|
response,
|
|
54
62
|
(event, data) => {
|
|
55
63
|
if (event !== 'matches') return;
|
|
64
|
+
// A run superseded by a workspace switch is no longer this panel's.
|
|
65
|
+
if (scopeRef.current !== requested) return;
|
|
56
66
|
try {
|
|
57
67
|
const parsed = JSON.parse(data);
|
|
58
|
-
if (Array.isArray(parsed))
|
|
68
|
+
if (Array.isArray(parsed)) {
|
|
69
|
+
setState(prev => prev.scope === requested
|
|
70
|
+
? { scope: requested, results: [...prev.results, ...parsed] }
|
|
71
|
+
: prev);
|
|
72
|
+
}
|
|
59
73
|
} catch {
|
|
60
74
|
// Malformed frame — skip; the next frame carries its own payload.
|
|
61
75
|
}
|
|
@@ -72,5 +86,10 @@ export function useSearchStream(): SearchStreamState & {
|
|
|
72
86
|
}
|
|
73
87
|
}, []);
|
|
74
88
|
|
|
75
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
results: state.scope === scope ? state.results : [],
|
|
91
|
+
// A run in flight for another scope is not searching THIS one.
|
|
92
|
+
isSearching: isSearching && state.scope === scope,
|
|
93
|
+
start,
|
|
94
|
+
};
|
|
76
95
|
}
|
package/src/client/main.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import { hydrate } from 'preact';
|
|
|
2
2
|
import { App } from '@/client/App';
|
|
3
3
|
import { bootSyntax } from '@/shared/lib/code/highlighter';
|
|
4
4
|
import { primeChamberSettings } from '@/shared/lib/settings/client';
|
|
5
|
+
import { initDocumentTheme } from '@/client/hooks/ui/theme';
|
|
5
6
|
|
|
6
7
|
import '@/client/tailwind.css';
|
|
7
8
|
import '@/shared/lib/markdown/katex-fonts.css';
|
|
@@ -12,6 +13,11 @@ const bootstrap = window.__OMP_BOOTSTRAP__;
|
|
|
12
13
|
|
|
13
14
|
primeChamberSettings(bootstrap?.appSettings);
|
|
14
15
|
|
|
16
|
+
// The catalog stylesheet and both theme attributes, before the first render:
|
|
17
|
+
// the server writes them into the shell, so this only repairs a document served
|
|
18
|
+
// by an older build.
|
|
19
|
+
initDocumentTheme();
|
|
20
|
+
|
|
15
21
|
// Fire-and-forget: warms the Shiki highlighter in parallel with hydration.
|
|
16
22
|
bootSyntax();
|
|
17
23
|
|
package/src/client/tailwind.css
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@import "@xterm/xterm/css/xterm.css";
|
|
3
|
-
@plugin "@tailwindcss/typography";
|
|
4
3
|
|
|
5
4
|
@theme {
|
|
6
5
|
--color-ink: var(--theme-ink);
|
|
@@ -8,8 +7,6 @@
|
|
|
8
7
|
--color-canvas: var(--theme-canvas);
|
|
9
8
|
--color-error: var(--theme-error);
|
|
10
9
|
--color-success: var(--theme-success);
|
|
11
|
-
--color-success-bg: var(--theme-success-bg);
|
|
12
|
-
--color-success-border: var(--theme-success-border);
|
|
13
10
|
--color-meta: var(--theme-meta);
|
|
14
11
|
--color-info: var(--theme-info);
|
|
15
12
|
--color-info-hover: var(--theme-info-hover);
|
|
@@ -18,19 +15,23 @@
|
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
@layer base {
|
|
18
|
+
/* The default palette. Every other theme's block is generated from the
|
|
19
|
+
* catalog in `src/shared/lib/theme/palettes/` and injected as
|
|
20
|
+
* `html[data-theme=…]` by the SSR shell (see `shared/lib/theme/css.ts`) —
|
|
21
|
+
* hand-writing them here would duplicate the same eight values the appearance
|
|
22
|
+
* cards render as chips. */
|
|
21
23
|
:root {
|
|
22
24
|
--theme-paper: #faf8f3;
|
|
23
25
|
--theme-canvas: #f4f1ea;
|
|
24
26
|
--theme-ink: #141310;
|
|
25
27
|
--theme-error: #c8321e;
|
|
26
28
|
--theme-success: #047857;
|
|
27
|
-
--theme-success-bg: #ecfdf5;
|
|
28
|
-
--theme-success-border: #a7f3d0;
|
|
29
29
|
--theme-meta: #4f46e5;
|
|
30
30
|
--theme-info: #0284c7;
|
|
31
|
-
--theme-info-hover:
|
|
31
|
+
--theme-info-hover: color-mix(in srgb, var(--theme-info) 85%, var(--theme-ink));
|
|
32
32
|
--theme-warning: #c2410c;
|
|
33
|
-
--theme-warning-hover:
|
|
33
|
+
--theme-warning-hover: color-mix(in srgb, var(--theme-warning) 85%, var(--theme-ink));
|
|
34
|
+
color-scheme: light;
|
|
34
35
|
|
|
35
36
|
--paper-bg: var(--theme-canvas);
|
|
36
37
|
--paper-raised: var(--theme-paper);
|
|
@@ -42,51 +43,6 @@
|
|
|
42
43
|
--signal-red: var(--theme-error);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
[data-theme="contrast"] {
|
|
46
|
-
--theme-paper: #ffffff;
|
|
47
|
-
--theme-canvas: #f4f4f4;
|
|
48
|
-
--theme-ink: #111111;
|
|
49
|
-
--theme-error: #b42318;
|
|
50
|
-
--theme-success: #067647;
|
|
51
|
-
--theme-success-bg: #ecfdf3;
|
|
52
|
-
--theme-success-border: #abefc6;
|
|
53
|
-
--theme-meta: #4338ca;
|
|
54
|
-
--theme-info: #0369a1;
|
|
55
|
-
--theme-info-hover: #075985;
|
|
56
|
-
--theme-warning: #b54708;
|
|
57
|
-
--theme-warning-hover: #93370d;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
[data-theme="noir"] {
|
|
61
|
-
--theme-paper: #282c34;
|
|
62
|
-
--theme-canvas: #21252b;
|
|
63
|
-
--theme-ink: #abb2bf;
|
|
64
|
-
--theme-error: #e06c75;
|
|
65
|
-
--theme-success: #34d399;
|
|
66
|
-
--theme-success-bg: rgba(5, 150, 105, 0.15);
|
|
67
|
-
--theme-success-border: rgba(16, 185, 129, 0.2);
|
|
68
|
-
--theme-meta: #818cf8;
|
|
69
|
-
--theme-info: #61afef;
|
|
70
|
-
--theme-info-hover: #8ec7ff;
|
|
71
|
-
--theme-warning: #fb923c;
|
|
72
|
-
--theme-warning-hover: #fdba74;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
[data-theme="one-dark-pro-soft"] {
|
|
76
|
-
--theme-paper: #282c34;
|
|
77
|
-
--theme-canvas: #21252b;
|
|
78
|
-
--theme-ink: #abb2bf;
|
|
79
|
-
--theme-error: #e06c75;
|
|
80
|
-
--theme-success: #34d399;
|
|
81
|
-
--theme-success-bg: rgba(5, 150, 105, 0.15);
|
|
82
|
-
--theme-success-border: rgba(16, 185, 129, 0.2);
|
|
83
|
-
--theme-meta: #818cf8;
|
|
84
|
-
--theme-info: #61afef;
|
|
85
|
-
--theme-info-hover: #8ec7ff;
|
|
86
|
-
--theme-warning: #fb923c;
|
|
87
|
-
--theme-warning-hover: #fdba74;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
46
|
body {
|
|
91
47
|
font-family: 'IBM Plex Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
92
48
|
background-color: var(--theme-canvas);
|
|
@@ -235,6 +191,13 @@
|
|
|
235
191
|
.prose-content li { line-height: inherit; position: relative; }
|
|
236
192
|
.prose-content li + li { margin-top: 0.375rem; }
|
|
237
193
|
.prose-content li p { margin: 0.25rem 0; }
|
|
194
|
+
/* Tailwind's preflight makes every `img` block-level. Markdown images are
|
|
195
|
+
* inline content: a README badge row is one `<p align="center">` of
|
|
196
|
+
* `<a><img></a>` links, and a block image stacks it one badge per line (and
|
|
197
|
+
* pushes an image off the text line it was written on). `inline-block` restores
|
|
198
|
+
* the line box GitHub renders; the preflight's `max-width: 100%` still applies,
|
|
199
|
+
* and `vertical-align: middle` keeps badges of different heights aligned. */
|
|
200
|
+
.prose-content img { display: inline-block; vertical-align: middle; }
|
|
238
201
|
.prose-content ul > li::before { content: ''; display: block; position: absolute; left: -1.25em; top: 0; }
|
|
239
202
|
.prose-content blockquote {
|
|
240
203
|
border-left: 2px solid color-mix(in srgb, var(--theme-ink) 30%, transparent);
|
|
@@ -276,13 +239,13 @@
|
|
|
276
239
|
.prose-content .code-block pre { margin: 0; padding: 0.75rem; overflow-x: auto; line-height: var(--markdown-code-block-line-height); }
|
|
277
240
|
.prose-content .code-block code { font-family: 'Fira Code', monospace; font-size: 0.75rem; line-height: var(--markdown-code-block-line-height); }
|
|
278
241
|
.prose-content .code-block pre code { background: transparent; text-shadow: none; }
|
|
279
|
-
/* Shiki dual-theme tokens: the highlighter emits BOTH palettes (
|
|
242
|
+
/* Shiki dual-theme tokens: the highlighter emits BOTH palettes (one-light for
|
|
280
243
|
* light themes, one-dark-pro for dark themes) as CSS variables on every token
|
|
281
244
|
* span and no inline `color`, so the active [data-theme] selects the palette.
|
|
282
|
-
* Switching themes recolors code blocks with zero JS and no re-highlight.
|
|
245
|
+
* Switching themes recolors code blocks with zero JS and no re-highlight.
|
|
246
|
+
* The selector keys off the variant attribute, not a list of theme ids. */
|
|
283
247
|
.shiki span { color: var(--shiki-light); }
|
|
284
|
-
[data-theme="
|
|
285
|
-
[data-theme="one-dark-pro-soft"] .shiki span { color: var(--shiki-dark); }
|
|
248
|
+
html[data-theme-variant="dark"] .shiki span { color: var(--shiki-dark); }
|
|
286
249
|
.prose-content .katex-display { overflow-x: auto; overflow-y: hidden; }
|
|
287
250
|
|
|
288
251
|
/* Math placeholder: `marked.ts` emits a `.math-pending` span and KaTeX is
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Spawn arguments for a topic's side child.
|
|
8
|
+
*
|
|
9
|
+
* The side child is an ordinary `omp --mode rpc-ui` that resumes the topic's
|
|
10
|
+
* transcript. It runs WITH tools (unlike the TUI's `/btw`, which passes
|
|
11
|
+
* `--no-tools`): the panel exposes a real access-control dropdown, and an
|
|
12
|
+
* approval mode that governs nothing is a lie in the UI. What the child may
|
|
13
|
+
* actually do is therefore the `--approval-mode` flag's business, exactly as it
|
|
14
|
+
* is for a chat session.
|
|
15
|
+
*
|
|
16
|
+
* `--no-title` stays: auto-titling would spend a model call on a transcript
|
|
17
|
+
* nobody lists in the sidebar.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ApprovalMode } from '@/shared/lib/omp/config/access-mode';
|
|
21
|
+
import type { BtwModel } from '@/shared/types';
|
|
22
|
+
|
|
23
|
+
export interface BtwSpawnSettings {
|
|
24
|
+
model?: BtwModel;
|
|
25
|
+
/** omp's own default when absent (`auto`). */
|
|
26
|
+
thinkingLevel?: string;
|
|
27
|
+
/** Spawn-time only: omp exposes no RPC to change the approval mode. */
|
|
28
|
+
approvalMode?: ApprovalMode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildBtwSpawnArgs(sessionFile: string, settings: BtwSpawnSettings): string[] {
|
|
32
|
+
const args = ['--resume', sessionFile, '--no-title'];
|
|
33
|
+
if (settings.model) args.push('--model', `${settings.model.provider}/${settings.model.id}`);
|
|
34
|
+
// 'auto' means "leave omp's level alone" — the same rule the composer applies,
|
|
35
|
+
// so it is omitted rather than translated into a concrete level.
|
|
36
|
+
if (settings.thinkingLevel && settings.thinkingLevel !== 'auto') {
|
|
37
|
+
args.push('--thinking', settings.thinkingLevel);
|
|
38
|
+
}
|
|
39
|
+
if (settings.approvalMode) args.push('--approval-mode', settings.approvalMode);
|
|
40
|
+
return args;
|
|
41
|
+
}
|