ompchamber 1.1.0 → 1.2.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/dist/client/index.html +1 -1
- package/dist/client/static/css/index.b938f4a693.css +2 -0
- package/dist/client/static/js/77.8022a800b5.js +139 -0
- package/dist/client/static/js/async/{1k.5974df3b87.js → 1k.a411fe5b43.js} +2 -2
- package/dist/client/static/js/async/{3y.71f2ccf3b6.js → 3y.3186ecdd63.js} +5 -5
- package/dist/client/static/js/async/5b.d88304bc13.js +1 -0
- package/dist/client/static/js/async/66.86544ad741.js +93 -0
- package/dist/client/static/js/async/7g.db82aca7ba.js +1 -0
- package/dist/client/static/js/async/7l.fadbbc0583.js +1 -0
- package/dist/client/static/js/async/{dc.856fda6560.js → ar.ad5c8822a8.js} +2 -2
- package/dist/client/static/js/async/cx.9c8268d542.js +226 -0
- package/dist/client/static/js/async/e.6443e739a2.js +79 -0
- package/dist/client/static/js/async/f7.46971e9d6d.js +2 -0
- package/dist/client/static/js/async/g.ee41bfed7f.js +2 -0
- package/dist/client/static/js/async/h1.d7091cef78.js +1 -0
- package/dist/client/static/js/async/hs.e7c59d8090.js +41 -0
- package/dist/client/static/js/async/jl.4911fa3383.js +1 -0
- package/dist/client/static/js/async/{k.7466330d1a.js → k.d16cd09ae7.js} +2 -2
- package/dist/client/static/js/async/{m.c038c71b9f.js → m.2e1ea3db9e.js} +2 -2
- package/dist/client/static/js/async/m3.d2b05f9604.js +2 -0
- package/dist/client/static/js/async/mt.853be2b5aa.js +1 -0
- package/dist/client/static/js/async/{oc.9c060c9779.js → oc.eb22093f05.js} +1 -1
- package/dist/client/static/js/async/{og.634fba99ac.js → og.53f068804c.js} +1 -1
- package/dist/client/static/js/async/{5b.a5ccbc19dc.js → q.b453193538.js} +10 -10
- package/dist/client/static/js/async/q5.88b50ffa95.js +286 -0
- package/dist/client/static/js/async/q8.7f1b48e9cd.js +21 -0
- package/dist/client/static/js/async/{sy.d16be843a7.js → s.aad2ecebc6.js} +1 -1
- package/dist/client/static/js/async/{tr.e878658140.js → tr.2a0d2b7600.js} +1 -1
- package/dist/client/static/js/async/vt.20406a5604.js +1 -0
- package/dist/client/static/js/async/x1.fe8af17016.js +90 -0
- package/dist/client/static/js/async/yg.5a94e1fc8a.js +149 -0
- package/dist/client/static/js/async/yx.af2936c368.js +10 -0
- package/dist/client/static/js/index.57b39ffa7f.js +34 -0
- package/package.json +1 -1
- package/src/client/components/common/file-icon/index.tsx +4 -1
- package/src/client/components/common/image-viewer/index.tsx +224 -0
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +57 -43
- package/src/client/components/mobile/mobile-right-sidebar/Header.tsx +31 -24
- package/src/client/components/settings/Modal.tsx +1 -0
- package/src/client/components/settings/categories/ChatSettings.tsx +6 -2
- package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +99 -0
- package/src/client/components/workspace/btw-panel/AskCard.tsx +155 -0
- package/src/client/components/workspace/btw-panel/Composer.tsx +181 -0
- package/src/client/components/workspace/btw-panel/Messages.tsx +100 -0
- package/src/client/components/workspace/btw-panel/TopicMenu.tsx +106 -0
- package/src/client/components/workspace/btw-panel/attachments.ts +80 -0
- package/src/client/components/workspace/btw-panel/index.tsx +83 -0
- package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +37 -21
- package/src/client/components/workspace/chat-timeline/MinimapShortcuts.tsx +49 -12
- package/src/client/components/workspace/chat-timeline/TimelineBody.tsx +10 -6
- package/src/client/components/workspace/chat-timeline/chat-input/AttachmentToolbar.tsx +2 -1
- package/src/client/components/workspace/chat-timeline/chat-input/DropOverlay.tsx +17 -0
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +44 -40
- package/src/client/components/workspace/chat-timeline/index.tsx +11 -9
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Read.tsx +25 -7
- package/src/client/components/workspace/editor/Toolbar.tsx +32 -23
- package/src/client/components/workspace/editor/index.tsx +45 -36
- package/src/client/hooks/chat/btw/index.ts +180 -0
- package/src/client/hooks/chat/btw/intercept.ts +43 -0
- package/src/client/hooks/chat/btw/mode.ts +179 -0
- package/src/client/hooks/chat/composer/attachments.ts +198 -0
- package/src/client/hooks/chat/composer/drop-entries.ts +204 -0
- package/src/client/hooks/chat/composer/drop-payload.test.ts +97 -0
- package/src/client/hooks/chat/composer/drop-payload.ts +130 -0
- package/src/client/hooks/chat/composer/drop-references.ts +106 -0
- package/src/client/hooks/chat/composer/file-drop.test.ts +194 -0
- package/src/client/hooks/chat/composer/file-drop.ts +226 -0
- package/src/client/hooks/chat/composer/file-reads.test.ts +211 -0
- package/src/client/hooks/chat/composer/file-reads.ts +238 -0
- package/src/client/hooks/chat/composer/index.ts +7 -1
- package/src/client/hooks/chat/composer/pipeline.ts +181 -0
- package/src/client/hooks/chat/omp/index.ts +3 -3
- package/src/client/hooks/chat/omp/prompt-send.ts +5 -5
- package/src/client/hooks/chat/timeline/actions.ts +16 -1
- package/src/client/hooks/chat/timeline/index.ts +16 -8
- package/src/client/hooks/chat/timeline/prompt-text.ts +6 -1
- package/src/client/hooks/chat/timeline/send.ts +29 -65
- package/src/client/hooks/chat/timeline/session-load.ts +2 -1
- package/src/client/hooks/chat/timeline/session-pagination.ts +161 -14
- package/src/client/hooks/chat/timeline/user-turns.ts +149 -0
- package/src/client/hooks/editor/file-actions.ts +49 -0
- package/src/client/hooks/editor/use-file-editor.ts +38 -27
- package/src/client/hooks/editor/use-file-transfer.ts +78 -0
- package/src/client/tailwind.css +22 -0
- package/src/server/db.server.ts +21 -287
- package/src/server/lib/btw/frames.test.ts +68 -0
- package/src/server/lib/btw/frames.ts +56 -0
- package/src/server/lib/btw/prompt.ts +28 -0
- package/src/server/lib/btw/registry.server.ts +141 -0
- package/src/server/lib/btw/runtime.server.ts +324 -0
- package/src/server/lib/btw/schema.server.ts +44 -0
- package/src/server/lib/btw/service.server.ts +171 -0
- package/src/server/lib/btw/session-copy.server.ts +213 -0
- package/src/server/lib/btw/session-copy.test.ts +166 -0
- package/src/server/lib/btw/store.server.ts +240 -0
- package/src/server/lib/db/schema.ts +110 -0
- package/src/server/lib/db/seed.ts +160 -0
- package/src/server/lib/db/workspace-sync.ts +70 -0
- package/src/server/lib/fs/root.ts +37 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +207 -0
- package/src/server/lib/omp/rpc/manager.ts +27 -123
- package/src/server/lib/omp/session/auto-title.server.test.ts +137 -0
- package/src/server/lib/omp/session/auto-title.server.ts +152 -0
- package/src/server/lib/omp/session/blobs.server.test.ts +100 -0
- package/src/server/lib/omp/session/blobs.server.ts +76 -0
- package/src/server/lib/omp/session/file-scan.ts +174 -0
- package/src/server/lib/omp/session/files.test.ts +147 -0
- package/src/server/lib/omp/session/files.ts +52 -35
- package/src/server/lib/omp/session/locator.ts +2 -2
- package/src/server/lib/omp/session/merge-stored.test.ts +118 -0
- package/src/server/lib/omp/session/merge-stored.ts +171 -0
- package/src/server/lib/omp/session/messages.ts +34 -0
- package/src/server/lib/omp/session/reader.ts +2 -2
- package/src/server/lib/omp/session/subagent-presence.ts +9 -6
- package/src/server/lib/queue/delivery.server.test.ts +57 -0
- package/src/server/lib/queue/delivery.server.ts +25 -3
- package/src/server/plugins/ssr.ts +29 -2
- package/src/server/routes/btw/events.ts +70 -0
- package/src/server/routes/btw/index.ts +8 -0
- package/src/server/routes/btw/topics.ts +87 -0
- package/src/server/routes/btw/ws.ts +136 -0
- package/src/server/routes/chat/index.ts +2 -0
- package/src/server/routes/chat/session.ts +1 -115
- package/src/server/routes/chat/turns.ts +87 -0
- package/src/server/routes/fs/index.ts +4 -1
- package/src/server/routes/fs/raw.ts +85 -0
- package/src/server/routes/fs/read.ts +58 -3
- package/src/server/routes/index.ts +7 -1
- package/src/shared/lib/chat/attachments.test.ts +208 -0
- package/src/shared/lib/chat/attachments.ts +200 -15
- package/src/shared/lib/chat/btw/client.ts +94 -0
- package/src/shared/lib/chat/composer/client.test.ts +48 -0
- package/src/shared/lib/chat/composer/client.ts +6 -1
- package/src/shared/lib/chat/composer/composer.test.ts +39 -1
- package/src/shared/lib/chat/composer/trigger.ts +45 -1
- package/src/shared/lib/chat/omp/agent-events.ts +33 -80
- package/src/shared/lib/chat/omp/socket.ts +11 -6
- package/src/shared/lib/chat/omp/sse.ts +11 -6
- package/src/shared/lib/chat/omp/tool-results.ts +102 -0
- package/src/shared/lib/chat/omp/transport.ts +19 -3
- package/src/shared/lib/chat/text-file.ts +148 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +18 -32
- package/src/shared/lib/chat/timeline/stream-coalescer.ts +66 -0
- package/src/shared/lib/chat/timeline/turns.ts +7 -0
- package/src/shared/lib/fs/file-kind.ts +50 -0
- package/src/shared/lib/fs/paths.ts +19 -1
- package/src/shared/lib/markdown/math.test.ts +20 -3
- package/src/shared/lib/omp/session/inlined-attachments.test.ts +82 -0
- package/src/shared/lib/omp/session/mapper.ts +24 -4
- package/src/shared/lib/omp/session/messages-map.ts +16 -3
- package/src/shared/lib/omp/session/messages-parse.ts +2 -2
- package/src/shared/lib/omp/session/parse-message-blocks.ts +85 -5
- package/src/shared/types/btw.ts +65 -0
- package/src/shared/types/chat.ts +39 -1
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/omp/agent.ts +14 -1
- package/src/shared/types/omp/session.ts +4 -1
- package/src/shared/types/settings/state.ts +5 -0
- package/dist/client/static/css/index.b859cc1cc5.css +0 -2
- package/dist/client/static/js/async/5bh.8b2363424f.js +0 -1
- package/dist/client/static/js/async/66.5dfa29f957.js +0 -93
- package/dist/client/static/js/async/7g.a7814350c3.js +0 -1
- package/dist/client/static/js/async/7l.7a6a377ecc.js +0 -1
- package/dist/client/static/js/async/cx.ca5feec570.js +0 -226
- package/dist/client/static/js/async/e.93214d622b.js +0 -79
- package/dist/client/static/js/async/g.a06ca3b3e9.js +0 -2
- package/dist/client/static/js/async/h1.e8d5aa8e80.js +0 -1
- package/dist/client/static/js/async/hs.6d480d0ded.js +0 -41
- package/dist/client/static/js/async/jl.874d352278.js +0 -1
- package/dist/client/static/js/async/mt.0352e976d8.js +0 -1
- package/dist/client/static/js/async/nam.b5663e919f.js +0 -10
- package/dist/client/static/js/async/q.23af2a358f.js +0 -21
- package/dist/client/static/js/async/q5.e683f7c26e.js +0 -286
- package/dist/client/static/js/async/s.222ee974f6.js +0 -2
- package/dist/client/static/js/async/uf.46ad6209fa.js +0 -2
- package/dist/client/static/js/async/vt.42416b707d.js +0 -1
- package/dist/client/static/js/async/x1.da8687159b.js +0 -90
- package/dist/client/static/js/async/yg.35bebd6d0c.js +0 -149
- package/dist/client/static/js/index.6e065017eb.js +0 -33
- package/dist/client/static/js/jp.50817896f3.js +0 -139
- /package/dist/client/static/js/{jp.50817896f3.js.LICENSE.txt → 77.8022a800b5.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{1k.5974df3b87.js.LICENSE.txt → 1k.a411fe5b43.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.71f2ccf3b6.js.LICENSE.txt → 3y.3186ecdd63.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{dc.856fda6560.js.LICENSE.txt → ar.ad5c8822a8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{m.c038c71b9f.js.LICENSE.txt → f7.46971e9d6d.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{5b.a5ccbc19dc.js.LICENSE.txt → g.ee41bfed7f.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.23af2a358f.js.LICENSE.txt → m.2e1ea3db9e.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{s.222ee974f6.js.LICENSE.txt → m3.d2b05f9604.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.a06ca3b3e9.js.LICENSE.txt → q.b453193538.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{uf.46ad6209fa.js.LICENSE.txt → q8.7f1b48e9cd.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{nam.b5663e919f.js.LICENSE.txt → yx.af2936c368.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.6e065017eb.js.LICENSE.txt → index.57b39ffa7f.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Schema bootstrap for the chamber database: the `CREATE TABLE IF NOT EXISTS`
|
|
8
|
+
* block plus the in-place migrations that repair schemas written by older
|
|
9
|
+
* installs. Split out of `@/server/db.server` so the connection lifecycle
|
|
10
|
+
* there stays readable; the order below is load-bearing — the migrations run
|
|
11
|
+
* between the two DDL blocks, after the tables they touch exist.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { DbClient } from '@/server/lib/db/client';
|
|
15
|
+
import { migrateQueueTableFk } from '@/server/lib/queue/schema-migration.server';
|
|
16
|
+
import { ensureBtwSchema } from '@/server/lib/btw/schema.server';
|
|
17
|
+
import { migrateWorkspaceFolderColumns } from '@/shared/lib/workspace/schema-migrations';
|
|
18
|
+
|
|
19
|
+
export async function initSchema(db: DbClient): Promise<void> {
|
|
20
|
+
await db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS workspace_folders (
|
|
22
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
23
|
+
name TEXT NOT NULL,
|
|
24
|
+
is_expanded BOOLEAN DEFAULT 0,
|
|
25
|
+
project_path TEXT,
|
|
26
|
+
model TEXT DEFAULT 'Not selected',
|
|
27
|
+
accent_color TEXT DEFAULT '',
|
|
28
|
+
icon TEXT DEFAULT 'default',
|
|
29
|
+
custom_icon_url TEXT,
|
|
30
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
34
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
35
|
+
folder_id INTEGER NOT NULL,
|
|
36
|
+
title TEXT NOT NULL,
|
|
37
|
+
is_active BOOLEAN DEFAULT 0,
|
|
38
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
39
|
+
FOREIGN KEY (folder_id) REFERENCES workspace_folders (id)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE TABLE IF NOT EXISTS app_settings (
|
|
43
|
+
key TEXT PRIMARY KEY,
|
|
44
|
+
value TEXT NOT NULL
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS chat_sessions (
|
|
48
|
+
session_id TEXT PRIMARY KEY,
|
|
49
|
+
title TEXT,
|
|
50
|
+
messages TEXT NOT NULL,
|
|
51
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS deleted_workspaces (
|
|
55
|
+
project_path TEXT PRIMARY KEY,
|
|
56
|
+
deleted_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS archived_sessions (
|
|
60
|
+
session_id TEXT PRIMARY KEY,
|
|
61
|
+
archived_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS session_ui_state (
|
|
65
|
+
session_id TEXT PRIMARY KEY,
|
|
66
|
+
state TEXT NOT NULL DEFAULT '{}',
|
|
67
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
CREATE TABLE IF NOT EXISTS queued_messages (
|
|
71
|
+
id TEXT PRIMARY KEY,
|
|
72
|
+
session_id TEXT NOT NULL,
|
|
73
|
+
position INTEGER NOT NULL,
|
|
74
|
+
message TEXT NOT NULL DEFAULT '',
|
|
75
|
+
attachments TEXT NOT NULL DEFAULT '[]',
|
|
76
|
+
provider TEXT,
|
|
77
|
+
model_id TEXT,
|
|
78
|
+
thinking_level TEXT,
|
|
79
|
+
access_mode TEXT,
|
|
80
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
81
|
+
);
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_queued_messages_session ON queued_messages (session_id, position);
|
|
83
|
+
|
|
84
|
+
CREATE TABLE IF NOT EXISTS session_stream_state (
|
|
85
|
+
session_id TEXT PRIMARY KEY,
|
|
86
|
+
status TEXT NOT NULL,
|
|
87
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
88
|
+
);
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
await migrateWorkspaceFolderColumns(db);
|
|
92
|
+
// Installs older `queued_messages` schemas carry an invalid FK (see the
|
|
93
|
+
// migration module); rebuild once so real-session queue inserts work.
|
|
94
|
+
await migrateQueueTableFk(db);
|
|
95
|
+
// Side-question tables live in their own module (see schema.server.ts).
|
|
96
|
+
await ensureBtwSchema(db);
|
|
97
|
+
|
|
98
|
+
await db.exec(`
|
|
99
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
100
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
session_id INTEGER NOT NULL,
|
|
102
|
+
parent_id INTEGER,
|
|
103
|
+
name TEXT NOT NULL,
|
|
104
|
+
type TEXT NOT NULL,
|
|
105
|
+
is_expanded BOOLEAN DEFAULT 0,
|
|
106
|
+
FOREIGN KEY (session_id) REFERENCES sessions (id),
|
|
107
|
+
FOREIGN KEY (parent_id) REFERENCES files (id)
|
|
108
|
+
);
|
|
109
|
+
`);
|
|
110
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `MOCK=true` database seeding: demo workspace folders, screenshot-matching
|
|
8
|
+
* chat sessions, the bundled sample transcripts and a synthetic file tree.
|
|
9
|
+
* Split out of `@/server/db.server` so the connection lifecycle there stays
|
|
10
|
+
* readable. Runs only in mock mode and only on a database that has not been
|
|
11
|
+
* seeded before — every branch is idempotent, so a restart re-asserts the
|
|
12
|
+
* demo rows instead of duplicating them.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { DbClient } from '@/server/lib/db/client';
|
|
16
|
+
import { SAMPLE_TOOLS_SESSION_ID, getSampleToolsSession } from '@/client/data/samples/tools-session';
|
|
17
|
+
import { SAMPLE_DIALOGUE_SESSION_ID, getSampleDialogueSession } from '@/client/data/samples/dialogue-session';
|
|
18
|
+
import { SAMPLE_DEVICES_SESSION_ID, getSampleDevicesSession } from '@/client/data/samples/virtual-devices-session';
|
|
19
|
+
|
|
20
|
+
const INSERT_CHAT_SESSION =
|
|
21
|
+
'INSERT OR REPLACE INTO chat_sessions (session_id, title, messages, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)';
|
|
22
|
+
|
|
23
|
+
export async function seedMockData(db: DbClient): Promise<void> {
|
|
24
|
+
const folderCount = await db.get('SELECT COUNT(*) as count FROM workspace_folders');
|
|
25
|
+
|
|
26
|
+
if (folderCount.count === 0) {
|
|
27
|
+
await db.exec(`
|
|
28
|
+
INSERT OR IGNORE INTO workspace_folders (id, name, is_expanded) VALUES
|
|
29
|
+
(1, 'Chats', 1),
|
|
30
|
+
(2, 'Workspace', 1),
|
|
31
|
+
(3, 'drrealhandler', 1);
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await seedChatSessions(db);
|
|
36
|
+
await seedWorkspaceSessions(db, 2, [
|
|
37
|
+
'History Commit 2026-09-05 23:00',
|
|
38
|
+
'History Commit 2026-09-04 23:00',
|
|
39
|
+
'History Commit 2026-09-03 23:00',
|
|
40
|
+
'Error SMSC latency pada jns6.5 smppv2',
|
|
41
|
+
'Penyebab provider_submit_status=-1 di jns6.5 Smartfren SMPP',
|
|
42
|
+
'jns6.5 filevalidator error Mask Code di Release',
|
|
43
|
+
'Union query transaksi 202607 dan 202606',
|
|
44
|
+
]);
|
|
45
|
+
await seedWorkspaceSessions(db, 3, [
|
|
46
|
+
'Buat branch feat/add-provider-err-telco-to-dr-smpp',
|
|
47
|
+
'Update wiki v1.5.0 & testing dari add-configurabel',
|
|
48
|
+
'Konfigurasi rute DR SMPP via config lookup',
|
|
49
|
+
'drrealhandler update v1.5.0',
|
|
50
|
+
'Gitlab wiki clone isi kosong',
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
await seedFillerSessions(db);
|
|
54
|
+
await seedSessionFiles(db);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Demo folders 1-3 and the five sessions the sidebar screenshots show. */
|
|
58
|
+
async function seedChatSessions(db: DbClient): Promise<void> {
|
|
59
|
+
// Ensure ompchamber, Workspace, drrealhandler folders exist
|
|
60
|
+
await db.run("INSERT OR IGNORE INTO workspace_folders (id, name, is_expanded) VALUES (1, 'ompchamber', 1)");
|
|
61
|
+
await db.run("INSERT OR IGNORE INTO workspace_folders (id, name, is_expanded) VALUES (2, 'Workspace', 1)");
|
|
62
|
+
await db.run("INSERT OR IGNORE INTO workspace_folders (id, name, is_expanded) VALUES (3, 'drrealhandler', 1)");
|
|
63
|
+
await db.run("UPDATE workspace_folders SET name = 'ompchamber' WHERE id = 1");
|
|
64
|
+
|
|
65
|
+
const existingChats = await db.all("SELECT * FROM sessions WHERE folder_id = 1");
|
|
66
|
+
const titles = [
|
|
67
|
+
'Cek stream subagent di ompweb',
|
|
68
|
+
'Fitur Diff Panel dan Git Status Files',
|
|
69
|
+
'Implementasi Saved State UI per Session ID',
|
|
70
|
+
'Review loading indicator, model & intent title',
|
|
71
|
+
'Status fitur streer dan queue',
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
if (existingChats.length === 0) {
|
|
75
|
+
await db.run("INSERT INTO sessions (folder_id, title, is_active) VALUES (1, ?, 0)", [titles[0]]);
|
|
76
|
+
await db.run("INSERT INTO sessions (folder_id, title, is_active) VALUES (1, ?, 0)", [titles[1]]);
|
|
77
|
+
await db.run("INSERT INTO sessions (folder_id, title, is_active) VALUES (1, ?, 1)", [titles[2]]);
|
|
78
|
+
await db.run("INSERT INTO sessions (folder_id, title, is_active) VALUES (1, ?, 0)", [titles[3]]);
|
|
79
|
+
await db.run("INSERT INTO sessions (folder_id, title, is_active) VALUES (1, ?, 0)", [titles[4]]);
|
|
80
|
+
} else {
|
|
81
|
+
await db.run("UPDATE sessions SET title = ? WHERE id = 1", [titles[0]]);
|
|
82
|
+
await db.run("UPDATE sessions SET title = ? WHERE id = 2", [titles[1]]);
|
|
83
|
+
await db.run("UPDATE sessions SET title = ? WHERE id = 3", [titles[2]]);
|
|
84
|
+
await db.run("UPDATE sessions SET title = ? WHERE id = 4", [titles[3]]);
|
|
85
|
+
await db.run("UPDATE sessions SET title = ? WHERE id = 5", [titles[4]]);
|
|
86
|
+
await db.run("UPDATE sessions SET is_active = 0 WHERE folder_id = 1");
|
|
87
|
+
await db.run("UPDATE sessions SET is_active = 1 WHERE id = 3");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Pre-seed sample sessions data
|
|
91
|
+
try {
|
|
92
|
+
const s1 = getSampleToolsSession();
|
|
93
|
+
await db.run(INSERT_CHAT_SESSION, ['1', s1.title, JSON.stringify(s1.messages)]);
|
|
94
|
+
await db.run(INSERT_CHAT_SESSION, [SAMPLE_TOOLS_SESSION_ID, s1.title, JSON.stringify(s1.messages)]);
|
|
95
|
+
|
|
96
|
+
const s2 = getSampleDialogueSession();
|
|
97
|
+
await db.run(INSERT_CHAT_SESSION, ['2', s2.title, JSON.stringify(s2.messages)]);
|
|
98
|
+
await db.run(INSERT_CHAT_SESSION, [SAMPLE_DIALOGUE_SESSION_ID, s2.title, JSON.stringify(s2.messages)]);
|
|
99
|
+
|
|
100
|
+
const s3 = getSampleDevicesSession();
|
|
101
|
+
await db.run(INSERT_CHAT_SESSION, ['3', s3.title, JSON.stringify(s3.messages)]);
|
|
102
|
+
await db.run(INSERT_CHAT_SESSION, [SAMPLE_DEVICES_SESSION_ID, s3.title, JSON.stringify(s3.messages)]);
|
|
103
|
+
const s3Row = await db.get("SELECT id FROM sessions WHERE folder_id = 1 AND title = ?", [titles[2]]);
|
|
104
|
+
if (s3Row) {
|
|
105
|
+
await db.run(INSERT_CHAT_SESSION, [String(s3Row.id), s3.title, JSON.stringify(s3.messages)]);
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.error('Failed to pre-seed sample sessions:', e);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Add the given session titles to a folder when it has none yet. */
|
|
113
|
+
async function seedWorkspaceSessions(db: DbClient, folderId: number, titles: string[]): Promise<void> {
|
|
114
|
+
const existing = await db.all('SELECT * FROM sessions WHERE folder_id = ?', [folderId]);
|
|
115
|
+
if (existing.length > 0) return;
|
|
116
|
+
for (const title of titles) {
|
|
117
|
+
await db.run('INSERT INTO sessions (folder_id, title, is_active) VALUES (?, ?, 0)', [folderId, title]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Add random sessions if we don't have enough to demonstrate the UI. */
|
|
122
|
+
async function seedFillerSessions(db: DbClient): Promise<void> {
|
|
123
|
+
const sessionCount = await db.get('SELECT COUNT(*) as count FROM sessions');
|
|
124
|
+
if (sessionCount.count >= 15) return;
|
|
125
|
+
|
|
126
|
+
const titles = [
|
|
127
|
+
'Fix header styling issue', 'Implement authentication flow', 'Setup CI/CD pipeline',
|
|
128
|
+
'Debug database connection', 'Update README.md docs', 'Refactor UI components',
|
|
129
|
+
];
|
|
130
|
+
for (let i = 1; i <= 3; i++) {
|
|
131
|
+
for (let j = 0; j < 3; j++) {
|
|
132
|
+
const title = titles[Math.floor(Math.random() * titles.length)] + ' #' + Math.floor(Math.random() * 1000);
|
|
133
|
+
await db.run('INSERT INTO sessions (folder_id, title, is_active) VALUES (?, ?, 0)', [i, title]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Seed a demo file tree for every session when the files table is empty. */
|
|
139
|
+
async function seedSessionFiles(db: DbClient): Promise<void> {
|
|
140
|
+
const fileCount = await db.get('SELECT COUNT(*) as count FROM files');
|
|
141
|
+
if (fileCount.count !== 0) return;
|
|
142
|
+
|
|
143
|
+
const insertFile =
|
|
144
|
+
'INSERT INTO files (session_id, parent_id, name, type, is_expanded) VALUES (?, ?, ?, ?, ?)';
|
|
145
|
+
const allSessions = await db.all('SELECT id FROM sessions');
|
|
146
|
+
for (const session of allSessions) {
|
|
147
|
+
const src = await db.run(insertFile, [session.id, null, 'src', 'folder', 1]);
|
|
148
|
+
const components = await db.run(insertFile, [session.id, null, 'components', 'folder', 0]);
|
|
149
|
+
|
|
150
|
+
await db.run(insertFile, [session.id, src.lastID, 'index.tsx', 'file', 0]);
|
|
151
|
+
await db.run(insertFile, [session.id, src.lastID, 'App.tsx', 'file', 0]);
|
|
152
|
+
await db.run(insertFile, [session.id, src.lastID, 'styles.css', 'file', 0]);
|
|
153
|
+
|
|
154
|
+
await db.run(insertFile, [session.id, components.lastID, 'Button.tsx', 'file', 0]);
|
|
155
|
+
await db.run(insertFile, [session.id, components.lastID, 'Header.tsx', 'file', 0]);
|
|
156
|
+
|
|
157
|
+
await db.run(insertFile, [session.id, null, 'package.json', 'file', 0]);
|
|
158
|
+
await db.run(insertFile, [session.id, null, 'README.md', 'file', 0]);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Additive-only folder ↔ omp project sync: creates a workspace folder for
|
|
8
|
+
* every project discovered from ~/.omp/agent (folder name = lowercase
|
|
9
|
+
* basename, bound via project_path). It never modifies or deletes existing
|
|
10
|
+
* user data — no renames, no re-binding of unbound folders — and it skips
|
|
11
|
+
* projects the user has explicitly deleted (tombstoned in
|
|
12
|
+
* `deleted_workspaces`), so a deleted workspace stays deleted.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { DbClient } from '@/server/lib/db/client';
|
|
16
|
+
import { projectPathKey } from '@/server/lib/omp/core/paths';
|
|
17
|
+
import { loadOmpSidebarData } from '@/server/lib/omp/session/reader';
|
|
18
|
+
import { orderedOmpProjects, projectDisplayName } from '@/shared/lib/omp/session/sidebar';
|
|
19
|
+
|
|
20
|
+
/** SYNC_WORKSPACE env flag (default true when unset). When enabled in real
|
|
21
|
+
* mode, workspace folders are auto-created from discovered omp projects. */
|
|
22
|
+
function isWorkspaceSyncEnabled(): boolean {
|
|
23
|
+
const raw = (Bun.env.SYNC_WORKSPACE || '').trim().toLowerCase();
|
|
24
|
+
if (raw === '') return true;
|
|
25
|
+
return raw !== 'false' && raw !== '0' && raw !== 'off' && raw !== 'no';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Real-data workspace bootstrap: sync discovered omp projects into
|
|
30
|
+
* `workspace_folders` when SYNC_WORKSPACE allows it. Discovery must never
|
|
31
|
+
* block app startup, so a failure is logged rather than thrown.
|
|
32
|
+
*/
|
|
33
|
+
export async function syncWorkspaceFoldersFromDiscovery(db: DbClient): Promise<void> {
|
|
34
|
+
if (!isWorkspaceSyncEnabled()) return;
|
|
35
|
+
try {
|
|
36
|
+
await syncWorkspaceFoldersWithOmp(db);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// Discovery must never block app startup.
|
|
39
|
+
console.error('OMP workspace sync failed:', err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function syncWorkspaceFoldersWithOmp(db: DbClient): Promise<void> {
|
|
44
|
+
const data = await loadOmpSidebarData();
|
|
45
|
+
const existing = await db.all('SELECT id, name, project_path FROM workspace_folders');
|
|
46
|
+
const tombstoned = await db.all('SELECT project_path FROM deleted_workspaces');
|
|
47
|
+
|
|
48
|
+
// Compare by canonical key, not raw string: tombstones may predate the
|
|
49
|
+
// directory (unresolved symlink spelling) while project.path is realpath'd.
|
|
50
|
+
const byPath = new Set(existing.map((r) => r.project_path).filter(Boolean).map(projectPathKey));
|
|
51
|
+
const usedNames = new Set(existing.map((r) => (r.name as string).toLowerCase()));
|
|
52
|
+
const deletedPaths = new Set(tombstoned.map((r) => projectPathKey(r.project_path as string)));
|
|
53
|
+
|
|
54
|
+
for (const project of orderedOmpProjects(data)) {
|
|
55
|
+
if (deletedPaths.has(projectPathKey(project.path))) continue;
|
|
56
|
+
if (byPath.has(projectPathKey(project.path))) continue;
|
|
57
|
+
|
|
58
|
+
const name = projectDisplayName(project);
|
|
59
|
+
let candidate = name;
|
|
60
|
+
let suffix = 2;
|
|
61
|
+
while (usedNames.has(candidate)) {
|
|
62
|
+
candidate = `${name}-${suffix++}`;
|
|
63
|
+
}
|
|
64
|
+
usedNames.add(candidate);
|
|
65
|
+
await db.run(
|
|
66
|
+
'INSERT INTO workspace_folders (name, is_expanded, project_path) VALUES (?, 1, ?)',
|
|
67
|
+
[candidate, project.path],
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -28,6 +28,43 @@ export function resolveWithinRoot(root: string, rel: string): string | null {
|
|
|
28
28
|
return isWithinRoot(root, target) ? target : null;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a file a drop or a link pointed at, which may live outside the active
|
|
33
|
+
* root.
|
|
34
|
+
*
|
|
35
|
+
* A dropped file is always named by an absolute path or URL — a file manager
|
|
36
|
+
* cannot know which workspace the page considers active — so
|
|
37
|
+
* {@link resolveWithinRoot} rejects exactly the references the composer needs.
|
|
38
|
+
* The allow-list that governs a client-supplied `root` governs these too: the
|
|
39
|
+
* app root, a path beneath it, or an exact registered workspace project path.
|
|
40
|
+
* Anything else resolves to null, so an arbitrary path from a page still cannot
|
|
41
|
+
* read the filesystem.
|
|
42
|
+
*/
|
|
43
|
+
export async function resolveReferencedPath(rawPath: string): Promise<string | null> {
|
|
44
|
+
const candidate = rawPath.trim();
|
|
45
|
+
if (!candidate) return null;
|
|
46
|
+
|
|
47
|
+
const resolved = path.resolve(candidate);
|
|
48
|
+
if (resolved === APP_ROOT || resolved.startsWith(APP_ROOT + path.sep)) {
|
|
49
|
+
return (await pathExists(resolved)) ? resolved : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const db = await getDb();
|
|
54
|
+
const rows = (await db.all(
|
|
55
|
+
'SELECT project_path FROM workspace_folders WHERE project_path IS NOT NULL'
|
|
56
|
+
)) as { project_path: string }[];
|
|
57
|
+
for (const row of rows) {
|
|
58
|
+
const workspaceRoot = path.resolve(row.project_path);
|
|
59
|
+
if (isWithinRoot(workspaceRoot, resolved) && (await pathExists(resolved))) return resolved;
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Database unavailable — nothing outside the app root can be verified.
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
31
68
|
/**
|
|
32
69
|
* Default browsing root when no session-bound project is active. Mock/demo
|
|
33
70
|
* mode browses the bundled `examples` tree; real mode browses the app root.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Folds one omp RPC frame into the session wrapper's runtime state, and runs
|
|
8
|
+
* the settle-time side effects a terminal run triggers.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the client's `foldAgentEvent` (shared/lib/chat/omp/agent-events.ts):
|
|
11
|
+
* the same shape of problem — one frame, many state flags — solved the same way
|
|
12
|
+
* so both ends of the transport read alike. Split out of rpc-manager.ts so the
|
|
13
|
+
* wrapper owns only process lifecycle.
|
|
14
|
+
*
|
|
15
|
+
* The state machine is the load-bearing part. `turn_end` fires for EVERY turn
|
|
16
|
+
* in a multi-turn run, `agent_end` is the only frame that knows whether the run
|
|
17
|
+
* is terminal, and a `prompt` response that failed must clear the same flags a
|
|
18
|
+
* successful settle does — a missed clear strands the session as "running" and
|
|
19
|
+
* the idle reclaim can never take it back.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { clearSessionFileCaches } from '@/server/lib/omp/session/files';
|
|
23
|
+
import {
|
|
24
|
+
consumeAutoTitleOutput,
|
|
25
|
+
triggerAutoSessionTitle,
|
|
26
|
+
type AutoTitleHost,
|
|
27
|
+
} from '@/server/lib/omp/session/auto-title.server';
|
|
28
|
+
import { scheduleQueueDelivery, type QueueDeliveryHost } from '@/server/lib/queue/delivery.server';
|
|
29
|
+
import { loadStreamStatuses, markStreamStatus } from '@/shared/lib/omp/session/stream-state.server';
|
|
30
|
+
import { NON_TERMINAL_CONTINUATION_GRACE_MS, type AgentEvent } from '@/server/lib/omp/rpc/constants';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Wrapper surface the fold reads and writes. Every field is owned by the
|
|
34
|
+
* wrapper; the fold mutates them in place exactly as the inline switch did.
|
|
35
|
+
* Composes the two host contracts the settle path hands the wrapper to
|
|
36
|
+
* (auto-title, queue delivery) so the wrapper itself satisfies this whole type.
|
|
37
|
+
*/
|
|
38
|
+
export interface SessionFrameHost extends AutoTitleHost, QueueDeliveryHost {
|
|
39
|
+
promptRunning: boolean;
|
|
40
|
+
streaming: boolean;
|
|
41
|
+
compacting: boolean;
|
|
42
|
+
awaitingAgentStart: boolean;
|
|
43
|
+
awaitingAgentStartDeadline: number;
|
|
44
|
+
continuationGraceUntil: number;
|
|
45
|
+
emit(event: AgentEvent): void;
|
|
46
|
+
/** Remember a blocking ask/approval dialog so a reattaching client can answer it. */
|
|
47
|
+
trackUiDialog(frame: AgentEvent): void;
|
|
48
|
+
/** Fold a subagent frame into the liveness roster. */
|
|
49
|
+
observeSubagent(frame: AgentEvent, now: number): void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** What the caller must do after the fold. */
|
|
53
|
+
export interface FrameFoldResult {
|
|
54
|
+
/** The session list is stale and needs a rebuild. */
|
|
55
|
+
refreshSessionList: boolean;
|
|
56
|
+
/** The fold already handled this frame's user-visible effect and the caller
|
|
57
|
+
* must NOT forward it: a failed `prompt` response has emitted its own
|
|
58
|
+
* `prompt_error`, and a `command_output` belonging to the chamber's own
|
|
59
|
+
* background rename must stay out of the timeline. */
|
|
60
|
+
suppressForward: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** True when the ending turn was a user abort rather than a natural stop. */
|
|
64
|
+
function endedAborted(messages: unknown): boolean {
|
|
65
|
+
return (
|
|
66
|
+
Array.isArray(messages) &&
|
|
67
|
+
messages.some((entry) => {
|
|
68
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return false;
|
|
69
|
+
return (entry as Record<string, unknown>).stopReason === 'aborted';
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Write the terminal stream badge from the ending turn's own stopReason.
|
|
76
|
+
*
|
|
77
|
+
* The `abort` command already wrote `abort` at dispatch time, but the
|
|
78
|
+
* `agent_end` frame arrives later and previously flattened it to `finish`.
|
|
79
|
+
* Re-check the current row and only upgrade `stream` rows, so a `finish`
|
|
80
|
+
* written here can never clobber a newer run's live `stream`/`abort`.
|
|
81
|
+
*/
|
|
82
|
+
async function markEndStatus(sessionId: string, messages: unknown): Promise<void> {
|
|
83
|
+
if (!sessionId) return;
|
|
84
|
+
const current = await loadStreamStatuses();
|
|
85
|
+
if (current[sessionId] !== 'stream') return;
|
|
86
|
+
await markStreamStatus(sessionId, endedAborted(messages) ? 'abort' : 'finish');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Apply one frame to the wrapper's runtime state. */
|
|
90
|
+
export function foldSessionFrame(host: SessionFrameHost, event: AgentEvent): FrameFoldResult {
|
|
91
|
+
const result: FrameFoldResult = { refreshSessionList: false, suppressForward: false };
|
|
92
|
+
|
|
93
|
+
switch (event.type) {
|
|
94
|
+
case 'agent_start':
|
|
95
|
+
host.promptRunning = true;
|
|
96
|
+
host.streaming = true;
|
|
97
|
+
host.awaitingAgentStart = false;
|
|
98
|
+
host.awaitingAgentStartDeadline = 0;
|
|
99
|
+
host.continuationGraceUntil = 0;
|
|
100
|
+
clearSessionFileCaches();
|
|
101
|
+
result.refreshSessionList = true;
|
|
102
|
+
if (host.sessionId) void markStreamStatus(host.sessionId, 'stream');
|
|
103
|
+
break;
|
|
104
|
+
case 'turn_start':
|
|
105
|
+
// Redundant with agent_start in the happy path, but the run-level
|
|
106
|
+
// counterpart can be missed (e.g. the state was cleared by a command
|
|
107
|
+
// between frames). The turn is proof enough the session is live.
|
|
108
|
+
if (host.sessionId) void markStreamStatus(host.sessionId, 'stream');
|
|
109
|
+
break;
|
|
110
|
+
case 'message_start':
|
|
111
|
+
// Same recovery as turn_start: a message frame is only emitted inside a
|
|
112
|
+
// live run, so its arrival re-arms the `stream` row no matter what stale
|
|
113
|
+
// status sits there (upsert overwrites any terminal badge).
|
|
114
|
+
if (host.sessionId) void markStreamStatus(host.sessionId, 'stream');
|
|
115
|
+
break;
|
|
116
|
+
case 'turn_end': {
|
|
117
|
+
// A multi-turn run emits turn_end for EVERY turn — intermediates end with
|
|
118
|
+
// `toolUse`/`stop` while the run keeps going (verified against omp
|
|
119
|
+
// 18.2.8: agent_start → turn_start → turn_end('toolUse') → turn_start →
|
|
120
|
+
// turn_end('stop') → agent_end, and the frame carries no isTerminal
|
|
121
|
+
// field). Only `aborted` is unambiguous here: the run is over, so the
|
|
122
|
+
// abort badge can be written before agent_end arrives. Everything else
|
|
123
|
+
// waits for agent_end, which knows isTerminal.
|
|
124
|
+
if (!host.sessionId) break;
|
|
125
|
+
const turn = event.message as Record<string, unknown> | undefined;
|
|
126
|
+
if (turn?.stopReason === 'aborted') void markStreamStatus(host.sessionId, 'abort');
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'agent_end':
|
|
130
|
+
if (event.isTerminal !== false) {
|
|
131
|
+
host.streaming = false;
|
|
132
|
+
host.promptRunning = false;
|
|
133
|
+
host.awaitingAgentStart = false;
|
|
134
|
+
host.awaitingAgentStartDeadline = 0;
|
|
135
|
+
host.continuationGraceUntil = 0;
|
|
136
|
+
clearSessionFileCaches();
|
|
137
|
+
const aborted = endedAborted(event.messages);
|
|
138
|
+
if (host.sessionId) void markEndStatus(host.sessionId, event.messages);
|
|
139
|
+
// A settled run is the moment a session becomes nameable: omp's own
|
|
140
|
+
// first-message titling is suppressed under `--mode rpc-ui`
|
|
141
|
+
// (PI_NO_TITLE), so the chamber asks for the same title itself. The
|
|
142
|
+
// trigger is a no-op once the session carries a name — see
|
|
143
|
+
// triggerAutoSessionTitle for why that check is the safety property.
|
|
144
|
+
if (!aborted) void triggerAutoSessionTitle(host);
|
|
145
|
+
// The run truly ended — the server, not the browser, decides whether a
|
|
146
|
+
// queued follow-up goes out next. A user-aborted run holds the queue
|
|
147
|
+
// (stop-all semantics): the next run end or an explicit send picks it up.
|
|
148
|
+
if (!aborted) scheduleQueueDelivery(host);
|
|
149
|
+
} else {
|
|
150
|
+
host.continuationGraceUntil = Date.now() + NON_TERMINAL_CONTINUATION_GRACE_MS;
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case 'prompt_result':
|
|
154
|
+
host.promptRunning = false;
|
|
155
|
+
host.awaitingAgentStart = false;
|
|
156
|
+
host.awaitingAgentStartDeadline = 0;
|
|
157
|
+
break;
|
|
158
|
+
case 'auto_compaction_start':
|
|
159
|
+
host.compacting = true;
|
|
160
|
+
break;
|
|
161
|
+
case 'auto_compaction_end':
|
|
162
|
+
host.compacting = false;
|
|
163
|
+
clearSessionFileCaches();
|
|
164
|
+
break;
|
|
165
|
+
case 'command_output':
|
|
166
|
+
// A `command_output` for a command the OPERATOR typed renders as a notice
|
|
167
|
+
// row. But the chamber fires its own `/rename` after a settled run, and
|
|
168
|
+
// omp answers it on the same frame — "Session renamed to …", or "Could not
|
|
169
|
+
// generate a session title" for a low-signal first message. Neither was
|
|
170
|
+
// asked for, and the second would be pure noise. The frame carries no
|
|
171
|
+
// correlation id, so attribution is by the request window.
|
|
172
|
+
result.suppressForward = consumeAutoTitleOutput(host);
|
|
173
|
+
break;
|
|
174
|
+
case 'session_info_update':
|
|
175
|
+
// A rename rewrote the fixed-width title slot in place; the scan cache is
|
|
176
|
+
// keyed on file mtime, which that write cannot move, so the list must be
|
|
177
|
+
// rebuilt explicitly.
|
|
178
|
+
clearSessionFileCaches();
|
|
179
|
+
result.refreshSessionList = true;
|
|
180
|
+
break;
|
|
181
|
+
case 'extension_ui_request':
|
|
182
|
+
host.trackUiDialog(event);
|
|
183
|
+
break;
|
|
184
|
+
// Subagent frames carry no turn state, but they are the only proof that
|
|
185
|
+
// work is still running once the parent turn has ended.
|
|
186
|
+
case 'subagent_lifecycle':
|
|
187
|
+
case 'subagent_progress':
|
|
188
|
+
case 'subagent_event':
|
|
189
|
+
host.observeSubagent(event, Date.now());
|
|
190
|
+
break;
|
|
191
|
+
case 'response': {
|
|
192
|
+
if (event.success === false && event.command === 'prompt') {
|
|
193
|
+
host.promptRunning = false;
|
|
194
|
+
host.awaitingAgentStart = false;
|
|
195
|
+
host.awaitingAgentStartDeadline = 0;
|
|
196
|
+
host.emit({ type: 'prompt_error', errorMessage: (event.error as string) ?? 'Prompt failed' });
|
|
197
|
+
// The caller's trailing notify covers this: promptRunning just flipped,
|
|
198
|
+
// so the running snapshot changes and the listeners fire. Suppress only
|
|
199
|
+
// the forward, or the error would land twice.
|
|
200
|
+
result.suppressForward = true;
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return result;
|
|
207
|
+
}
|