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
|
@@ -51,8 +51,6 @@ export interface SessionFrameHost extends AutoTitleHost, QueueDeliveryHost {
|
|
|
51
51
|
|
|
52
52
|
/** What the caller must do after the fold. */
|
|
53
53
|
export interface FrameFoldResult {
|
|
54
|
-
/** The session list is stale and needs a rebuild. */
|
|
55
|
-
refreshSessionList: boolean;
|
|
56
54
|
/** The fold already handled this frame's user-visible effect and the caller
|
|
57
55
|
* must NOT forward it: a failed `prompt` response has emitted its own
|
|
58
56
|
* `prompt_error`, and a `command_output` belonging to the chamber's own
|
|
@@ -88,7 +86,7 @@ async function markEndStatus(sessionId: string, messages: unknown): Promise<void
|
|
|
88
86
|
|
|
89
87
|
/** Apply one frame to the wrapper's runtime state. */
|
|
90
88
|
export function foldSessionFrame(host: SessionFrameHost, event: AgentEvent): FrameFoldResult {
|
|
91
|
-
const result: FrameFoldResult = {
|
|
89
|
+
const result: FrameFoldResult = { suppressForward: false };
|
|
92
90
|
|
|
93
91
|
switch (event.type) {
|
|
94
92
|
case 'agent_start':
|
|
@@ -97,8 +95,12 @@ export function foldSessionFrame(host: SessionFrameHost, event: AgentEvent): Fra
|
|
|
97
95
|
host.awaitingAgentStart = false;
|
|
98
96
|
host.awaitingAgentStartDeadline = 0;
|
|
99
97
|
host.continuationGraceUntil = 0;
|
|
98
|
+
// One early auto-title attempt per run, not per conversation: the flag is
|
|
99
|
+
// reset here so a run that opens after an aborted one (which skipped the
|
|
100
|
+
// settle attempt) still gets its own early try. The eligibility latch is
|
|
101
|
+
// what bounds this to the conversation's first run.
|
|
102
|
+
host.autoTitleRequested = false;
|
|
100
103
|
clearSessionFileCaches();
|
|
101
|
-
result.refreshSessionList = true;
|
|
102
104
|
if (host.sessionId) void markStreamStatus(host.sessionId, 'stream');
|
|
103
105
|
break;
|
|
104
106
|
case 'turn_start':
|
|
@@ -113,19 +115,34 @@ export function foldSessionFrame(host: SessionFrameHost, event: AgentEvent): Fra
|
|
|
113
115
|
// status sits there (upsert overwrites any terminal badge).
|
|
114
116
|
if (host.sessionId) void markStreamStatus(host.sessionId, 'stream');
|
|
115
117
|
break;
|
|
116
|
-
case '
|
|
117
|
-
// A
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
case 'message_end': {
|
|
119
|
+
// A settled USER message is the earliest point at which the transcript
|
|
120
|
+
// carries the conversation's opening intent, so it is the earliest a
|
|
121
|
+
// title can be derived from it.
|
|
122
|
+
//
|
|
123
|
+
// `agent_start` is too early, and that is not an implementation detail
|
|
124
|
+
// but a property of omp's event order: the agent loop pushes
|
|
125
|
+
// `agent_start` BEFORE the turn opens, and the user's message rides
|
|
126
|
+
// `emitInputMessages` inside that turn. Measured on omp 18.3.0: the
|
|
127
|
+
// user message lands ~120ms after `agent_start`, so a `/rename` fired
|
|
128
|
+
// there reads `messageCount: 0`, builds an empty title context, and omp
|
|
129
|
+
// answers "Could not generate a session title". `message_start` is
|
|
130
|
+
// equally too early — the message is appended to the agent's state on
|
|
131
|
+
// `message_end`, so only here does `get_state` report it.
|
|
132
|
+
//
|
|
133
|
+
// The latch makes a later user message (a queued steer) a no-op, and a
|
|
134
|
+
// session that resumed an existing conversation is ineligible from the
|
|
135
|
+
// start. `agent_end` below retries if this attempt produced nothing.
|
|
136
|
+
const message = event.message as Record<string, unknown> | undefined;
|
|
137
|
+
if (message?.role === 'user') void triggerAutoSessionTitle(host, 'opening');
|
|
127
138
|
break;
|
|
128
139
|
}
|
|
140
|
+
// `turn_end` is deliberately NOT handled here: a multi-turn run emits it for
|
|
141
|
+
// EVERY turn (verified against omp 18.2.8: agent_start → turn_start →
|
|
142
|
+
// turn_end('toolUse') → turn_start → turn_end('stop') → agent_end) and the
|
|
143
|
+
// frame carries no isTerminal field, so no turn-level stopReason can be read
|
|
144
|
+
// as "the run is over". The terminal badge is written from `agent_end`
|
|
145
|
+
// alone (markEndStatus), which is the only frame that knows.
|
|
129
146
|
case 'agent_end':
|
|
130
147
|
if (event.isTerminal !== false) {
|
|
131
148
|
host.streaming = false;
|
|
@@ -136,12 +153,15 @@ export function foldSessionFrame(host: SessionFrameHost, event: AgentEvent): Fra
|
|
|
136
153
|
clearSessionFileCaches();
|
|
137
154
|
const aborted = endedAborted(event.messages);
|
|
138
155
|
if (host.sessionId) void markEndStatus(host.sessionId, event.messages);
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
156
|
+
// Fallback for the early attempt: the first user message fires `/rename`
|
|
157
|
+
// before the turn runs, and that attempt can come back empty (a provider
|
|
158
|
+
// error, a timeout, a child that went away, or the tiny model
|
|
159
|
+
// declining). The run end is the last moment a title can still be
|
|
160
|
+
// derived from this conversation's OPENING turn, so retry here.
|
|
161
|
+
//
|
|
162
|
+
// An aborted run still skips: the operator stopped the turn, and the
|
|
163
|
+
// next completed run is a better moment than a half-run transcript.
|
|
164
|
+
if (!aborted) void triggerAutoSessionTitle(host, 'settle');
|
|
145
165
|
// The run truly ended — the server, not the browser, decides whether a
|
|
146
166
|
// queued follow-up goes out next. A user-aborted run holds the queue
|
|
147
167
|
// (stop-all semantics): the next run end or an explicit send picks it up.
|
|
@@ -164,8 +184,8 @@ export function foldSessionFrame(host: SessionFrameHost, event: AgentEvent): Fra
|
|
|
164
184
|
break;
|
|
165
185
|
case 'command_output':
|
|
166
186
|
// A `command_output` for a command the OPERATOR typed renders as a notice
|
|
167
|
-
// row. But the chamber fires its own `/rename`
|
|
168
|
-
//
|
|
187
|
+
// row. But the chamber fires its own `/rename` in the background, and omp
|
|
188
|
+
// answers it on the same frame — "Session renamed to …", or "Could not
|
|
169
189
|
// generate a session title" for a low-signal first message. Neither was
|
|
170
190
|
// asked for, and the second would be pure noise. The frame carries no
|
|
171
191
|
// correlation id, so attribution is by the request window.
|
|
@@ -176,7 +196,6 @@ export function foldSessionFrame(host: SessionFrameHost, event: AgentEvent): Fra
|
|
|
176
196
|
// keyed on file mtime, which that write cannot move, so the list must be
|
|
177
197
|
// rebuilt explicitly.
|
|
178
198
|
clearSessionFileCaches();
|
|
179
|
-
result.refreshSessionList = true;
|
|
180
199
|
break;
|
|
181
200
|
case 'extension_ui_request':
|
|
182
201
|
host.trackUiDialog(event);
|
|
@@ -60,6 +60,33 @@ async function rejectionOf(operation: Promise<unknown>): Promise<WebRpcError> {
|
|
|
60
60
|
return error as WebRpcError;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** A process that completes the readiness handshake, so `waitUntilReady`
|
|
64
|
+
* reaches the `get_state` probe whose message count seeds the title latch. */
|
|
65
|
+
function makeReadyProc(messageCount: number): RpcProcess {
|
|
66
|
+
const proc = {
|
|
67
|
+
isAlive: true,
|
|
68
|
+
pid: 4243,
|
|
69
|
+
onFrame() {
|
|
70
|
+
return () => {};
|
|
71
|
+
},
|
|
72
|
+
waitReady() {
|
|
73
|
+
return Promise.resolve({ type: 'ready' });
|
|
74
|
+
},
|
|
75
|
+
negotiateProtocol() {
|
|
76
|
+
return Promise.resolve(1);
|
|
77
|
+
},
|
|
78
|
+
sendCommand(command: { type: string }) {
|
|
79
|
+
if (command.type === 'get_state') {
|
|
80
|
+
return Promise.resolve({ sessionId: 'sess-1', sessionFile: '/tmp/s.jsonl', messageCount, isStreaming: false, isCompacting: false });
|
|
81
|
+
}
|
|
82
|
+
return Promise.resolve(undefined);
|
|
83
|
+
},
|
|
84
|
+
sendFrame() {},
|
|
85
|
+
async dispose() {},
|
|
86
|
+
};
|
|
87
|
+
return proc as unknown as RpcProcess;
|
|
88
|
+
}
|
|
89
|
+
|
|
63
90
|
describe('AgentSessionWrapper lifecycle', () => {
|
|
64
91
|
// Every command arms the wrapper's idle timer; keep the clock fake so a test
|
|
65
92
|
// never schedules a real ten-minute window.
|
|
@@ -135,4 +162,18 @@ describe('AgentSessionWrapper lifecycle', () => {
|
|
|
135
162
|
|
|
136
163
|
expect(stub.disposeCount()).toBe(1);
|
|
137
164
|
});
|
|
165
|
+
|
|
166
|
+
test('a fresh conversation is title-eligible, a resumed one is not', async () => {
|
|
167
|
+
// omp reports the messages it restored, so `messageCount` is the only
|
|
168
|
+
// signal that separates "this session has no first turn yet" from "this
|
|
169
|
+
// child resumed a conversation that already has one". Getting it wrong
|
|
170
|
+
// re-titles an existing session from its newest turn.
|
|
171
|
+
const fresh = new AgentSessionWrapper(makeReadyProc(0), '/tmp');
|
|
172
|
+
await fresh.waitUntilReady();
|
|
173
|
+
expect(fresh.autoTitlePending).toBe(true);
|
|
174
|
+
|
|
175
|
+
const resumed = new AgentSessionWrapper(makeReadyProc(2), '/tmp');
|
|
176
|
+
await resumed.waitUntilReady();
|
|
177
|
+
expect(resumed.autoTitlePending).toBe(false);
|
|
178
|
+
});
|
|
138
179
|
});
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import { RpcProcess, type RpcFrame } from '@/server/lib/omp/rpc/process';
|
|
19
19
|
import { PendingUiDialogs } from '@/server/lib/omp/rpc/pending-ui-dialogs';
|
|
20
20
|
import { foldSessionFrame } from '@/server/lib/omp/rpc/frame-fold';
|
|
21
|
-
import { notifyRunningChange } from '@/server/lib/omp/rpc/session-registry';
|
|
22
21
|
import { dispatchSessionCommand } from '@/server/lib/omp/rpc/session-commands';
|
|
23
22
|
import { SubagentLiveness } from '@/server/lib/omp/rpc/subagent-liveness';
|
|
24
23
|
import { markStreamStatus } from '@/shared/lib/omp/session/stream-state.server';
|
|
@@ -67,10 +66,19 @@ export class AgentSessionWrapper {
|
|
|
67
66
|
// set in initialize(). No other flag on this wrapper can see them, and an
|
|
68
67
|
// idle reclaim or a spawn-mode reconcile must never kill a running subagent.
|
|
69
68
|
private readonly subagents = new SubagentLiveness();
|
|
70
|
-
// One background `/rename` at a time.
|
|
71
|
-
//
|
|
72
|
-
// would race to write the same title slot.
|
|
69
|
+
// One background `/rename` at a time. The first run makes up to two attempts
|
|
70
|
+
// (the early one at the first user message, the fallback at run end), and two
|
|
71
|
+
// overlapping generations would race to write the same title slot.
|
|
73
72
|
autoTitleInFlight = false;
|
|
73
|
+
// One-shot: the FIRST run of this conversation is the only one the chamber
|
|
74
|
+
// may ask omp to name it from. Seeded in applyIdentity from omp's own message
|
|
75
|
+
// count, so a session reclaimed and respawned with `--resume` (which reports
|
|
76
|
+
// the messages it restored) never re-titles from its newest turn.
|
|
77
|
+
autoTitlePending = true;
|
|
78
|
+
// True once this run's early attempt (the first settled user message) has
|
|
79
|
+
// been made. Reset at each `agent_start`, so a queued steer message inside the
|
|
80
|
+
// same run cannot re-ask and cancel the generation already in flight.
|
|
81
|
+
autoTitleRequested = false;
|
|
74
82
|
// Epoch ms until which `command_output` frames belong to our own background
|
|
75
83
|
// rename and must not reach the timeline. 0 when nothing is outstanding.
|
|
76
84
|
autoTitleWindowUntil = 0;
|
|
@@ -129,7 +137,6 @@ export class AgentSessionWrapper {
|
|
|
129
137
|
start(): void {
|
|
130
138
|
this.unsubscribeFrames = this.proc.onFrame((frame) => this.handleFrame(frame));
|
|
131
139
|
this.resetIdleTimer();
|
|
132
|
-
notifyRunningChange();
|
|
133
140
|
}
|
|
134
141
|
|
|
135
142
|
/** Resolves once the child announced readiness and identity is known. */
|
|
@@ -162,6 +169,12 @@ export class AgentSessionWrapper {
|
|
|
162
169
|
this.streaming = state.isStreaming;
|
|
163
170
|
this.compacting = state.isCompacting;
|
|
164
171
|
this.fastModeEnabled = state.fastModeEnabled ?? state.fastMode ?? this.fastModeEnabled;
|
|
172
|
+
// omp reports the messages it restored, so a child spawned for an EXISTING
|
|
173
|
+
// conversation (a `--resume` after the idle reclaim, or a session opened
|
|
174
|
+
// from the sidebar) already has a first turn behind it — it must never be
|
|
175
|
+
// titled from whatever the operator asks next. Only a conversation with no
|
|
176
|
+
// messages at all is eligible, which is exactly the fresh-spawn case.
|
|
177
|
+
this.autoTitlePending = state.messageCount === 0;
|
|
165
178
|
}
|
|
166
179
|
|
|
167
180
|
handleProcessExit(stderrTail: string): void {
|
|
@@ -194,14 +207,11 @@ export class AgentSessionWrapper {
|
|
|
194
207
|
const event = frame as AgentEvent;
|
|
195
208
|
// The state machine and its settle-time side effects live in frame-fold.ts;
|
|
196
209
|
// this method owns only the wrapper's own bookkeeping around it.
|
|
197
|
-
const {
|
|
210
|
+
const { suppressForward } = foldSessionFrame(this, event);
|
|
198
211
|
// `suppressForward` withholds only the FRAME — a failed prompt response
|
|
199
212
|
// already emitted its own `prompt_error`, and the chamber's own background
|
|
200
|
-
// rename must not surface its diagnostics.
|
|
201
|
-
// still fires: the fold just cleared `promptRunning`, and that flip is what
|
|
202
|
-
// releases the sidebar's spinner.
|
|
213
|
+
// rename must not surface its diagnostics.
|
|
203
214
|
if (!suppressForward) this.emit(event);
|
|
204
|
-
notifyRunningChange({ refreshSessionList });
|
|
205
215
|
}
|
|
206
216
|
|
|
207
217
|
emit(event: AgentEvent): void {
|
|
@@ -255,14 +265,6 @@ export class AgentSessionWrapper {
|
|
|
255
265
|
this.onDestroyCallback = cb;
|
|
256
266
|
}
|
|
257
267
|
|
|
258
|
-
async withFinalRunningNotification<T>(operation: () => Promise<T>): Promise<T> {
|
|
259
|
-
try {
|
|
260
|
-
return await operation();
|
|
261
|
-
} finally {
|
|
262
|
-
notifyRunningChange();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
268
|
/** Persist the session identity omp reports in a get_state payload. */
|
|
267
269
|
adoptSessionIdentity(state: RpcSessionState): void {
|
|
268
270
|
if (!state.sessionId) return;
|
|
@@ -298,11 +300,9 @@ export class AgentSessionWrapper {
|
|
|
298
300
|
this.continuationGraceUntil = 0;
|
|
299
301
|
const disposed = this.proc.dispose().catch(() => {});
|
|
300
302
|
this.destroyPromise = disposed;
|
|
301
|
-
notifyRunningChange();
|
|
302
303
|
await disposed;
|
|
303
304
|
this.onDestroyCallback?.();
|
|
304
305
|
}
|
|
305
306
|
}
|
|
306
307
|
|
|
307
|
-
export
|
|
308
|
-
export { getRpcSession, getRunningRpcSessions, getRunningRpcSessionIds, subscribeRunningSessions, notifyRunningChange, startRpcSession, prewarmRpcSession, startNewRpcSession } from '@/server/lib/omp/rpc/session-registry';
|
|
308
|
+
export { getRpcSession, startRpcSession, prewarmRpcSession, startNewRpcSession } from '@/server/lib/omp/rpc/session-registry';
|
|
@@ -26,22 +26,26 @@ export class PendingUiDialogs {
|
|
|
26
26
|
// answer them in — a Map preserves it.
|
|
27
27
|
private readonly requests = new Map<string, RpcFrame>();
|
|
28
28
|
|
|
29
|
-
/** Record an answerable request, or forget one omp withdrew (`cancel`).
|
|
30
|
-
|
|
29
|
+
/** Record an answerable request, or forget one omp withdrew (`cancel`).
|
|
30
|
+
* Returns whether the pending set changed — a caller that republishes state
|
|
31
|
+
* on every frame would otherwise republish for `notify`/`open_url` too. */
|
|
32
|
+
track(frame: RpcFrame): boolean {
|
|
31
33
|
if (frame.method === 'cancel') {
|
|
32
34
|
const targetId = typeof frame.targetId === 'string' ? frame.targetId : '';
|
|
33
|
-
|
|
34
|
-
return;
|
|
35
|
+
return targetId ? this.requests.delete(targetId) : false;
|
|
35
36
|
}
|
|
36
37
|
const id = typeof frame.id === 'string' ? frame.id : '';
|
|
37
|
-
if (!id || typeof frame.method !== 'string' || !ANSWERABLE_UI_METHODS.has(frame.method)) return;
|
|
38
|
+
if (!id || typeof frame.method !== 'string' || !ANSWERABLE_UI_METHODS.has(frame.method)) return false;
|
|
39
|
+
// A repeat of the same id is not a change.
|
|
40
|
+
if (this.requests.has(id)) return false;
|
|
38
41
|
this.requests.set(id, frame);
|
|
42
|
+
return true;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
/** Forget a request once its response is on the wire — omp will not raise it
|
|
42
46
|
* again, so replaying it to a later client would only show a dead modal. */
|
|
43
|
-
resolve(id: string):
|
|
44
|
-
this.requests.delete(id);
|
|
47
|
+
resolve(id: string): boolean {
|
|
48
|
+
return this.requests.delete(id);
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/** Still-blocking requests, oldest first. */
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
import { RpcCommandTimeoutError, type RpcProcess } from '@/server/lib/omp/rpc/process';
|
|
13
13
|
import { AWAITING_AGENT_START_TIMEOUT_MS, GET_STATE_TIMEOUT_MS, IMAGE_BEARING_COMMANDS, PASSTHROUGH_COMMANDS, PROMPT_ACK_TIMEOUT_MS, RESTARTING_MESSAGE, SESSION_BUSY_MESSAGE, WebRpcError, toImageContents, type AgentEvent, type RpcSessionState, validateAgentImages } from '@/server/lib/omp/rpc/constants';
|
|
14
14
|
import { clearSessionFileCaches } from '@/server/lib/omp/session/files';
|
|
15
|
-
import { notifyRunningChange } from '@/server/lib/omp/rpc/session-registry';
|
|
16
15
|
import { scheduleQueueDelivery } from '@/server/lib/queue/delivery.server';
|
|
17
16
|
import { clearStreamStatus, markStreamStatus } from '@/shared/lib/omp/session/stream-state.server';
|
|
18
17
|
import { buildWebState, type WebStateHost } from '@/server/lib/omp/rpc/web-state';
|
|
@@ -34,7 +33,6 @@ export interface SessionCommandHost extends WebStateHost {
|
|
|
34
33
|
resetIdleTimer(force?: boolean): void;
|
|
35
34
|
/** Forget a pending ask/approval dialog once its response is sent. */
|
|
36
35
|
resolvePendingUiDialog(id: string): void;
|
|
37
|
-
withFinalRunningNotification<T>(operation: () => Promise<T>): Promise<T>;
|
|
38
36
|
destroyAndWait(): Promise<void>;
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -87,7 +85,6 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
87
85
|
host.awaitingAgentStartDeadline = 0;
|
|
88
86
|
host.continuationGraceUntil = 0;
|
|
89
87
|
if (ownsStreamRow) void markStreamStatus(host.sessionId, 'stream');
|
|
90
|
-
notifyRunningChange();
|
|
91
88
|
}
|
|
92
89
|
try {
|
|
93
90
|
const ack = await host.proc.sendCommand<{ agentInvoked?: boolean } | undefined>({
|
|
@@ -102,7 +99,6 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
102
99
|
host.awaitingAgentStartDeadline = 0;
|
|
103
100
|
releaseStreamRow();
|
|
104
101
|
host.emit({ type: 'prompt_result', agentInvoked: false });
|
|
105
|
-
notifyRunningChange();
|
|
106
102
|
// Nothing ran (agent was idle and declined) — the queue may hold the
|
|
107
103
|
// next item; give it the same delivery window a run end would.
|
|
108
104
|
scheduleQueueDelivery(host);
|
|
@@ -115,7 +111,6 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
115
111
|
host.awaitingAgentStart = false;
|
|
116
112
|
host.awaitingAgentStartDeadline = 0;
|
|
117
113
|
releaseStreamRow();
|
|
118
|
-
notifyRunningChange();
|
|
119
114
|
// The ack may simply be queued behind a running turn: omp accepts the
|
|
120
115
|
// prompt before the turn it starts. No reset, and the client must not
|
|
121
116
|
// resend — a duplicate prompt would run twice.
|
|
@@ -130,14 +125,12 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
case 'abort':
|
|
133
|
-
await host.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (host.sessionId) void markStreamStatus(host.sessionId, 'abort');
|
|
140
|
-
});
|
|
128
|
+
await host.proc.sendCommand({ type: 'abort' });
|
|
129
|
+
host.promptRunning = false;
|
|
130
|
+
host.awaitingAgentStart = false;
|
|
131
|
+
host.awaitingAgentStartDeadline = 0;
|
|
132
|
+
host.continuationGraceUntil = 0;
|
|
133
|
+
if (host.sessionId) void markStreamStatus(host.sessionId, 'abort');
|
|
141
134
|
return null;
|
|
142
135
|
|
|
143
136
|
// Escape hatch behind the Stop button: `abort` resolves only once the turn
|
|
@@ -173,26 +166,23 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
173
166
|
|
|
174
167
|
case 'compact': {
|
|
175
168
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
host.compacting = false;
|
|
187
|
-
}
|
|
188
|
-
});
|
|
169
|
+
host.compacting = true;
|
|
170
|
+
try {
|
|
171
|
+
const result = await host.proc.sendCommand<{ summary?: string; tokensBefore?: number; estimatedTokensAfter?: number }>({
|
|
172
|
+
type: 'compact',
|
|
173
|
+
...(command.customInstructions ? { customInstructions: command.customInstructions } : {}),
|
|
174
|
+
});
|
|
175
|
+
return result;
|
|
176
|
+
} finally {
|
|
177
|
+
host.compacting = false;
|
|
178
|
+
}
|
|
189
179
|
} finally {
|
|
190
180
|
clearSessionFileCaches();
|
|
191
181
|
}
|
|
192
182
|
}
|
|
193
183
|
|
|
194
184
|
case 'abort_compaction':
|
|
195
|
-
await host.
|
|
185
|
+
await host.proc.sendCommand({ type: 'abort' });
|
|
196
186
|
return null;
|
|
197
187
|
|
|
198
188
|
case 'set_session_name': {
|
|
@@ -215,13 +205,11 @@ export async function dispatchSessionCommand(host: SessionCommandHost, command:
|
|
|
215
205
|
throw new Error('Cannot run a shell command while the session is busy');
|
|
216
206
|
}
|
|
217
207
|
host.bashRunning = true;
|
|
218
|
-
notifyRunningChange();
|
|
219
208
|
try {
|
|
220
209
|
return await host.proc.sendCommand<{ output?: string; exitCode?: number }>({ type: 'bash', command: command.command as string });
|
|
221
210
|
} finally {
|
|
222
211
|
host.bashRunning = false;
|
|
223
212
|
clearSessionFileCaches();
|
|
224
|
-
notifyRunningChange();
|
|
225
213
|
}
|
|
226
214
|
}
|
|
227
215
|
|
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Global omp session registry: keeps one AgentSessionWrapper per session id
|
|
8
|
-
* exposes lookups
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* usages happen inside functions, never at module init.
|
|
7
|
+
* Global omp session registry: keeps one AgentSessionWrapper per session id and
|
|
8
|
+
* exposes lookups. Split out of rpc-manager.ts so each file stays under the
|
|
9
|
+
* repo's per-file size ceiling. Importing AgentSessionWrapper here is a
|
|
10
|
+
* runtime-only circular reference with rpc-manager — every usage happens inside
|
|
11
|
+
* a function, never at module init.
|
|
13
12
|
*/
|
|
14
13
|
|
|
15
14
|
import { RpcProcess } from '@/server/lib/omp/rpc/process';
|
|
@@ -17,24 +16,11 @@ import { buildSessionSpawnArgs } from '@/server/lib/omp/rpc/constants';
|
|
|
17
16
|
import { AgentSessionWrapper } from '@/server/lib/omp/rpc/manager';
|
|
18
17
|
import { DEFAULT_APPROVAL_MODE, type ApprovalMode } from '@/shared/lib/omp/config/access-mode';
|
|
19
18
|
|
|
20
|
-
export interface RunningRpcSession {
|
|
21
|
-
id: string;
|
|
22
|
-
cwd: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface RunningSessionUpdate {
|
|
26
|
-
ids: string[];
|
|
27
|
-
runningSessions: RunningRpcSession[];
|
|
28
|
-
refreshSessionList: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
19
|
declare global {
|
|
32
20
|
// eslint-disable-next-line no-var
|
|
33
21
|
var __ompSessions: Map<string, AgentSessionWrapper> | undefined;
|
|
34
22
|
// eslint-disable-next-line no-var
|
|
35
23
|
var __ompStartLocks: Map<string, Promise<{ session: AgentSessionWrapper; realSessionId: string }>> | undefined;
|
|
36
|
-
// eslint-disable-next-line no-var
|
|
37
|
-
var __ompRunningListeners: Set<(update: RunningSessionUpdate) => void> | undefined;
|
|
38
24
|
}
|
|
39
25
|
|
|
40
26
|
function getRegistry(): Map<string, AgentSessionWrapper> {
|
|
@@ -57,21 +43,6 @@ export function getRpcSession(sessionId: string): AgentSessionWrapper | undefine
|
|
|
57
43
|
return getRegistry().get(sessionId);
|
|
58
44
|
}
|
|
59
45
|
|
|
60
|
-
export function getRunningRpcSessions(): RunningRpcSession[] {
|
|
61
|
-
const map = new Map<string, string>();
|
|
62
|
-
for (const [sessionId, session] of getRegistry()) {
|
|
63
|
-
if (session.isRunning()) {
|
|
64
|
-
const realId = session.sessionId || sessionId;
|
|
65
|
-
map.set(realId, session.cwd);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return [...map.entries()].map(([id, cwd]) => ({ id, cwd }));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function getRunningRpcSessionIds(): string[] {
|
|
72
|
-
return getRunningRpcSessions().map((s) => s.id);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
46
|
/**
|
|
76
47
|
* Sessions whose omp process is parked on a dialog only the user can release —
|
|
77
48
|
* an `ask` question or an approval gate. Deliberately independent of
|
|
@@ -90,38 +61,6 @@ export function getAwaitingInputSessionIds(): string[] {
|
|
|
90
61
|
return [...ids];
|
|
91
62
|
}
|
|
92
63
|
|
|
93
|
-
function getRunningListeners(): Set<(update: RunningSessionUpdate) => void> {
|
|
94
|
-
if (!globalThis.__ompRunningListeners) globalThis.__ompRunningListeners = new Set();
|
|
95
|
-
return globalThis.__ompRunningListeners;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Subscribe to running-session-id changes and session-list refreshes. */
|
|
99
|
-
export function subscribeRunningSessions(listener: (update: RunningSessionUpdate) => void): () => void {
|
|
100
|
-
const listeners = getRunningListeners();
|
|
101
|
-
listeners.add(listener);
|
|
102
|
-
return () => { listeners.delete(listener); };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let lastRunningSnapshot = '';
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Recompute the running-session-id set and, when it changes, broadcast it.
|
|
109
|
-
* A session file may first appear after its id starts running, so callers can
|
|
110
|
-
* force one otherwise-identical update to refresh sidebar session metadata.
|
|
111
|
-
*/
|
|
112
|
-
export function notifyRunningChange({ refreshSessionList = false }: { refreshSessionList?: boolean } = {}): void {
|
|
113
|
-
const runningSessions = getRunningRpcSessions();
|
|
114
|
-
const ids = runningSessions.map((s) => s.id);
|
|
115
|
-
if (runningSessions.length === 0 && lastRunningSnapshot === '[]' && !refreshSessionList) return;
|
|
116
|
-
const snapshot = JSON.stringify(runningSessions.slice().sort((a, b) => a.id.localeCompare(b.id)));
|
|
117
|
-
if (snapshot === lastRunningSnapshot && !refreshSessionList) return;
|
|
118
|
-
lastRunningSnapshot = snapshot;
|
|
119
|
-
const update: RunningSessionUpdate = { ids, runningSessions, refreshSessionList };
|
|
120
|
-
for (const listener of getRunningListeners()) {
|
|
121
|
-
try { listener(update); } catch { /* ignore listener errors */ }
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
64
|
// The approval mode each wrapper's omp child was actually spawned with. omp has
|
|
126
65
|
// no RPC setter for it, so a live wrapper is stale once the desired mode
|
|
127
66
|
// differs and must be respawned with the new --approval-mode flag.
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* rpc-manager.ts stays under the repo's per-file size ceiling.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { notifyRunningChange } from '@/server/lib/omp/rpc/session-registry';
|
|
18
17
|
import type { RpcSessionState, WebSessionState } from '@/server/lib/omp/rpc/constants';
|
|
19
18
|
|
|
20
19
|
/** Wrapper state the reconciliation reads and writes. */
|
|
@@ -34,8 +33,6 @@ export interface WebStateHost {
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
export function buildWebState(host: WebStateHost, state: RpcSessionState): WebSessionState {
|
|
37
|
-
const wasRunning = host.isRunning();
|
|
38
|
-
|
|
39
36
|
host.streaming = state.isStreaming;
|
|
40
37
|
host.compacting = state.isCompacting;
|
|
41
38
|
host.adoptSessionIdentity(state);
|
|
@@ -56,9 +53,6 @@ export function buildWebState(host: WebStateHost, state: RpcSessionState): WebSe
|
|
|
56
53
|
host.awaitingAgentStartDeadline = 0;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
if (wasRunning && !host.isRunning()) {
|
|
60
|
-
notifyRunningChange();
|
|
61
|
-
}
|
|
62
56
|
return {
|
|
63
57
|
sessionId: state.sessionId,
|
|
64
58
|
sessionFile: state.sessionFile ?? '',
|