@beeuni/sdk 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -92
- package/dist/beeuni.esm.min.js +36 -34
- package/dist/beeuni.global.js +36 -34
- package/dist/browser/cache/idb-sync-cache.js +15 -5
- package/dist/browser/session-sync/server-transcript-mirror.d.ts +7 -1
- package/dist/browser/session-sync/server-transcript-mirror.js +30 -24
- package/dist/browser/session-sync/session-sync-registry.d.ts +3 -1
- package/dist/browser/session-sync/session-sync-registry.js +48 -3
- package/dist/browser/stores/sync-store/keep-unchanged.d.ts +4 -0
- package/dist/browser/stores/sync-store/keep-unchanged.js +34 -0
- package/dist/browser/stores/sync-store.d.ts +7 -0
- package/dist/browser/stores/sync-store.js +122 -35
- package/dist/core/attachments/prompt-attachments.d.ts +4 -0
- package/dist/core/attachments/prompt-attachments.js +34 -0
- package/dist/core/capture/rules.d.ts +17 -0
- package/dist/core/capture/rules.js +27 -0
- package/dist/core/client/beeuni.d.ts +99 -34
- package/dist/core/client/beeuni.js +5 -11
- package/dist/core/client/capture.d.ts +105 -0
- package/dist/core/client/capture.js +48 -0
- package/dist/core/client/facade-catalog.d.ts +0 -4
- package/dist/core/client/facade-catalog.js +1 -5
- package/dist/core/client/facade-identity.d.ts +1 -0
- package/dist/core/client/facade-identity.js +1 -0
- package/dist/core/client/project-access-resources.d.ts +3 -1
- package/dist/core/client/project-access-resources.js +1 -1
- package/dist/core/client/project-connections.d.ts +2 -2
- package/dist/core/client/project-connections.js +2 -2
- package/dist/core/client/project-operations-admin.d.ts +4 -4
- package/dist/core/client/project-operations-review.d.ts +2 -3
- package/dist/core/client/project-operations-review.js +1 -0
- package/dist/core/client/project-operations-workspace.d.ts +4 -4
- package/dist/core/client/project-operations-workspace.js +1 -1
- package/dist/core/client/project-operations.d.ts +9 -19
- package/dist/core/client/project-operations.js +2 -6
- package/dist/core/client/project-platform-security.d.ts +1 -1
- package/dist/core/client/project-platform-settings.d.ts +1 -1
- package/dist/core/client/session-context.d.ts +2 -0
- package/dist/core/client/session-handle.d.ts +13 -0
- package/dist/core/client/session-lifecycle-resources.d.ts +11 -0
- package/dist/core/client/session-lifecycle-resources.js +4 -0
- package/dist/core/client/session-lifecycle-security.d.ts +2 -0
- package/dist/core/client/session-lifecycle-security.js +1 -0
- package/dist/core/client/session-readiness.d.ts +2 -0
- package/dist/core/client/session-readiness.js +20 -1
- package/dist/core/http/api-client.d.ts +1 -7
- package/dist/core/http/api-client.js +2 -37
- package/dist/core/http/auth.d.ts +2 -4
- package/dist/core/http/auth.js +2 -8
- package/dist/core/http/config.d.ts +2 -0
- package/dist/core/http/response-error.js +26 -11
- package/dist/core/http/transport.js +6 -1
- package/dist/core/models/composer-model.d.ts +14 -0
- package/dist/core/models/composer-model.js +21 -1
- package/dist/core/rest/platform-client/capture.d.ts +363 -0
- package/dist/core/rest/platform-client/capture.js +116 -0
- package/dist/core/rest/platform-client/client-policy.d.ts +8 -0
- package/dist/core/rest/platform-client/client-policy.js +30 -0
- package/dist/core/rest/platform-client/index.d.ts +4 -4
- package/dist/core/rest/platform-client/index.js +4 -4
- package/dist/core/rest/platform-client/lifecycle.js +18 -12
- package/dist/core/rest/platform-client/shared.d.ts +5 -0
- package/dist/core/rest/platform-client/shared.js +18 -0
- package/dist/core/rest/platform-client/types.d.ts +1 -1
- package/dist/core/rest/projects-client/account-secret-resources.d.ts +2 -1
- package/dist/core/rest/projects-client/accounts.d.ts +34 -0
- package/dist/core/rest/projects-client/accounts.js +10 -0
- package/dist/core/rest/projects-client/admin-providers.d.ts +0 -61
- package/dist/core/rest/projects-client/admin-providers.js +0 -6
- package/dist/core/rest/projects-client/agent-config.d.ts +3 -0
- package/dist/core/rest/projects-client/apps-fleet.d.ts +5 -1
- package/dist/core/rest/projects-client/apps-fleet.js +2 -2
- package/dist/core/rest/projects-client/apps.d.ts +11 -1
- package/dist/core/rest/projects-client/change-requests.d.ts +2 -0
- package/dist/core/rest/projects-client/change-requests.js +2 -0
- package/dist/core/rest/projects-client/connectors.d.ts +13 -55
- package/dist/core/rest/projects-client/connectors.js +5 -29
- package/dist/core/rest/projects-client/files.d.ts +3 -0
- package/dist/core/rest/projects-client/files.js +4 -0
- package/dist/core/rest/projects-client/gateway.d.ts +5 -0
- package/dist/core/rest/projects-client/git-backend.d.ts +1 -10
- package/dist/core/rest/projects-client/git-backend.js +0 -10
- package/dist/core/rest/projects-client/iam-actions.d.ts +76 -0
- package/dist/core/rest/projects-client/iam-actions.js +101 -0
- package/dist/core/rest/projects-client/iam-sso.d.ts +0 -10
- package/dist/core/rest/projects-client/iam-sso.js +0 -3
- package/dist/core/rest/projects-client/iam.d.ts +1 -0
- package/dist/core/rest/projects-client/iam.js +1 -0
- package/dist/core/rest/projects-client/index.d.ts +1 -4
- package/dist/core/rest/projects-client/index.js +1 -4
- package/dist/core/rest/projects-client/notifications.d.ts +1 -1
- package/dist/core/rest/projects-client/notifications.js +4 -0
- package/dist/core/rest/projects-client/projects.d.ts +4 -2
- package/dist/core/rest/projects-client/projects.js +4 -2
- package/dist/core/rest/projects-client/review-items.d.ts +2 -0
- package/dist/core/rest/projects-client/review-items.js +4 -0
- package/dist/core/rest/projects-client/review.d.ts +17 -3
- package/dist/core/rest/projects-client/review.js +11 -0
- package/dist/core/rest/projects-client/sandbox.d.ts +8 -9
- package/dist/core/rest/projects-client/secrets.d.ts +3 -0
- package/dist/core/rest/projects-client/session-attachments.js +14 -1
- package/dist/core/rest/projects-client/session-sandbox.d.ts +1 -1
- package/dist/core/rest/projects-client/sessions.d.ts +31 -4
- package/dist/core/rest/projects-client/sessions.js +34 -4
- package/dist/core/runtime/runtime-rest-client.js +1 -1
- package/dist/core/runtime/runtime-types.d.ts +1 -0
- package/dist/core/session/control-stream.d.ts +1 -0
- package/dist/core/session/control-stream.js +18 -1
- package/dist/core/session/open-bundle.d.ts +4 -4
- package/dist/core/session/open-bundle.js +29 -46
- package/dist/core/session/turn-end-watch.d.ts +47 -0
- package/dist/core/session/turn-end-watch.js +141 -0
- package/dist/core/session-log/guard.d.ts +6 -0
- package/dist/core/session-log/guard.js +109 -0
- package/dist/core/session-log/index.d.ts +7 -0
- package/dist/core/session-log/index.js +5 -0
- package/dist/core/session-log/saved-log.d.ts +10 -0
- package/dist/core/session-log/saved-log.js +25 -0
- package/dist/core/session-log/source.d.ts +25 -0
- package/dist/core/session-log/source.js +30 -0
- package/dist/core/session-log/store.d.ts +21 -0
- package/dist/core/session-log/store.js +126 -0
- package/dist/core/session-log/tool-call.d.ts +11 -0
- package/dist/core/session-log/tool-call.js +65 -0
- package/dist/core/session-log/turns/blocks.d.ts +60 -0
- package/dist/core/session-log/turns/blocks.js +124 -0
- package/dist/core/session-log/turns/history.d.ts +32 -0
- package/dist/core/session-log/turns/history.js +99 -0
- package/dist/core/session-log/turns/index.d.ts +4 -0
- package/dist/core/session-log/turns/index.js +4 -0
- package/dist/core/session-log/turns/metrics.d.ts +21 -0
- package/dist/core/session-log/turns/metrics.js +29 -0
- package/dist/core/session-log/turns/turn.d.ts +55 -0
- package/dist/core/session-log/turns/turn.js +279 -0
- package/dist/core/session-log/turns/v1-parts.d.ts +8 -0
- package/dist/core/session-log/turns/v1-parts.js +43 -0
- package/dist/core/session-log/types.d.ts +264 -0
- package/dist/core/session-log/types.js +6 -0
- package/dist/core/session-log/v1-view.d.ts +13 -0
- package/dist/core/session-log/v1-view.js +215 -0
- package/dist/core/session-log/v1.d.ts +31 -0
- package/dist/core/session-log/v1.js +473 -0
- package/dist/core/session-sync/copy-store-types.d.ts +19 -0
- package/dist/core/session-sync/copy-store-types.js +1 -0
- package/dist/core/session-sync/saved-copy-store.js +25 -4
- package/dist/core/session-sync/saved-transcript.d.ts +15 -1
- package/dist/core/session-sync/saved-transcript.js +55 -0
- package/dist/core/session-sync/session-sync-controller.js +3 -0
- package/dist/core/stream/event-stream.js +2 -2
- package/dist/core/turns/classify.d.ts +2 -1
- package/dist/core/turns/classify.js +7 -3
- package/dist/core/turns/edit-blocks.d.ts +1 -0
- package/dist/core/turns/edit-blocks.js +15 -0
- package/dist/core/turns/parts.d.ts +3 -2
- package/dist/core/turns/parts.js +4 -2
- package/dist/core/turns/segments/burst-summary.d.ts +1 -0
- package/dist/core/turns/segments/burst-summary.js +24 -0
- package/dist/core/turns/segments/group-steps.d.ts +4 -3
- package/dist/core/turns/segments/group-steps.js +9 -6
- package/dist/core/turns/segments/index.d.ts +1 -0
- package/dist/core/turns/segments/index.js +1 -0
- package/dist/core/turns/segments/narration.d.ts +5 -4
- package/dist/core/turns/segments/narration.js +12 -3
- package/dist/core/turns/segments/session-activity-groups.d.ts +2 -1
- package/dist/core/turns/segments/session-activity-groups.js +3 -1
- package/dist/core/turns/segments/step-label.d.ts +2 -1
- package/dist/core/turns/segments/step-label.js +3 -1
- package/dist/core/turns/segments/work-split.d.ts +10 -0
- package/dist/core/turns/segments/work-split.js +40 -0
- package/dist/core/turns/segments/working-turn.d.ts +4 -2
- package/dist/core/turns/segments/working-turn.js +9 -8
- package/dist/core/turns/state.d.ts +2 -7
- package/dist/core/turns/state.js +28 -134
- package/dist/core/turns/tool-kind.d.ts +1 -1
- package/dist/core/turns/tool-kind.js +3 -2
- package/dist/core/turns/tool-registry.js +1 -0
- package/dist/core/turns/tools/tool-meta.d.ts +3 -2
- package/dist/core/turns/tools/tool-meta.js +5 -2
- package/dist/core/turns/tools/tool-outcome.d.ts +2 -1
- package/dist/core/turns/tools/tool-outcome.js +3 -1
- package/dist/core/turns/types.d.ts +1 -1
- package/dist/core/turns/view-model.js +1 -13
- package/dist/core/warm-session-pool.d.ts +29 -0
- package/dist/core/warm-session-pool.js +127 -0
- package/dist/genui/catalog.d.ts +28 -0
- package/dist/genui/catalog.js +396 -0
- package/dist/genui/expansion.d.ts +3 -0
- package/dist/genui/expansion.js +295 -0
- package/dist/genui/fence-entry.d.ts +2 -0
- package/dist/genui/fence-entry.js +1 -0
- package/dist/genui/fence.d.ts +21 -0
- package/dist/genui/fence.js +138 -0
- package/dist/genui/index.d.ts +6 -0
- package/dist/genui/index.js +6 -0
- package/dist/genui/markdown.d.ts +12 -0
- package/dist/genui/markdown.js +104 -0
- package/dist/genui/parse.d.ts +6 -0
- package/dist/genui/parse.js +75 -0
- package/dist/genui/prompt.d.ts +2 -0
- package/dist/genui/prompt.js +206 -0
- package/dist/genui/react/default-components.d.ts +2 -0
- package/dist/genui/react/default-components.js +72 -0
- package/dist/genui/react/default-components.test.d.ts +1 -0
- package/dist/genui/react/default-components.test.js +94 -0
- package/dist/genui/react/genui-block-disabled.test.d.ts +1 -0
- package/dist/genui/react/genui-block-disabled.test.js +50 -0
- package/dist/genui/react/genui-block.d.ts +28 -0
- package/dist/genui/react/genui-block.js +91 -0
- package/dist/genui/react/genui-block.test.d.ts +1 -0
- package/dist/genui/react/genui-block.test.js +205 -0
- package/dist/genui/react/index.d.ts +2 -0
- package/dist/genui/react/index.js +2 -0
- package/dist/genui/share.d.ts +5 -0
- package/dist/genui/share.js +30 -0
- package/dist/genui/test-fixtures.d.ts +2 -0
- package/dist/genui/test-fixtures.js +16 -0
- package/dist/genui/types.d.ts +29 -0
- package/dist/genui/types.js +1 -0
- package/dist/genui/urls.d.ts +1 -0
- package/dist/genui/urls.js +18 -0
- package/dist/genui/validate.d.ts +9 -0
- package/dist/genui/validate.js +116 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/internal/connector-catalog.d.ts +48 -0
- package/dist/internal/connector-catalog.js +27 -0
- package/dist/internal/host-configuration.d.ts +22 -0
- package/dist/internal/host-configuration.js +28 -0
- package/dist/internal/platform-admin-react.d.ts +12 -0
- package/dist/internal/platform-admin-react.js +13 -0
- package/dist/internal/platform-admin.d.ts +9 -0
- package/dist/internal/platform-admin.js +9 -0
- package/dist/platform/ui.d.ts +8 -2
- package/dist/platform/ui.js +19 -4
- package/dist/react/chat/use-chat-turns.d.ts +1 -1
- package/dist/react/human-presence.d.ts +9 -0
- package/dist/react/human-presence.js +12 -0
- package/dist/react/index.d.ts +4 -13
- package/dist/react/index.js +4 -13
- package/dist/react/opencode.d.ts +1 -0
- package/dist/react/opencode.js +1 -0
- package/dist/react/query-contracts.d.ts +6 -0
- package/dist/react/query-contracts.js +6 -0
- package/dist/react/query-keys.d.ts +17 -2
- package/dist/react/query-keys.js +24 -2
- package/dist/react/reset-identity-state.js +2 -0
- package/dist/react/session-cache-write.d.ts +1 -0
- package/dist/react/session-cache-write.js +20 -0
- package/dist/react/session-transcript-subscription.js +3 -1
- package/dist/react/sign-in-with-beeuni.d.ts +6 -6
- package/dist/react/turn-end-handoff.d.ts +13 -0
- package/dist/react/turn-end-handoff.js +101 -0
- package/dist/react/use-admin-providers.d.ts +3 -6
- package/dist/react/use-admin-providers.js +1 -11
- package/dist/react/use-capture.d.ts +47 -0
- package/dist/react/use-capture.js +173 -0
- package/dist/react/use-feature-flag.js +1 -0
- package/dist/react/use-opencode-events/handle-event.d.ts +1 -0
- package/dist/react/use-opencode-events/handle-event.js +5 -0
- package/dist/react/use-opencode-events/handle-interaction-event.js +8 -3
- package/dist/react/use-opencode-events/handle-session-event.js +13 -8
- package/dist/react/use-opencode-events/handler-context.d.ts +1 -0
- package/dist/react/use-opencode-events/helpers.d.ts +1 -0
- package/dist/react/use-opencode-events/helpers.js +11 -1
- package/dist/react/use-opencode-events/index.js +1 -0
- package/dist/react/use-opencode-local.d.ts +5 -1
- package/dist/react/use-opencode-local.js +15 -2
- package/dist/react/use-opencode-sessions/commands.d.ts +11 -1
- package/dist/react/use-opencode-sessions/commands.js +21 -1
- package/dist/react/use-opencode-sessions/permissions.js +28 -1
- package/dist/react/use-project-sessions.d.ts +1 -1
- package/dist/react/use-project-sessions.js +6 -5
- package/dist/react/use-prompt-attachments.js +1 -0
- package/dist/react/use-review-summary.d.ts +13 -0
- package/dist/react/use-review-summary.js +32 -0
- package/dist/react/use-runtime-reconnect.js +1 -1
- package/dist/react/use-session-list-state.d.ts +6 -0
- package/dist/react/use-session-list-state.js +33 -0
- package/dist/react/use-session-log.d.ts +16 -0
- package/dist/react/use-session-log.js +143 -0
- package/dist/react/use-session-messages.d.ts +2 -1
- package/dist/react/use-session-messages.js +15 -14
- package/dist/react/use-session-stream.js +12 -1
- package/dist/react/use-session-sync.d.ts +2 -2
- package/dist/react/use-session-sync.js +10 -8
- package/dist/react/use-session-transcript-history.js +2 -12
- package/dist/react/use-session-working.d.ts +2 -0
- package/dist/react/use-session-working.js +10 -0
- package/dist/react/use-session.d.ts +6 -0
- package/dist/react/use-session.js +88 -6
- package/package.json +62 -8
|
@@ -23,11 +23,21 @@ function openDB() {
|
|
|
23
23
|
const pending = new Promise((resolve, reject) => {
|
|
24
24
|
const request = idb.open(DB_NAME, DB_VERSION);
|
|
25
25
|
request.onupgradeneeded = () => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
db.
|
|
29
|
-
|
|
30
|
-
db.
|
|
26
|
+
try {
|
|
27
|
+
const db = request.result;
|
|
28
|
+
if (db.objectStoreNames.contains(RETIRED_STORE_NAME))
|
|
29
|
+
db.deleteObjectStore(RETIRED_STORE_NAME);
|
|
30
|
+
if (!db.objectStoreNames.contains(STORE_NAME))
|
|
31
|
+
db.createObjectStore(STORE_NAME);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
reject(error);
|
|
35
|
+
try {
|
|
36
|
+
request.transaction?.abort();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
31
41
|
};
|
|
32
42
|
request.onsuccess = () => {
|
|
33
43
|
const db = request.result;
|
|
@@ -18,7 +18,6 @@ export declare function parseBeeuniSessionScope(scope: string | undefined): {
|
|
|
18
18
|
} | null;
|
|
19
19
|
export declare function loadSessionTranscriptMirror(input: {
|
|
20
20
|
beeuniSessionScope: string | undefined;
|
|
21
|
-
limit?: number;
|
|
22
21
|
signal?: AbortSignal;
|
|
23
22
|
child?: string;
|
|
24
23
|
}): Promise<SessionTranscriptSyncEnvelope | null>;
|
|
@@ -29,5 +28,12 @@ export declare function loadOlderSessionTranscriptMirror(input: {
|
|
|
29
28
|
signal?: AbortSignal;
|
|
30
29
|
child?: string;
|
|
31
30
|
}): Promise<SessionTranscriptSyncEnvelope | null>;
|
|
31
|
+
export declare function loadSavedSessionTranscriptMessages(scope: {
|
|
32
|
+
projectId: string;
|
|
33
|
+
sessionId: string;
|
|
34
|
+
}, signal?: AbortSignal): Promise<Array<{
|
|
35
|
+
info: Message;
|
|
36
|
+
parts: Part[];
|
|
37
|
+
}>>;
|
|
32
38
|
export declare const SAVED_COPY_REFRESH_DELAY_MS = 3000;
|
|
33
39
|
export declare function refreshSavedCopy(store: SavedCopyStore, projectId: string, sessionId: string): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSessionTranscriptSync, } from "../../core/rest/projects-client/sessions.js";
|
|
2
|
-
import {
|
|
2
|
+
import { loadSavedTranscript } from "../../core/session-sync/saved-transcript.js";
|
|
3
3
|
export const MIRROR_HYDRATE_LIMIT = 40;
|
|
4
4
|
export function shouldHydrateFromMirror(input) {
|
|
5
5
|
const { envelope, runtimeSessionId, hasMessages } = input;
|
|
@@ -52,31 +52,14 @@ export async function loadSessionTranscriptMirror(input) {
|
|
|
52
52
|
const scope = parseBeeuniSessionScope(input.beeuniSessionScope);
|
|
53
53
|
if (!scope)
|
|
54
54
|
return null;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
limit:
|
|
55
|
+
try {
|
|
56
|
+
return input.child
|
|
57
|
+
? await getSessionTranscriptSync(scope.projectId, scope.sessionId, {
|
|
58
|
+
limit: MIRROR_HYDRATE_LIMIT,
|
|
59
59
|
signal: input.signal,
|
|
60
60
|
child: input.child,
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const claimed = claimOpenBundle(scope.projectId, scope.sessionId);
|
|
68
|
-
if (claimed)
|
|
69
|
-
await claimed;
|
|
70
|
-
const stashed = takeOpenBundleTranscript(scope.projectId, scope.sessionId);
|
|
71
|
-
if (stashed)
|
|
72
|
-
return stashed;
|
|
73
|
-
if (takeOpenBundleTranscriptAbsence(scope.projectId, scope.sessionId))
|
|
74
|
-
return null;
|
|
75
|
-
try {
|
|
76
|
-
return await getSessionTranscriptSync(scope.projectId, scope.sessionId, {
|
|
77
|
-
limit: input.limit ?? MIRROR_HYDRATE_LIMIT,
|
|
78
|
-
signal: input.signal,
|
|
79
|
-
});
|
|
61
|
+
})
|
|
62
|
+
: await loadSavedTranscript(scope.projectId, scope.sessionId, { signal: input.signal });
|
|
80
63
|
}
|
|
81
64
|
catch {
|
|
82
65
|
return null;
|
|
@@ -98,6 +81,29 @@ export async function loadOlderSessionTranscriptMirror(input) {
|
|
|
98
81
|
return null;
|
|
99
82
|
}
|
|
100
83
|
}
|
|
84
|
+
const SAVED_EXPORT_WINDOW = 500;
|
|
85
|
+
export async function loadSavedSessionTranscriptMessages(scope, signal) {
|
|
86
|
+
const windows = [];
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
let before = null;
|
|
89
|
+
do {
|
|
90
|
+
const envelope = await getSessionTranscriptSync(scope.projectId, scope.sessionId, {
|
|
91
|
+
limit: SAVED_EXPORT_WINDOW,
|
|
92
|
+
history: true,
|
|
93
|
+
before,
|
|
94
|
+
signal,
|
|
95
|
+
});
|
|
96
|
+
if (!envelope.available)
|
|
97
|
+
break;
|
|
98
|
+
windows.unshift(mirrorMessagesForHydrate(envelope));
|
|
99
|
+
before = envelope.next_cursor ?? null;
|
|
100
|
+
if (before !== null && seen.has(before))
|
|
101
|
+
break;
|
|
102
|
+
if (before !== null)
|
|
103
|
+
seen.add(before);
|
|
104
|
+
} while (before !== null);
|
|
105
|
+
return windows.flat();
|
|
106
|
+
}
|
|
101
107
|
export const SAVED_COPY_REFRESH_DELAY_MS = 3_000;
|
|
102
108
|
export async function refreshSavedCopy(store, projectId, sessionId) {
|
|
103
109
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Message, Part, SessionStatus } from '../../core/runtime/runtime-types.js';
|
|
2
|
-
import { SessionSyncController, type SessionSyncPage, type SessionSyncReason } from '../../core/session-sync/session-sync-controller.js';
|
|
2
|
+
import { SessionSyncController, type SessionSyncPage, type SessionSyncReason, type SessionSyncTelemetryEvent } from '../../core/session-sync/session-sync-controller.js';
|
|
3
3
|
interface MessagesResponse {
|
|
4
4
|
data?: Array<{
|
|
5
5
|
info: Message;
|
|
@@ -28,6 +28,7 @@ export declare function readSessionMessagePage(client: SessionMessageClient, ses
|
|
|
28
28
|
limit: number;
|
|
29
29
|
before?: string;
|
|
30
30
|
}, signal?: AbortSignal): Promise<SessionSyncPage>;
|
|
31
|
+
export declare function reportTelemetry(sessionId: string, event: SessionSyncTelemetryEvent): void;
|
|
31
32
|
export declare function getSessionSyncController(sessionId: string, client?: SessionMessageClient, runtimeScope?: string): SessionSyncController;
|
|
32
33
|
export declare function prefetchSessionSyncOnce(sessionId: string, source: SessionPrefetchSource, client?: SessionMessageClient): Promise<boolean>;
|
|
33
34
|
export declare function clearActiveSessionPrefetches(): void;
|
|
@@ -35,6 +36,7 @@ export declare function retainSessionSyncController(sessionId: string, runtimeSc
|
|
|
35
36
|
export declare function reconcileSessionTail(sessionId: string, reason: SessionSyncReason, runtimeScope?: string): Promise<void>;
|
|
36
37
|
export declare function loadSessionRuntimeStatus(sessionId: string, client?: SessionMessageClient): Promise<SessionStatus | null>;
|
|
37
38
|
export declare function loadSessionTranscriptMessages(sessionId: string): Promise<SessionSyncPage['messages']>;
|
|
39
|
+
export declare function loadSessionExportMessages(sessionId: string, beeuniSessionScope?: string): Promise<SessionSyncPage['messages']>;
|
|
38
40
|
export declare function noteSessionSyncEvent(event: {
|
|
39
41
|
type?: string;
|
|
40
42
|
properties?: unknown;
|
|
@@ -4,8 +4,10 @@ import { ApiError } from '../../core/http/api/errors.js';
|
|
|
4
4
|
import { SessionSyncController, loadCompleteSessionHistory, } from '../../core/session-sync/session-sync-controller.js';
|
|
5
5
|
import { getCurrentRuntimeSandboxId, getCurrentRuntimeUrl } from '../../core/session/current-runtime.js';
|
|
6
6
|
import { useSyncStore } from '../stores/sync-store.js';
|
|
7
|
+
import { loadSavedSessionTranscriptMessages, parseBeeuniSessionScope } from './server-transcript-mirror.js';
|
|
7
8
|
export const ACTIVE_SESSION_PREFETCH_SOURCE = Symbol('active-session-prefetch');
|
|
8
9
|
const MAX_CONTROLLERS = 20;
|
|
10
|
+
const IDLE_BEFORE_EVICT_MS = 30_000;
|
|
9
11
|
const controllers = new Map();
|
|
10
12
|
function runtimeScopeKey(runtimeScope) {
|
|
11
13
|
return runtimeScope ?? getCurrentRuntimeSandboxId() ?? 'none';
|
|
@@ -83,7 +85,14 @@ export async function readSessionMessagePage(client, sessionId, request, signal)
|
|
|
83
85
|
nextCursor: result.response?.headers.get('x-next-cursor') || undefined,
|
|
84
86
|
};
|
|
85
87
|
}
|
|
86
|
-
function reportTelemetry(sessionId, event) {
|
|
88
|
+
export function reportTelemetry(sessionId, event) {
|
|
89
|
+
try {
|
|
90
|
+
if (!globalThis.localStorage?.getItem('beeuni:debug'))
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
87
96
|
console.debug('[session-sync]', { sessionId, ...event });
|
|
88
97
|
}
|
|
89
98
|
function resolveClient(key) {
|
|
@@ -118,8 +127,11 @@ function createController(sessionId, key) {
|
|
|
118
127
|
function evictInactiveControllers(protectedKey) {
|
|
119
128
|
if (controllers.size <= MAX_CONTROLLERS)
|
|
120
129
|
return;
|
|
130
|
+
const now = Date.now();
|
|
121
131
|
const inactive = [...controllers.entries()]
|
|
122
|
-
.filter(([key, entry]) => entry.consumers === 0 &&
|
|
132
|
+
.filter(([key, entry]) => entry.consumers === 0 &&
|
|
133
|
+
key !== protectedKey &&
|
|
134
|
+
now - entry.lastUsedAt > IDLE_BEFORE_EVICT_MS)
|
|
123
135
|
.sort((a, b) => a[1].lastUsedAt - b[1].lastUsedAt);
|
|
124
136
|
for (const [key, entry] of inactive) {
|
|
125
137
|
entry.controller.destroy();
|
|
@@ -128,6 +140,21 @@ function evictInactiveControllers(protectedKey) {
|
|
|
128
140
|
return;
|
|
129
141
|
}
|
|
130
142
|
}
|
|
143
|
+
let evictionTimer;
|
|
144
|
+
function scheduleEviction(delayMs = IDLE_BEFORE_EVICT_MS + 1) {
|
|
145
|
+
if (evictionTimer !== undefined)
|
|
146
|
+
return;
|
|
147
|
+
evictionTimer = setTimeout(() => {
|
|
148
|
+
evictionTimer = undefined;
|
|
149
|
+
evictInactiveControllers();
|
|
150
|
+
if (controllers.size <= MAX_CONTROLLERS)
|
|
151
|
+
return;
|
|
152
|
+
const idleSince = Math.min(...[...controllers.values()].filter((entry) => entry.consumers === 0).map((entry) => entry.lastUsedAt));
|
|
153
|
+
if (Number.isFinite(idleSince))
|
|
154
|
+
scheduleEviction(Math.max(1, idleSince + IDLE_BEFORE_EVICT_MS + 1 - Date.now()));
|
|
155
|
+
}, delayMs);
|
|
156
|
+
evictionTimer.unref?.();
|
|
157
|
+
}
|
|
131
158
|
function getOrCreateRegistryEntry(sessionId, client, initialConsumers = 0, runtimeScope) {
|
|
132
159
|
const key = controllerKey(sessionId, runtimeScope);
|
|
133
160
|
const existing = controllers.get(key);
|
|
@@ -197,8 +224,10 @@ export function retainSessionSyncController(sessionId, runtimeScope) {
|
|
|
197
224
|
return;
|
|
198
225
|
current.consumers = Math.max(0, current.consumers - 1);
|
|
199
226
|
current.lastUsedAt = Date.now();
|
|
200
|
-
if (current.consumers === 0)
|
|
227
|
+
if (current.consumers === 0) {
|
|
201
228
|
controller.setBusy(false);
|
|
229
|
+
scheduleEviction();
|
|
230
|
+
}
|
|
202
231
|
evictInactiveControllers();
|
|
203
232
|
};
|
|
204
233
|
}
|
|
@@ -217,6 +246,20 @@ export async function loadSessionRuntimeStatus(sessionId, client = getClient())
|
|
|
217
246
|
export function loadSessionTranscriptMessages(sessionId) {
|
|
218
247
|
return loadCompleteSessionHistory((request) => readSessionMessagePage(getClient(), sessionId, request));
|
|
219
248
|
}
|
|
249
|
+
export async function loadSessionExportMessages(sessionId, beeuniSessionScope) {
|
|
250
|
+
const saved = parseBeeuniSessionScope(beeuniSessionScope);
|
|
251
|
+
let live = [];
|
|
252
|
+
try {
|
|
253
|
+
live = await loadSessionTranscriptMessages(sessionId);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
if (!saved)
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
if (live.length > 0 || !saved)
|
|
260
|
+
return live;
|
|
261
|
+
return loadSavedSessionTranscriptMessages(saved);
|
|
262
|
+
}
|
|
220
263
|
const TRANSCRIPT_EVENT_TYPES = new Set([
|
|
221
264
|
'message.updated',
|
|
222
265
|
'message.part.updated',
|
|
@@ -242,6 +285,8 @@ export function resetSessionSyncControllers() {
|
|
|
242
285
|
for (const entry of controllers.values())
|
|
243
286
|
entry.controller.destroy();
|
|
244
287
|
controllers.clear();
|
|
288
|
+
clearTimeout(evictionTimer);
|
|
289
|
+
evictionTimer = undefined;
|
|
245
290
|
}
|
|
246
291
|
export function resetSessionSyncControllersForSession(sessionId, keepRuntimeScope) {
|
|
247
292
|
for (const [key, entry] of controllers) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function sameValue(a: unknown, b: unknown): boolean;
|
|
2
|
+
export declare function keepUnchanged<M extends {
|
|
3
|
+
id: string;
|
|
4
|
+
}, P>(previous: readonly M[] | undefined, next: M[], previousParts: Record<string, P[] | undefined>, nextParts: Record<string, P[] | undefined>): M[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function sameValue(a, b) {
|
|
2
|
+
if (a === b)
|
|
3
|
+
return true;
|
|
4
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
5
|
+
return false;
|
|
6
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
7
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
8
|
+
return false;
|
|
9
|
+
return a.every((value, index) => sameValue(value, b[index]));
|
|
10
|
+
}
|
|
11
|
+
const left = a;
|
|
12
|
+
const right = b;
|
|
13
|
+
const keys = Object.keys(left);
|
|
14
|
+
if (keys.length !== Object.keys(right).length)
|
|
15
|
+
return false;
|
|
16
|
+
return keys.every((key) => Object.hasOwn(right, key) && sameValue(left[key], right[key]));
|
|
17
|
+
}
|
|
18
|
+
export function keepUnchanged(previous, next, previousParts, nextParts) {
|
|
19
|
+
const byId = new Map((previous ?? []).map((message) => [message.id, message]));
|
|
20
|
+
const shared = next.map((message) => {
|
|
21
|
+
const old = byId.get(message.id);
|
|
22
|
+
return old && sameValue(old, message) ? old : message;
|
|
23
|
+
});
|
|
24
|
+
for (const message of shared) {
|
|
25
|
+
const before = previousParts[message.id];
|
|
26
|
+
const after = nextParts[message.id];
|
|
27
|
+
if (before && after && before !== after && sameValue(before, after))
|
|
28
|
+
nextParts[message.id] = before;
|
|
29
|
+
}
|
|
30
|
+
const unchanged = previous !== undefined &&
|
|
31
|
+
previous.length === shared.length &&
|
|
32
|
+
shared.every((message, index) => message === previous[index]);
|
|
33
|
+
return unchanged ? previous : shared;
|
|
34
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Message, Event as RuntimeEvent, Part, SessionStatus, Todo } from "../../core/runtime/runtime-types.js";
|
|
2
|
+
import type { SessionLogPage } from "../../core/session-log/source.js";
|
|
3
|
+
import { type SessionLogEventInput, type SessionLogState } from "../../core/session-log/store.js";
|
|
2
4
|
import { ascendingId } from "./sync-store/ascending-id.js";
|
|
3
5
|
import { Binary } from "./sync-store/binary.js";
|
|
4
6
|
import type { FileDiff, MessageError, MessageWithParts, SessionRewindState } from "./sync-store/types.js";
|
|
@@ -16,7 +18,11 @@ interface SyncState {
|
|
|
16
18
|
todos: Record<string, Todo[]>;
|
|
17
19
|
sessionRevert: Record<string, SessionRewindState | null>;
|
|
18
20
|
sessionRevertNeedsTailReconcile: Record<string, boolean>;
|
|
21
|
+
sessionLogs: Record<string, SessionLogState>;
|
|
19
22
|
applyEvent: (event: RuntimeEvent) => void;
|
|
23
|
+
applySessionLogPage: (sessionId: string, page: SessionLogPage) => void;
|
|
24
|
+
applySessionLogEvent: (sessionId: string, event: SessionLogEventInput) => void;
|
|
25
|
+
dropSessionLog: (sessionId: string) => void;
|
|
20
26
|
upsertMessage: (sessionID: string, message: Message) => void;
|
|
21
27
|
removeMessage: (sessionID: string, messageID: string) => void;
|
|
22
28
|
upsertPart: (messageID: string, part: Part, sessionID?: string) => void;
|
|
@@ -62,4 +68,5 @@ interface SyncState {
|
|
|
62
68
|
statuses: Record<string, SessionStatus>;
|
|
63
69
|
}
|
|
64
70
|
export declare function hasOnlyCacheSourcedMessages(sessionID: string): boolean;
|
|
71
|
+
export declare function hasRuntimeConfirmedMessage(sessionID: string, messageID: string): boolean;
|
|
65
72
|
export declare const useSyncStore: import("zustand").UseBoundStore<import("zustand").StoreApi<SyncState>>;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { create } from "zustand";
|
|
3
3
|
import { commitSessionRewind, isWithinRewindWindow, stageSessionRewind, } from "../../core/session/rewind.js";
|
|
4
|
+
import { EMPTY_SESSION_LOG_STATE, applySessionLogEvent, applySessionLogPage, } from "../../core/session-log/store.js";
|
|
4
5
|
import { isRetryableTurnError } from "../../core/turns/open-turn.js";
|
|
5
6
|
import { ascendingId } from "./sync-store/ascending-id.js";
|
|
6
7
|
import { Binary } from "./sync-store/binary.js";
|
|
7
8
|
import { DELTA_EVENT_TAIL_LIMIT } from "./sync-store/delta-event-window.js";
|
|
8
9
|
import { reconcileHydratedParts } from "./sync-store/reconcile-parts.js";
|
|
10
|
+
import { keepUnchanged } from "./sync-store/keep-unchanged.js";
|
|
9
11
|
export { ascendingId, Binary };
|
|
10
12
|
export function sameSessionStatus(a, b) {
|
|
11
13
|
if (a === b)
|
|
@@ -81,6 +83,16 @@ export function hasOnlyCacheSourcedMessages(sessionID) {
|
|
|
81
83
|
return false;
|
|
82
84
|
return messages.every((message) => cached.has(message.id));
|
|
83
85
|
}
|
|
86
|
+
export function hasRuntimeConfirmedMessage(sessionID, messageID) {
|
|
87
|
+
const messages = useSyncStore.getState().messages[sessionID];
|
|
88
|
+
if (!messages)
|
|
89
|
+
return false;
|
|
90
|
+
let ids = messageIdSets.get(messages);
|
|
91
|
+
if (!ids)
|
|
92
|
+
messageIdSets.set(messages, (ids = new Set(messages.map((message) => message.id))));
|
|
93
|
+
return ids.has(messageID) && !cacheSourcedIds.get(sessionID)?.has(messageID);
|
|
94
|
+
}
|
|
95
|
+
const messageIdSets = new WeakMap();
|
|
84
96
|
function recordOptimisticEcho(sessionID, optimisticID, echoID) {
|
|
85
97
|
if (optimisticID === echoID)
|
|
86
98
|
return;
|
|
@@ -138,6 +150,7 @@ function forgetSessionIds(sessionID) {
|
|
|
138
150
|
deltaActiveParts.delete(sessionID);
|
|
139
151
|
bridgedPartIds.delete(sessionID);
|
|
140
152
|
deltaEventTails.delete(sessionID);
|
|
153
|
+
deltaOffsets.delete(sessionID);
|
|
141
154
|
stubAssistantIds.delete(sessionID);
|
|
142
155
|
}
|
|
143
156
|
const isOptimistic = (sessionID, messageID) => hasTrackedId(optimisticIds, sessionID, messageID);
|
|
@@ -160,6 +173,15 @@ const awaitsUserEcho = (sessionID, messageID) => {
|
|
|
160
173
|
const bridgedPartIds = new Map();
|
|
161
174
|
const deltaActiveParts = new Map();
|
|
162
175
|
const deltaEventTails = new Map();
|
|
176
|
+
const deltaOffsets = new Map();
|
|
177
|
+
function setDeltaOffset(sessionID, key, offset) {
|
|
178
|
+
let bucket = deltaOffsets.get(sessionID);
|
|
179
|
+
if (!bucket) {
|
|
180
|
+
bucket = new Map();
|
|
181
|
+
deltaOffsets.set(sessionID, bucket);
|
|
182
|
+
}
|
|
183
|
+
bucket.set(key, offset);
|
|
184
|
+
}
|
|
163
185
|
const stubAssistantIds = new Map();
|
|
164
186
|
function trackStub(sessionID, stubId, parentId) {
|
|
165
187
|
const bucket = stubAssistantIds.get(sessionID);
|
|
@@ -223,6 +245,7 @@ const DETACHED_SESSION_LIMIT = 8;
|
|
|
223
245
|
const MESSAGE_ROWS_LIMIT = 20;
|
|
224
246
|
const sessionMessageRows = new Map();
|
|
225
247
|
const EMPTY_MESSAGE_ROWS = [];
|
|
248
|
+
const rowsByMessages = new WeakMap();
|
|
226
249
|
function touchSessionMessageRows(sessionID, entry) {
|
|
227
250
|
sessionMessageRows.delete(sessionID);
|
|
228
251
|
sessionMessageRows.set(sessionID, entry);
|
|
@@ -280,6 +303,41 @@ function pruneDetachedSessions(messages) {
|
|
|
280
303
|
}
|
|
281
304
|
return evicted;
|
|
282
305
|
}
|
|
306
|
+
function takeDeltaEventId(sessionID, tailKey, id) {
|
|
307
|
+
let sessionTails = deltaEventTails.get(sessionID);
|
|
308
|
+
if (!sessionTails) {
|
|
309
|
+
sessionTails = new Map();
|
|
310
|
+
deltaEventTails.set(sessionID, sessionTails);
|
|
311
|
+
}
|
|
312
|
+
let appliedIds = sessionTails.get(tailKey);
|
|
313
|
+
if (appliedIds?.has(id))
|
|
314
|
+
return false;
|
|
315
|
+
if (!appliedIds) {
|
|
316
|
+
appliedIds = new Set();
|
|
317
|
+
sessionTails.set(tailKey, appliedIds);
|
|
318
|
+
}
|
|
319
|
+
appliedIds.add(id);
|
|
320
|
+
if (appliedIds.size > DELTA_EVENT_TAIL_LIMIT) {
|
|
321
|
+
const oldest = appliedIds.values().next().value;
|
|
322
|
+
if (oldest !== undefined)
|
|
323
|
+
appliedIds.delete(oldest);
|
|
324
|
+
}
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
function applySessionLogFrame(sessionId, event) {
|
|
328
|
+
const state = useSyncStore.getState();
|
|
329
|
+
const current = state.sessionLogs[sessionId] ?? EMPTY_SESSION_LOG_STATE;
|
|
330
|
+
const next = applySessionLogEvent(current, event);
|
|
331
|
+
if (next === current)
|
|
332
|
+
return;
|
|
333
|
+
if (event.type === "log.block.delta") {
|
|
334
|
+
const p = event.properties;
|
|
335
|
+
if (event.id && !takeDeltaEventId(p.thread_id, `${p.message_id}:${p.block_id}:log`, event.id))
|
|
336
|
+
return;
|
|
337
|
+
state.noteSessionActivity(p.thread_id);
|
|
338
|
+
}
|
|
339
|
+
useSyncStore.setState((s) => ({ sessionLogs: { ...s.sessionLogs, [sessionId]: next } }));
|
|
340
|
+
}
|
|
283
341
|
function appendPartDeltas(sessionID, messageID, partID, field, pieces) {
|
|
284
342
|
trackId(deltaActiveParts, sessionID, partID);
|
|
285
343
|
let applied = false;
|
|
@@ -290,38 +348,38 @@ function appendPartDeltas(sessionID, messageID, partID, field, pieces) {
|
|
|
290
348
|
const result = Binary.search(list, partID, (p) => p.id);
|
|
291
349
|
if (!result.found)
|
|
292
350
|
return s;
|
|
293
|
-
|
|
351
|
+
const messages = s.messages[sessionID] ?? [];
|
|
352
|
+
let message;
|
|
353
|
+
for (let i = messages.length - 1; i >= 0 && !message; i--)
|
|
354
|
+
if (messages[i].id === messageID)
|
|
355
|
+
message = messages[i];
|
|
356
|
+
if (message?.role === "assistant" && message.time?.completed)
|
|
357
|
+
return s;
|
|
358
|
+
const existing = list[result.index][field] ?? "";
|
|
359
|
+
const offsetKey = `${messageID}:${partID}:${field}`;
|
|
360
|
+
let offset = deltaOffsets.get(sessionID)?.get(offsetKey) ?? existing.length;
|
|
361
|
+
let text = existing;
|
|
294
362
|
for (const piece of pieces) {
|
|
295
|
-
if (piece.id) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
sessionTails = new Map();
|
|
300
|
-
deltaEventTails.set(sessionID, sessionTails);
|
|
301
|
-
}
|
|
302
|
-
let appliedIds = sessionTails.get(tailKey);
|
|
303
|
-
if (appliedIds?.has(piece.id))
|
|
304
|
-
continue;
|
|
305
|
-
if (!appliedIds) {
|
|
306
|
-
appliedIds = new Set();
|
|
307
|
-
sessionTails.set(tailKey, appliedIds);
|
|
308
|
-
}
|
|
309
|
-
appliedIds.add(piece.id);
|
|
310
|
-
if (appliedIds.size > DELTA_EVENT_TAIL_LIMIT) {
|
|
311
|
-
const oldest = appliedIds.values().next().value;
|
|
312
|
-
if (oldest !== undefined)
|
|
313
|
-
appliedIds.delete(oldest);
|
|
314
|
-
}
|
|
363
|
+
if (piece.id && !takeDeltaEventId(sessionID, offsetKey, piece.id)) {
|
|
364
|
+
if (offset < text.length && text.startsWith(piece.delta, offset))
|
|
365
|
+
offset += piece.delta.length;
|
|
366
|
+
continue;
|
|
315
367
|
}
|
|
316
|
-
|
|
368
|
+
if (offset < text.length && text.startsWith(piece.delta, offset)) {
|
|
369
|
+
offset += piece.delta.length;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const held = offset < text.length && piece.delta.startsWith(text.slice(offset)) ? text.length - offset : 0;
|
|
373
|
+
text += piece.delta.slice(held);
|
|
374
|
+
offset = text.length;
|
|
317
375
|
applied = true;
|
|
318
376
|
}
|
|
377
|
+
setDeltaOffset(sessionID, offsetKey, offset);
|
|
319
378
|
if (!applied)
|
|
320
379
|
return s;
|
|
321
380
|
const next = [...list];
|
|
322
381
|
const part = { ...next[result.index] };
|
|
323
|
-
|
|
324
|
-
part[field] = (existing ?? "") + delta;
|
|
382
|
+
part[field] = text;
|
|
325
383
|
next[result.index] = part;
|
|
326
384
|
return { parts: { ...s.parts, [messageID]: next } };
|
|
327
385
|
});
|
|
@@ -339,6 +397,7 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
339
397
|
todos: {},
|
|
340
398
|
sessionRevert: {},
|
|
341
399
|
sessionRevertNeedsTailReconcile: {},
|
|
400
|
+
sessionLogs: {},
|
|
342
401
|
get statuses() {
|
|
343
402
|
return get().sessionStatus;
|
|
344
403
|
},
|
|
@@ -346,15 +405,15 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
346
405
|
const list = s.messages[sessionID] ?? [];
|
|
347
406
|
const result = Binary.search(list, message.id, (m) => m.id);
|
|
348
407
|
const bsValid = result.found && list[result.index]?.id === message.id;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
408
|
+
const index = bsValid ? result.index : list.findIndex((m) => m.id === message.id);
|
|
409
|
+
if (index !== -1) {
|
|
410
|
+
const previous = list[index];
|
|
411
|
+
const completed = previous.role === "assistant" ? previous.time?.completed : undefined;
|
|
412
|
+
const kept = completed && message.role === "assistant" && message.time && !message.time.completed
|
|
413
|
+
? { ...message, time: { ...message.time, completed } }
|
|
414
|
+
: message;
|
|
356
415
|
const next = [...list];
|
|
357
|
-
next[
|
|
416
|
+
next[index] = kept;
|
|
358
417
|
return { messages: { ...s.messages, [sessionID]: next } };
|
|
359
418
|
}
|
|
360
419
|
const next = [...list];
|
|
@@ -405,6 +464,9 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
405
464
|
prevText.length > 0) {
|
|
406
465
|
const isPrefixGrowth = incomingText.startsWith(prevText);
|
|
407
466
|
if (!isPrefixGrowth) {
|
|
467
|
+
if (partSessionID && prevText.startsWith(incomingText)) {
|
|
468
|
+
setDeltaOffset(partSessionID, `${messageID}:${part.id}:text`, incomingText.length);
|
|
469
|
+
}
|
|
408
470
|
if (!bridgeCleared)
|
|
409
471
|
return s;
|
|
410
472
|
const next = [...list];
|
|
@@ -413,9 +475,15 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
413
475
|
}
|
|
414
476
|
}
|
|
415
477
|
}
|
|
478
|
+
const followStream = () => {
|
|
479
|
+
if (partSessionID && isTextLikePart(part)) {
|
|
480
|
+
setDeltaOffset(partSessionID, `${messageID}:${part.id}:text`, part.text.length);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
416
483
|
const next = [...list];
|
|
417
484
|
if (result.found) {
|
|
418
485
|
next[result.index] = part;
|
|
486
|
+
followStream();
|
|
419
487
|
}
|
|
420
488
|
else {
|
|
421
489
|
if (hasTrackedId(deltaActiveParts, partSessionID, part.id) && isTextLikePart(part)) {
|
|
@@ -427,6 +495,7 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
427
495
|
}
|
|
428
496
|
}
|
|
429
497
|
next.splice(result.index, 0, part);
|
|
498
|
+
followStream();
|
|
430
499
|
}
|
|
431
500
|
return { parts: { ...s.parts, [messageID]: next } };
|
|
432
501
|
}),
|
|
@@ -729,7 +798,7 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
729
798
|
buildSessionMessages: (sessionID, msgs, parts) => {
|
|
730
799
|
if (!msgs || msgs.length === 0)
|
|
731
800
|
return EMPTY_MESSAGE_ROWS;
|
|
732
|
-
const cached = sessionMessageRows.get(sessionID);
|
|
801
|
+
const cached = rowsByMessages.get(msgs) ?? sessionMessageRows.get(sessionID);
|
|
733
802
|
if (cached && cached.msgs === msgs) {
|
|
734
803
|
let same = cached.partRefs.length === msgs.length;
|
|
735
804
|
if (same) {
|
|
@@ -775,7 +844,9 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
775
844
|
}
|
|
776
845
|
result.push(reuse ?? { info, parts: messageParts ?? [] });
|
|
777
846
|
}
|
|
778
|
-
|
|
847
|
+
const entry = { msgs, partRefs, result };
|
|
848
|
+
touchSessionMessageRows(sessionID, entry);
|
|
849
|
+
rowsByMessages.set(msgs, entry);
|
|
779
850
|
return result;
|
|
780
851
|
},
|
|
781
852
|
hydrate: (sessionID, msgs, opts) => {
|
|
@@ -1008,8 +1079,9 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
1008
1079
|
clearBridge: (id) => untrackId(bridgedPartIds, sessionID, id),
|
|
1009
1080
|
isDeltaActive: (id) => hasTrackedId(deltaActiveParts, sessionID, id),
|
|
1010
1081
|
});
|
|
1082
|
+
const kept = keepUnchanged(s.messages[sessionID], merged, s.parts, newParts);
|
|
1011
1083
|
return {
|
|
1012
|
-
messages: { ...s.messages, [sessionID]:
|
|
1084
|
+
messages: kept === s.messages[sessionID] ? s.messages : { ...s.messages, [sessionID]: kept },
|
|
1013
1085
|
parts: newParts,
|
|
1014
1086
|
};
|
|
1015
1087
|
});
|
|
@@ -1025,6 +1097,7 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
1025
1097
|
bridgedPartIds.clear();
|
|
1026
1098
|
deltaActiveParts.clear();
|
|
1027
1099
|
deltaEventTails.clear();
|
|
1100
|
+
deltaOffsets.clear();
|
|
1028
1101
|
stubAssistantIds.clear();
|
|
1029
1102
|
sessionConsumers.clear();
|
|
1030
1103
|
detachedSessions.clear();
|
|
@@ -1041,8 +1114,21 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
1041
1114
|
todos: {},
|
|
1042
1115
|
sessionRevert: {},
|
|
1043
1116
|
sessionRevertNeedsTailReconcile: {},
|
|
1117
|
+
sessionLogs: {},
|
|
1044
1118
|
});
|
|
1045
1119
|
},
|
|
1120
|
+
applySessionLogPage: (sessionId, page) => set((s) => {
|
|
1121
|
+
const current = s.sessionLogs[sessionId] ?? EMPTY_SESSION_LOG_STATE;
|
|
1122
|
+
const next = applySessionLogPage(current, { ...page, session: { session_id: sessionId, ...page.session } });
|
|
1123
|
+
return next === current ? s : { sessionLogs: { ...s.sessionLogs, [sessionId]: next } };
|
|
1124
|
+
}),
|
|
1125
|
+
applySessionLogEvent: (sessionId, event) => applySessionLogFrame(sessionId, event),
|
|
1126
|
+
dropSessionLog: (sessionId) => set((s) => {
|
|
1127
|
+
if (!(sessionId in s.sessionLogs))
|
|
1128
|
+
return s;
|
|
1129
|
+
const { [sessionId]: _dropped, ...rest } = s.sessionLogs;
|
|
1130
|
+
return { sessionLogs: rest };
|
|
1131
|
+
}),
|
|
1046
1132
|
hasOptimisticMessages: (sessionID) => {
|
|
1047
1133
|
const list = get().messages[sessionID];
|
|
1048
1134
|
if (!list || list.length === 0)
|
|
@@ -1252,6 +1338,7 @@ export const useSyncStore = create()((set, get) => ({
|
|
|
1252
1338
|
}
|
|
1253
1339
|
deltaActiveParts.delete(sid);
|
|
1254
1340
|
deltaEventTails.delete(sid);
|
|
1341
|
+
deltaOffsets.delete(sid);
|
|
1255
1342
|
set((s) => {
|
|
1256
1343
|
const msgs = s.messages[sid] ?? [];
|
|
1257
1344
|
let userIdx = -1;
|
|
@@ -21,6 +21,10 @@ export interface PromptAttachmentControllerOptions {
|
|
|
21
21
|
export interface PromptAttachmentController {
|
|
22
22
|
add(file: File): string;
|
|
23
23
|
addMany(files: readonly File[]): string[];
|
|
24
|
+
restore(uploads: readonly {
|
|
25
|
+
attachment: PromptAttachment;
|
|
26
|
+
file?: File;
|
|
27
|
+
}[]): (string | undefined)[];
|
|
24
28
|
retry(id: string): void;
|
|
25
29
|
remove(id: string): Promise<void>;
|
|
26
30
|
abort(id: string): void;
|
|
@@ -216,6 +216,39 @@ export function createPromptAttachmentController(projectId, options = {}) {
|
|
|
216
216
|
pump();
|
|
217
217
|
return ids;
|
|
218
218
|
}
|
|
219
|
+
function restore(uploads) {
|
|
220
|
+
const valid = uploads.map(({ attachment, file }) => {
|
|
221
|
+
try {
|
|
222
|
+
return { attachment: completedMetadata(attachment), file };
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
validateSizes(valid.flatMap((upload) => (upload ? [upload.attachment.size] : [])));
|
|
229
|
+
const ids = valid.map((upload) => {
|
|
230
|
+
if (!upload)
|
|
231
|
+
return undefined;
|
|
232
|
+
const id = `attachment-${++lastAttachmentId}`;
|
|
233
|
+
const { attachment, file } = upload;
|
|
234
|
+
entries.set(id, {
|
|
235
|
+
generation: 0,
|
|
236
|
+
item: {
|
|
237
|
+
id,
|
|
238
|
+
...(file ? { file } : {}),
|
|
239
|
+
filename: attachment.filename,
|
|
240
|
+
mime: attachment.mime,
|
|
241
|
+
size: attachment.size,
|
|
242
|
+
status: 'ready',
|
|
243
|
+
receivedBytes: attachment.size,
|
|
244
|
+
attachment,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
return id;
|
|
248
|
+
});
|
|
249
|
+
emit();
|
|
250
|
+
return ids;
|
|
251
|
+
}
|
|
219
252
|
function abort(id) {
|
|
220
253
|
const entry = entries.get(id);
|
|
221
254
|
if (!entry || entry.item.status === 'ready')
|
|
@@ -284,6 +317,7 @@ export function createPromptAttachmentController(projectId, options = {}) {
|
|
|
284
317
|
return {
|
|
285
318
|
add: (file) => addMany([file])[0],
|
|
286
319
|
addMany,
|
|
320
|
+
restore,
|
|
287
321
|
retry(id) {
|
|
288
322
|
const entry = entries.get(id);
|
|
289
323
|
if (!entry?.handedOff)
|