@opengeni/db 0.28.9 → 0.36.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/attached-browser-devices.d.ts +42 -0
- package/dist/attempt-tool-catalogs.d.ts +16 -0
- package/dist/browser-auth.d.ts +299 -0
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +137 -0
- package/dist/browser-sessions.d.ts +354 -0
- package/dist/browser-state-artifacts.d.ts +91 -0
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-components.d.ts +37 -0
- package/dist/capability-integrations.d.ts +194 -0
- package/dist/chunk-5KTIEDX7.js +13200 -0
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/chunk-CQZ4QSP5.js +995 -0
- package/dist/chunk-CQZ4QSP5.js.map +1 -0
- package/dist/chunk-F32YT7MP.js +117 -0
- package/dist/chunk-F32YT7MP.js.map +1 -0
- package/dist/chunk-JC5QPLUM.js +666 -0
- package/dist/chunk-JC5QPLUM.js.map +1 -0
- package/dist/chunk-P3RE4D2B.js +3535 -0
- package/dist/chunk-P3RE4D2B.js.map +1 -0
- package/dist/chunk-TAUTWOS5.js +259 -0
- package/dist/chunk-TAUTWOS5.js.map +1 -0
- package/dist/chunk-VKLB2ZGI.js +2389 -0
- package/dist/chunk-VKLB2ZGI.js.map +1 -0
- package/dist/codemode-operations.d.ts +117 -0
- package/dist/codex-token-resolver.d.ts +2 -2
- package/dist/company-profile-schema.d.ts +798 -0
- package/dist/company-profile.d.ts +268 -0
- package/dist/computer-sessions.d.ts +109 -0
- package/dist/connection-token-resolver.d.ts +9 -4
- package/dist/database.d.ts +57 -3
- package/dist/editable-artifact-durable-export.d.ts +153 -0
- package/dist/editable-artifact-durable-export.js +788 -0
- package/dist/editable-artifact-durable-export.js.map +1 -0
- package/dist/editable-artifact-materialization.d.ts +123 -0
- package/dist/editable-artifacts-schema.d.ts +4152 -0
- package/dist/editable-artifacts.d.ts +610 -0
- package/dist/editable-artifacts.js +26 -0
- package/dist/editable-artifacts.js.map +1 -0
- package/dist/generated-images.d.ts +139 -0
- package/dist/index.d.ts +1122 -126
- package/dist/index.js +42329 -18407
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +73 -10
- package/dist/integration-bindings.d.ts +70 -0
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +14 -0
- package/dist/interaction-schema.d.ts +6036 -0
- package/dist/knowledge-source-sync-schema.d.ts +1679 -0
- package/dist/knowledge-source-sync.d.ts +274 -0
- package/dist/memory-governance.d.ts +2 -2
- package/dist/memory-slack-delivery.d.ts +101 -0
- package/dist/new-session-drafts.d.ts +2 -2
- package/dist/pack-components.d.ts +76 -0
- package/dist/pack-installations.d.ts +86 -0
- package/dist/persistence-errors.d.ts +3 -3
- package/dist/plugin-packages.d.ts +126 -0
- package/dist/preference-registry.d.ts +1 -1
- package/dist/provision-roles.d.ts +9 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +19 -6
- package/dist/schema.d.ts +22654 -9359
- package/dist/schema.js +209 -3
- package/dist/scoped-knowledge.d.ts +97 -1
- package/dist/session-control.d.ts +33 -7
- package/dist/session-queue-commands.d.ts +19 -11
- package/dist/session-realtime-context.d.ts +2 -2
- package/dist/session-realtime-ledger.d.ts +4 -4
- package/dist/session-realtime-mirror.d.ts +2 -2
- package/dist/session-realtime-state.d.ts +1 -1
- package/dist/session-realtime-terminal.d.ts +1 -1
- package/dist/session-realtime.d.ts +7 -7
- package/dist/session-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/session-tool-call-settlement.d.ts +1 -1
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/slack-user-link-access.d.ts +54 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/transcription-recordings.d.ts +2 -2
- package/dist/turn-initiator.d.ts +2 -2
- package/dist/video-generation.d.ts +225 -0
- package/dist/video-generation.js +68 -0
- package/dist/video-generation.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +1 -1
- package/dist/workspace-instruction-policies.d.ts +1 -1
- package/dist/workspace-learning-policy-schema.d.ts +932 -0
- package/dist/workspace-learning-policy.d.ts +161 -0
- package/dist/xai-subscription.d.ts +326 -0
- package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
- package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
- package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
- package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
- package/drizzle/0187_generated_image_artifacts.sql +173 -0
- package/drizzle/0188_image_generation_retention_failure.sql +25 -0
- package/drizzle/0189_retained_session_image_formats.sql +13 -0
- package/drizzle/0190_timeline_annotations.sql +31 -0
- package/drizzle/0191_editable_artifact_engine.sql +3853 -0
- package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
- package/drizzle/0193_editable_artifact_authorization.sql +190 -0
- package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
- package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
- package/drizzle/0196_rig_provider_images.sql +152 -0
- package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
- package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
- package/drizzle/0199_workspace_learning_policy.sql +980 -0
- package/drizzle/0201_company_profile_authority.sql +849 -0
- package/drizzle/0202_document_index_checkpoints.sql +85 -0
- package/drizzle/0203_durable_video_generation.sql +353 -0
- package/drizzle/0204_video_generation_funding.sql +44 -0
- package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
- package/drizzle/0206_browser_sessions.sql +646 -0
- package/drizzle/0207_browser_identities.sql +379 -0
- package/drizzle/0208_attached_browser_devices.sql +165 -0
- package/drizzle/0209_computer_sessions.sql +575 -0
- package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
- package/drizzle/0211_editable_artifact_session_links.sql +58 -0
- package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0212_slack_installation_bindings.sql +348 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0214_session_activity_commit_gate.sql +262 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0215_capabilities_platform.sql +980 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0216_pack_component_ownership.sql +235 -0
- package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +26 -5
- package/src/attached-browser-devices.ts +371 -0
- package/src/attempt-tool-catalogs.ts +131 -0
- package/src/browser-auth.ts +4373 -0
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +1385 -0
- package/src/browser-sessions.ts +2294 -0
- package/src/browser-state-artifacts.ts +449 -0
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-components.ts +166 -0
- package/src/capability-integrations.ts +1571 -0
- package/src/codemode-operations.ts +674 -0
- package/src/company-profile-schema.ts +143 -0
- package/src/company-profile.ts +968 -0
- package/src/computer-sessions.ts +1130 -0
- package/src/connection-token-resolver.ts +270 -25
- package/src/database.ts +468 -24
- package/src/editable-artifact-durable-export.ts +1201 -0
- package/src/editable-artifact-materialization.ts +893 -0
- package/src/editable-artifacts-schema.ts +879 -0
- package/src/editable-artifacts.ts +5404 -0
- package/src/generated-images.ts +740 -0
- package/src/index.ts +9963 -1465
- package/src/insights.ts +251 -23
- package/src/integration-bindings.ts +455 -0
- package/src/integration-facets.ts +862 -0
- package/src/interaction-revisions.ts +70 -0
- package/src/interaction-schema.ts +1606 -0
- package/src/knowledge-source-sync-schema.ts +192 -0
- package/src/knowledge-source-sync.ts +1922 -0
- package/src/memory-slack-delivery.ts +801 -0
- package/src/pack-components.ts +954 -0
- package/src/pack-installations.ts +886 -0
- package/src/persistence-errors.ts +4 -7
- package/src/plugin-packages.ts +1186 -0
- package/src/provision-roles.ts +534 -1
- package/src/runtime-posture-cli.ts +9 -0
- package/src/runtime-posture.ts +721 -5
- package/src/schema.ts +6270 -3159
- package/src/scoped-knowledge-schema.ts +1 -1
- package/src/scoped-knowledge.ts +655 -3
- package/src/session-control.ts +130 -42
- package/src/session-queue-commands.ts +265 -15
- package/src/session-realtime-context.ts +2 -2
- package/src/session-realtime-ledger.ts +3 -3
- package/src/session-realtime.ts +7 -7
- package/src/session-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +18 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/slack-user-link-access.ts +702 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +1530 -0
- package/src/workspace-artifacts.ts +1 -1
- package/src/workspace-learning-policy-schema.ts +139 -0
- package/src/workspace-learning-policy.ts +595 -0
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-P6CUHXQZ.js +0 -7248
- package/dist/chunk-P6CUHXQZ.js.map +0 -1
- package/dist/chunk-VHBFHMPC.js +0 -1219
- package/dist/chunk-VHBFHMPC.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -34,6 +34,26 @@
|
|
|
34
34
|
"./provision-roles": {
|
|
35
35
|
"types": "./dist/provision-roles.d.ts",
|
|
36
36
|
"import": "./dist/provision-roles.js"
|
|
37
|
+
},
|
|
38
|
+
"./editable-artifacts": {
|
|
39
|
+
"types": "./dist/editable-artifacts.d.ts",
|
|
40
|
+
"import": "./dist/editable-artifacts.js"
|
|
41
|
+
},
|
|
42
|
+
"./editable-artifact-durable-export": {
|
|
43
|
+
"types": "./dist/editable-artifact-durable-export.d.ts",
|
|
44
|
+
"import": "./dist/editable-artifact-durable-export.js"
|
|
45
|
+
},
|
|
46
|
+
"./video-generation": {
|
|
47
|
+
"types": "./dist/video-generation.d.ts",
|
|
48
|
+
"import": "./dist/video-generation.js"
|
|
49
|
+
},
|
|
50
|
+
"./canonical-human-identities": {
|
|
51
|
+
"types": "./dist/canonical-human-identities.d.ts",
|
|
52
|
+
"import": "./dist/canonical-human-identities.js"
|
|
53
|
+
},
|
|
54
|
+
"./session-tenancy": {
|
|
55
|
+
"types": "./dist/session-tenancy.d.ts",
|
|
56
|
+
"import": "./dist/session-tenancy.js"
|
|
37
57
|
}
|
|
38
58
|
},
|
|
39
59
|
"publishConfig": {
|
|
@@ -50,10 +70,11 @@
|
|
|
50
70
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
51
71
|
},
|
|
52
72
|
"dependencies": {
|
|
53
|
-
"@opengeni/
|
|
54
|
-
"@opengeni/
|
|
55
|
-
"@opengeni/
|
|
56
|
-
"@opengeni/
|
|
73
|
+
"@opengeni/codemode": "^0.4.1",
|
|
74
|
+
"@opengeni/codex": "^0.2.16",
|
|
75
|
+
"@opengeni/config": "^0.16.1",
|
|
76
|
+
"@opengeni/contracts": "^0.50.0",
|
|
77
|
+
"@opengeni/network": "^0.2.2",
|
|
57
78
|
"drizzle-orm": "^0.45.2",
|
|
58
79
|
"postgres": "^3.4.7"
|
|
59
80
|
},
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AttachedBrowserDevice,
|
|
3
|
+
AttachedBrowserDeviceCapabilities,
|
|
4
|
+
AttachedBrowserDeviceListResponse,
|
|
5
|
+
AttachedBrowserInventorySnapshot,
|
|
6
|
+
type AttachedBrowserDevice as AttachedBrowserDeviceValue,
|
|
7
|
+
type AttachedBrowserDeviceAnnouncement,
|
|
8
|
+
type AttachedBrowserDeviceListResponse as AttachedBrowserDeviceListResponseValue,
|
|
9
|
+
type AttachedBrowserInventorySnapshot as AttachedBrowserInventorySnapshotValue,
|
|
10
|
+
} from "@opengeni/contracts";
|
|
11
|
+
import { and, asc, desc, eq, inArray, isNull } from "drizzle-orm";
|
|
12
|
+
import { type Database, withRlsContext } from "./database";
|
|
13
|
+
import {
|
|
14
|
+
advanceWorkspaceInteractionRevision,
|
|
15
|
+
readWorkspaceInteractionRevision,
|
|
16
|
+
} from "./interaction-revisions";
|
|
17
|
+
import * as schema from "./schema";
|
|
18
|
+
|
|
19
|
+
type DeviceRow = typeof schema.attachedBrowserDevices.$inferSelect;
|
|
20
|
+
|
|
21
|
+
const CONSISTENT_READ = {
|
|
22
|
+
isolationLevel: "repeatable read",
|
|
23
|
+
accessMode: "read only",
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export class AttachedBrowserDeviceNotFoundError extends Error {
|
|
27
|
+
readonly name = "AttachedBrowserDeviceNotFoundError";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class AttachedBrowserInventoryConflictError extends Error {
|
|
31
|
+
readonly name = "AttachedBrowserInventoryConflictError";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type ReconcileAttachedBrowserInventoryResult = {
|
|
35
|
+
accepted: boolean;
|
|
36
|
+
changed: boolean;
|
|
37
|
+
revision: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function safeInteger(value: number, label: string): number {
|
|
41
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label} is outside safe range`);
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function deviceFromRow(row: DeviceRow): AttachedBrowserDeviceValue {
|
|
46
|
+
return AttachedBrowserDevice.parse({
|
|
47
|
+
id: row.id,
|
|
48
|
+
accountId: row.accountId,
|
|
49
|
+
workspaceId: row.workspaceId,
|
|
50
|
+
enrollmentId: row.enrollmentId,
|
|
51
|
+
name: row.name,
|
|
52
|
+
profileLabel: row.profileLabel,
|
|
53
|
+
browserName: row.browserName,
|
|
54
|
+
browserVersion: row.browserVersion,
|
|
55
|
+
extensionVersion: row.extensionVersion,
|
|
56
|
+
platform: row.platform,
|
|
57
|
+
architecture: row.architecture,
|
|
58
|
+
state: row.disconnectedAt ? "disconnected" : "connected",
|
|
59
|
+
connectionGeneration: row.connectionGeneration,
|
|
60
|
+
inventoryRevision: safeInteger(row.inventoryRevision, "attached browser inventory revision"),
|
|
61
|
+
tabCount: safeInteger(row.tabCount, "attached browser tab count"),
|
|
62
|
+
capabilities: row.capabilities,
|
|
63
|
+
lastSeenAt: row.lastSeenAt.toISOString(),
|
|
64
|
+
disconnectedAt: row.disconnectedAt?.toISOString() ?? null,
|
|
65
|
+
createdAt: row.createdAt.toISOString(),
|
|
66
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sameAnnouncement(row: DeviceRow, value: AttachedBrowserDeviceAnnouncement): boolean {
|
|
71
|
+
return (
|
|
72
|
+
row.name === value.name &&
|
|
73
|
+
row.profileLabel === value.profileLabel &&
|
|
74
|
+
row.browserName === value.browserName &&
|
|
75
|
+
row.browserVersion === value.browserVersion &&
|
|
76
|
+
row.extensionVersion === value.extensionVersion &&
|
|
77
|
+
row.platform === value.platform &&
|
|
78
|
+
row.architecture === value.architecture &&
|
|
79
|
+
row.connectionGeneration === value.connectionGeneration &&
|
|
80
|
+
row.inventoryRevision === value.inventoryRevision &&
|
|
81
|
+
row.tabCount === value.tabCount &&
|
|
82
|
+
JSON.stringify(AttachedBrowserDeviceCapabilities.parse(row.capabilities)) ===
|
|
83
|
+
JSON.stringify(value.capabilities) &&
|
|
84
|
+
row.disconnectedAt === null
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Reconcile one full browser-bridge snapshot. The enrollment row serializes
|
|
89
|
+
* concurrent heartbeats; a lower revision from the same bridge generation is
|
|
90
|
+
* ignored, and a replay only refreshes liveness. */
|
|
91
|
+
export async function reconcileAttachedBrowserInventory(
|
|
92
|
+
db: Database,
|
|
93
|
+
input: {
|
|
94
|
+
accountId: string;
|
|
95
|
+
workspaceId: string;
|
|
96
|
+
enrollmentId: string;
|
|
97
|
+
snapshot: AttachedBrowserInventorySnapshotValue;
|
|
98
|
+
},
|
|
99
|
+
): Promise<ReconcileAttachedBrowserInventoryResult> {
|
|
100
|
+
const snapshot = AttachedBrowserInventorySnapshot.parse(input.snapshot);
|
|
101
|
+
return await withRlsContext(
|
|
102
|
+
db,
|
|
103
|
+
input,
|
|
104
|
+
async (scopedDb) =>
|
|
105
|
+
await scopedDb.transaction(async (rawTx) => {
|
|
106
|
+
const tx = rawTx as unknown as Database;
|
|
107
|
+
const [enrollment] = await tx
|
|
108
|
+
.select({
|
|
109
|
+
id: schema.enrollments.id,
|
|
110
|
+
accountId: schema.enrollments.accountId,
|
|
111
|
+
workspaceId: schema.enrollments.workspaceId,
|
|
112
|
+
status: schema.enrollments.status,
|
|
113
|
+
})
|
|
114
|
+
.from(schema.enrollments)
|
|
115
|
+
.where(
|
|
116
|
+
and(
|
|
117
|
+
eq(schema.enrollments.id, input.enrollmentId),
|
|
118
|
+
eq(schema.enrollments.accountId, input.accountId),
|
|
119
|
+
eq(schema.enrollments.workspaceId, input.workspaceId),
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
.for("update")
|
|
123
|
+
.limit(1);
|
|
124
|
+
if (!enrollment || enrollment.status !== "active") {
|
|
125
|
+
throw new AttachedBrowserInventoryConflictError(
|
|
126
|
+
"attached browser inventory belongs to an unavailable enrollment",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const [cursor] = await tx
|
|
131
|
+
.select()
|
|
132
|
+
.from(schema.attachedBrowserInventories)
|
|
133
|
+
.where(
|
|
134
|
+
and(
|
|
135
|
+
eq(schema.attachedBrowserInventories.workspaceId, input.workspaceId),
|
|
136
|
+
eq(schema.attachedBrowserInventories.enrollmentId, input.enrollmentId),
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
.for("update")
|
|
140
|
+
.limit(1);
|
|
141
|
+
if (
|
|
142
|
+
cursor?.bridgeGeneration === snapshot.bridgeGeneration &&
|
|
143
|
+
snapshot.revision < cursor.revision
|
|
144
|
+
) {
|
|
145
|
+
return {
|
|
146
|
+
accepted: false,
|
|
147
|
+
changed: false,
|
|
148
|
+
revision: await readWorkspaceInteractionRevision(tx, input.workspaceId),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const now = new Date();
|
|
153
|
+
const rows = await tx
|
|
154
|
+
.select()
|
|
155
|
+
.from(schema.attachedBrowserDevices)
|
|
156
|
+
.where(eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId))
|
|
157
|
+
.for("update");
|
|
158
|
+
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
159
|
+
let changed = false;
|
|
160
|
+
for (const device of snapshot.devices) {
|
|
161
|
+
const existing = byId.get(device.id);
|
|
162
|
+
if (existing && existing.enrollmentId !== input.enrollmentId) {
|
|
163
|
+
throw new AttachedBrowserInventoryConflictError(
|
|
164
|
+
"attached browser device id is already owned by another enrollment",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (!existing) {
|
|
168
|
+
await tx.insert(schema.attachedBrowserDevices).values({
|
|
169
|
+
id: device.id,
|
|
170
|
+
accountId: input.accountId,
|
|
171
|
+
workspaceId: input.workspaceId,
|
|
172
|
+
enrollmentId: input.enrollmentId,
|
|
173
|
+
name: device.name,
|
|
174
|
+
profileLabel: device.profileLabel,
|
|
175
|
+
browserName: device.browserName,
|
|
176
|
+
browserVersion: device.browserVersion,
|
|
177
|
+
extensionVersion: device.extensionVersion,
|
|
178
|
+
platform: device.platform,
|
|
179
|
+
architecture: device.architecture,
|
|
180
|
+
connectionGeneration: device.connectionGeneration,
|
|
181
|
+
inventoryRevision: device.inventoryRevision,
|
|
182
|
+
tabCount: device.tabCount,
|
|
183
|
+
capabilities: device.capabilities,
|
|
184
|
+
lastSeenAt: now,
|
|
185
|
+
disconnectedAt: null,
|
|
186
|
+
createdAt: now,
|
|
187
|
+
updatedAt: now,
|
|
188
|
+
});
|
|
189
|
+
changed = true;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const materialChanged = !sameAnnouncement(existing, device);
|
|
193
|
+
await tx
|
|
194
|
+
.update(schema.attachedBrowserDevices)
|
|
195
|
+
.set({
|
|
196
|
+
name: device.name,
|
|
197
|
+
profileLabel: device.profileLabel,
|
|
198
|
+
browserName: device.browserName,
|
|
199
|
+
browserVersion: device.browserVersion,
|
|
200
|
+
extensionVersion: device.extensionVersion,
|
|
201
|
+
platform: device.platform,
|
|
202
|
+
architecture: device.architecture,
|
|
203
|
+
connectionGeneration: device.connectionGeneration,
|
|
204
|
+
inventoryRevision: device.inventoryRevision,
|
|
205
|
+
tabCount: device.tabCount,
|
|
206
|
+
capabilities: device.capabilities,
|
|
207
|
+
lastSeenAt: now,
|
|
208
|
+
disconnectedAt: null,
|
|
209
|
+
updatedAt: materialChanged ? now : existing.updatedAt,
|
|
210
|
+
})
|
|
211
|
+
.where(
|
|
212
|
+
and(
|
|
213
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
214
|
+
eq(schema.attachedBrowserDevices.id, device.id),
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
changed ||= materialChanged;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const announced = new Set(snapshot.devices.map((device) => device.id));
|
|
221
|
+
const disconnectedIds = rows
|
|
222
|
+
.filter(
|
|
223
|
+
(row) =>
|
|
224
|
+
row.enrollmentId === input.enrollmentId &&
|
|
225
|
+
row.disconnectedAt === null &&
|
|
226
|
+
!announced.has(row.id),
|
|
227
|
+
)
|
|
228
|
+
.map((row) => row.id);
|
|
229
|
+
if (disconnectedIds.length > 0) {
|
|
230
|
+
await tx
|
|
231
|
+
.update(schema.attachedBrowserDevices)
|
|
232
|
+
.set({ disconnectedAt: now, updatedAt: now })
|
|
233
|
+
.where(
|
|
234
|
+
and(
|
|
235
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
236
|
+
eq(schema.attachedBrowserDevices.enrollmentId, input.enrollmentId),
|
|
237
|
+
inArray(schema.attachedBrowserDevices.id, disconnectedIds),
|
|
238
|
+
isNull(schema.attachedBrowserDevices.disconnectedAt),
|
|
239
|
+
),
|
|
240
|
+
);
|
|
241
|
+
changed = true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (cursor) {
|
|
245
|
+
await tx
|
|
246
|
+
.update(schema.attachedBrowserInventories)
|
|
247
|
+
.set({
|
|
248
|
+
bridgeGeneration: snapshot.bridgeGeneration,
|
|
249
|
+
revision: snapshot.revision,
|
|
250
|
+
observedAt: now,
|
|
251
|
+
updatedAt: now,
|
|
252
|
+
})
|
|
253
|
+
.where(
|
|
254
|
+
and(
|
|
255
|
+
eq(schema.attachedBrowserInventories.workspaceId, input.workspaceId),
|
|
256
|
+
eq(schema.attachedBrowserInventories.enrollmentId, input.enrollmentId),
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
} else {
|
|
260
|
+
await tx.insert(schema.attachedBrowserInventories).values({
|
|
261
|
+
accountId: input.accountId,
|
|
262
|
+
workspaceId: input.workspaceId,
|
|
263
|
+
enrollmentId: input.enrollmentId,
|
|
264
|
+
bridgeGeneration: snapshot.bridgeGeneration,
|
|
265
|
+
revision: snapshot.revision,
|
|
266
|
+
observedAt: now,
|
|
267
|
+
updatedAt: now,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const revision = changed
|
|
271
|
+
? await advanceWorkspaceInteractionRevision(tx, input.accountId, input.workspaceId)
|
|
272
|
+
: await readWorkspaceInteractionRevision(tx, input.workspaceId);
|
|
273
|
+
return { accepted: true, changed, revision };
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Immediately mark every live Chrome profile on a cleanly disconnected agent
|
|
279
|
+
* offline. Historical endpoint rows remain discoverable and reconnect in place. */
|
|
280
|
+
export async function disconnectAttachedBrowserDevices(
|
|
281
|
+
db: Database,
|
|
282
|
+
input: { accountId: string; workspaceId: string; enrollmentId: string },
|
|
283
|
+
): Promise<{ changed: boolean; revision: number }> {
|
|
284
|
+
return await withRlsContext(
|
|
285
|
+
db,
|
|
286
|
+
input,
|
|
287
|
+
async (scopedDb) =>
|
|
288
|
+
await scopedDb.transaction(async (rawTx) => {
|
|
289
|
+
const tx = rawTx as unknown as Database;
|
|
290
|
+
const now = new Date();
|
|
291
|
+
const rows = await tx
|
|
292
|
+
.update(schema.attachedBrowserDevices)
|
|
293
|
+
.set({ disconnectedAt: now, updatedAt: now })
|
|
294
|
+
.where(
|
|
295
|
+
and(
|
|
296
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
297
|
+
eq(schema.attachedBrowserDevices.enrollmentId, input.enrollmentId),
|
|
298
|
+
isNull(schema.attachedBrowserDevices.disconnectedAt),
|
|
299
|
+
),
|
|
300
|
+
)
|
|
301
|
+
.returning({ id: schema.attachedBrowserDevices.id });
|
|
302
|
+
const revision = rows.length
|
|
303
|
+
? await advanceWorkspaceInteractionRevision(tx, input.accountId, input.workspaceId)
|
|
304
|
+
: await readWorkspaceInteractionRevision(tx, input.workspaceId);
|
|
305
|
+
return { changed: rows.length > 0, revision };
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export async function listAttachedBrowserDevices(
|
|
311
|
+
db: Database,
|
|
312
|
+
input: {
|
|
313
|
+
accountId: string;
|
|
314
|
+
workspaceId: string;
|
|
315
|
+
includeDisconnected?: boolean;
|
|
316
|
+
},
|
|
317
|
+
): Promise<AttachedBrowserDeviceListResponseValue> {
|
|
318
|
+
return await withRlsContext(
|
|
319
|
+
db,
|
|
320
|
+
input,
|
|
321
|
+
async (scopedDb) => {
|
|
322
|
+
const rows = await scopedDb
|
|
323
|
+
.select()
|
|
324
|
+
.from(schema.attachedBrowserDevices)
|
|
325
|
+
.where(
|
|
326
|
+
input.includeDisconnected
|
|
327
|
+
? eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId)
|
|
328
|
+
: and(
|
|
329
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
330
|
+
isNull(schema.attachedBrowserDevices.disconnectedAt),
|
|
331
|
+
),
|
|
332
|
+
)
|
|
333
|
+
.orderBy(
|
|
334
|
+
desc(schema.attachedBrowserDevices.disconnectedAt),
|
|
335
|
+
desc(schema.attachedBrowserDevices.updatedAt),
|
|
336
|
+
asc(schema.attachedBrowserDevices.name),
|
|
337
|
+
asc(schema.attachedBrowserDevices.id),
|
|
338
|
+
);
|
|
339
|
+
return AttachedBrowserDeviceListResponse.parse({
|
|
340
|
+
revision: await readWorkspaceInteractionRevision(scopedDb, input.workspaceId),
|
|
341
|
+
devices: rows.map(deviceFromRow),
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
CONSISTENT_READ,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function getAttachedBrowserDevice(
|
|
349
|
+
db: Database,
|
|
350
|
+
input: { accountId: string; workspaceId: string; deviceId: string },
|
|
351
|
+
): Promise<AttachedBrowserDeviceValue> {
|
|
352
|
+
return await withRlsContext(
|
|
353
|
+
db,
|
|
354
|
+
input,
|
|
355
|
+
async (scopedDb) => {
|
|
356
|
+
const [row] = await scopedDb
|
|
357
|
+
.select()
|
|
358
|
+
.from(schema.attachedBrowserDevices)
|
|
359
|
+
.where(
|
|
360
|
+
and(
|
|
361
|
+
eq(schema.attachedBrowserDevices.workspaceId, input.workspaceId),
|
|
362
|
+
eq(schema.attachedBrowserDevices.id, input.deviceId),
|
|
363
|
+
),
|
|
364
|
+
)
|
|
365
|
+
.limit(1);
|
|
366
|
+
if (!row) throw new AttachedBrowserDeviceNotFoundError("Attached browser not found");
|
|
367
|
+
return deviceFromRow(row);
|
|
368
|
+
},
|
|
369
|
+
CONSISTENT_READ,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { parseVerifiedAttemptToolCatalog } from "@opengeni/codemode";
|
|
2
|
+
import type { AttemptToolCatalog } from "@opengeni/contracts";
|
|
3
|
+
import { and, eq } from "drizzle-orm";
|
|
4
|
+
import type { Database } from "./database";
|
|
5
|
+
import { withRlsContext } from "./database";
|
|
6
|
+
import * as schema from "./schema";
|
|
7
|
+
|
|
8
|
+
export class AttemptToolCatalogAuthorityError extends Error {
|
|
9
|
+
readonly code = "attempt_tool_catalog_authority_mismatch";
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
super("Attempt tool catalog does not match the exact durable execution attempt");
|
|
13
|
+
this.name = "AttemptToolCatalogAuthorityError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AttemptToolCatalogConflictError extends Error {
|
|
18
|
+
readonly code = "attempt_tool_catalog_conflict";
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
super("Execution attempt already has a different immutable tool catalog");
|
|
22
|
+
this.name = "AttemptToolCatalogConflictError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function persistAttemptToolCatalog(
|
|
27
|
+
db: Database,
|
|
28
|
+
input: AttemptToolCatalog,
|
|
29
|
+
): Promise<AttemptToolCatalog> {
|
|
30
|
+
const catalog = parseVerifiedAttemptToolCatalog(input);
|
|
31
|
+
return await withRlsContext(
|
|
32
|
+
db,
|
|
33
|
+
{ accountId: catalog.accountId, workspaceId: catalog.workspaceId },
|
|
34
|
+
async (scopedDb) =>
|
|
35
|
+
await scopedDb.transaction(async (tx) => {
|
|
36
|
+
const [attempt] = await tx
|
|
37
|
+
.select({
|
|
38
|
+
accountId: schema.sessionTurnAttempts.accountId,
|
|
39
|
+
workspaceId: schema.sessionTurnAttempts.workspaceId,
|
|
40
|
+
sessionId: schema.sessionTurnAttempts.sessionId,
|
|
41
|
+
turnId: schema.sessionTurnAttempts.turnId,
|
|
42
|
+
executionGeneration: schema.sessionTurnAttempts.executionGeneration,
|
|
43
|
+
})
|
|
44
|
+
.from(schema.sessionTurnAttempts)
|
|
45
|
+
.where(
|
|
46
|
+
and(
|
|
47
|
+
eq(schema.sessionTurnAttempts.workspaceId, catalog.workspaceId),
|
|
48
|
+
eq(schema.sessionTurnAttempts.id, catalog.attemptId),
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
.limit(1);
|
|
52
|
+
if (
|
|
53
|
+
!attempt ||
|
|
54
|
+
attempt.accountId !== catalog.accountId ||
|
|
55
|
+
attempt.sessionId !== catalog.sessionId ||
|
|
56
|
+
attempt.turnId !== catalog.turnId ||
|
|
57
|
+
attempt.executionGeneration !== catalog.executionGeneration
|
|
58
|
+
) {
|
|
59
|
+
throw new AttemptToolCatalogAuthorityError();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await tx
|
|
63
|
+
.insert(schema.sessionAttemptToolCatalogs)
|
|
64
|
+
.values({
|
|
65
|
+
attemptId: catalog.attemptId,
|
|
66
|
+
accountId: catalog.accountId,
|
|
67
|
+
workspaceId: catalog.workspaceId,
|
|
68
|
+
sessionId: catalog.sessionId,
|
|
69
|
+
turnId: catalog.turnId,
|
|
70
|
+
executionGeneration: catalog.executionGeneration,
|
|
71
|
+
catalogVersion: catalog.version,
|
|
72
|
+
generation: catalog.generation,
|
|
73
|
+
digest: catalog.digest,
|
|
74
|
+
catalog,
|
|
75
|
+
createdAt: new Date(catalog.createdAt),
|
|
76
|
+
})
|
|
77
|
+
.onConflictDoNothing({ target: schema.sessionAttemptToolCatalogs.attemptId });
|
|
78
|
+
|
|
79
|
+
const stored = await attemptToolCatalogByAttemptTx(
|
|
80
|
+
tx,
|
|
81
|
+
catalog.workspaceId,
|
|
82
|
+
catalog.attemptId,
|
|
83
|
+
);
|
|
84
|
+
if (!stored) throw new Error("Failed to persist attempt tool catalog");
|
|
85
|
+
if (stored.digest !== catalog.digest || stored.generation !== catalog.generation) {
|
|
86
|
+
throw new AttemptToolCatalogConflictError();
|
|
87
|
+
}
|
|
88
|
+
return parseVerifiedAttemptToolCatalog(stored.catalog);
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function getAttemptToolCatalog(
|
|
94
|
+
db: Database,
|
|
95
|
+
input: { accountId: string; workspaceId: string; attemptId: string },
|
|
96
|
+
): Promise<AttemptToolCatalog | null> {
|
|
97
|
+
return await withRlsContext(
|
|
98
|
+
db,
|
|
99
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
100
|
+
async (scopedDb) => {
|
|
101
|
+
const stored = await attemptToolCatalogByAttemptTx(
|
|
102
|
+
scopedDb,
|
|
103
|
+
input.workspaceId,
|
|
104
|
+
input.attemptId,
|
|
105
|
+
);
|
|
106
|
+
return stored ? parseVerifiedAttemptToolCatalog(stored.catalog) : null;
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function attemptToolCatalogByAttemptTx(
|
|
112
|
+
tx: Pick<Database, "select">,
|
|
113
|
+
workspaceId: string,
|
|
114
|
+
attemptId: string,
|
|
115
|
+
) {
|
|
116
|
+
const [row] = await tx
|
|
117
|
+
.select({
|
|
118
|
+
generation: schema.sessionAttemptToolCatalogs.generation,
|
|
119
|
+
digest: schema.sessionAttemptToolCatalogs.digest,
|
|
120
|
+
catalog: schema.sessionAttemptToolCatalogs.catalog,
|
|
121
|
+
})
|
|
122
|
+
.from(schema.sessionAttemptToolCatalogs)
|
|
123
|
+
.where(
|
|
124
|
+
and(
|
|
125
|
+
eq(schema.sessionAttemptToolCatalogs.workspaceId, workspaceId),
|
|
126
|
+
eq(schema.sessionAttemptToolCatalogs.attemptId, attemptId),
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
.limit(1);
|
|
130
|
+
return row ?? null;
|
|
131
|
+
}
|