@opengeni/db 0.36.1 → 3.1.0-canary.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 +7 -0
- package/dist/automations.d.ts +154 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-integrations.d.ts +13 -0
- package/dist/child-lifecycle-notices.d.ts +68 -0
- package/dist/{chunk-GYO7ED2Q.js → chunk-EULAN2NP.js} +2 -2
- package/dist/{chunk-VTPRIXKG.js → chunk-NJVZPWXY.js} +3 -3
- package/dist/{chunk-HSOWSYOX.js → chunk-NMUBV7Z5.js} +19 -11
- package/dist/chunk-NMUBV7Z5.js.map +1 -0
- package/dist/chunk-OF5OBB76.js +4457 -0
- package/dist/chunk-OF5OBB76.js.map +1 -0
- package/dist/{chunk-5KTIEDX7.js → chunk-PR2WMLOQ.js} +7714 -4443
- package/dist/chunk-PR2WMLOQ.js.map +1 -0
- package/dist/{chunk-Z7VYMNPZ.js → chunk-SCPUJS53.js} +41 -4
- package/dist/chunk-SCPUJS53.js.map +1 -0
- package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
- package/dist/chunk-SIBYBA2D.js.map +1 -0
- package/dist/chunk-TIKF3KTI.js +383 -0
- package/dist/chunk-TIKF3KTI.js.map +1 -0
- package/dist/chunk-ZZ5LJEFX.js +516 -0
- package/dist/chunk-ZZ5LJEFX.js.map +1 -0
- package/dist/codemode-operations.d.ts +1 -6
- package/dist/company-brain-context-selection-schema.d.ts +705 -0
- package/dist/company-brain-context-selection.d.ts +94 -0
- package/dist/company-brain-governed-writes.d.ts +24 -0
- package/dist/company-brain.d.ts +55 -0
- package/dist/company-profile-agent-admin.d.ts +15 -0
- package/dist/company-profile-schema.d.ts +619 -2
- package/dist/company-profile.d.ts +9 -4
- package/dist/connection-authority.d.ts +95 -0
- package/dist/connection-scopes.d.ts +8 -0
- package/dist/connection-tenancy-backfill.d.ts +41 -0
- package/dist/connection-token-resolver.d.ts +30 -1
- package/dist/database.d.ts +19 -0
- package/dist/editable-artifact-durable-export.d.ts +10 -0
- package/dist/editable-artifact-durable-export.js +18 -3
- package/dist/editable-artifact-durable-export.js.map +1 -1
- package/dist/editable-artifacts.d.ts +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/governed-learning-activation-schema.d.ts +984 -0
- package/dist/governed-learning-activation.d.ts +98 -0
- package/dist/governed-learning-evaluator-schema.d.ts +633 -0
- package/dist/governed-learning-evaluator.d.ts +28 -0
- package/dist/index.d.ts +1633 -87
- package/dist/index.js +22004 -8109
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +18 -0
- package/dist/integration-bindings.d.ts +1 -0
- package/dist/integration-facets.d.ts +30 -0
- package/dist/interaction-placement-loss.d.ts +22 -0
- package/dist/knowledge-source-sync-schema.d.ts +730 -0
- package/dist/knowledge-source-sync.d.ts +60 -0
- package/dist/managed-human-provisioning.d.ts +25 -0
- package/dist/memory-domain.d.ts +3 -3
- package/dist/migrate.d.ts +11 -1
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +43 -5
- package/dist/organization-membership-backfill.d.ts +174 -0
- package/dist/organization-membership-lifecycle.d.ts +162 -0
- package/dist/pr-review.d.ts +107 -0
- package/dist/preference-registry-schema.d.ts +2 -0
- package/dist/preference-registry.d.ts +2 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +23 -9
- package/dist/schema.d.ts +24925 -13697
- package/dist/schema.js +103 -1
- package/dist/scoped-knowledge-schema.d.ts +52 -1
- package/dist/session-background-commands.d.ts +125 -0
- package/dist/session-background-commands.js +34 -0
- package/dist/session-background-commands.js.map +1 -0
- package/dist/session-control.d.ts +156 -3
- package/dist/session-goal-pacing.d.ts +68 -0
- package/dist/session-queue-commands.d.ts +42 -5
- package/dist/session-realtime-ledger.d.ts +3 -0
- package/dist/session-tenancy.d.ts +71 -1
- package/dist/session-tenancy.js +25 -3
- package/dist/session-tool-call-settlement.d.ts +9 -4
- package/dist/slack-routing-personal-workspace.d.ts +23 -0
- package/dist/slack-routing.d.ts +153 -0
- package/dist/slack-task-policy-schema.d.ts +34 -0
- package/dist/slack-task-policy.d.ts +8 -0
- package/dist/slack-user-link-access.d.ts +1 -0
- package/dist/task-notes-schema.d.ts +594 -0
- package/dist/task-notes.d.ts +39 -0
- package/dist/turn-initiator.d.ts +2 -0
- package/dist/user-resource-authority.d.ts +109 -0
- package/dist/video-generation.js +3 -3
- package/dist/workspace-authority.d.ts +81 -0
- package/dist/workspace-instruction-policies-schema.d.ts +279 -0
- package/dist/workspace-instruction-policies.d.ts +62 -0
- package/dist/workspace-learning-policy.d.ts +1 -0
- package/dist/workspace-membership-permissions.d.ts +2 -0
- package/drizzle/0240_enrollment_connection_authority.sql +39 -0
- package/drizzle/0240_model_context_user_messages.sql +204 -0
- package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
- package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
- package/drizzle/0243_google_drive_object_acl_authority.sql +554 -0
- package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
- package/drizzle/0245_model_context_contribution_facts.sql +88 -0
- package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
- package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
- package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
- package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
- package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
- package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
- package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
- package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
- package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
- package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
- package/drizzle/0256_connection_authority_delegation.sql +703 -0
- package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
- package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
- package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
- package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
- package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
- package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
- package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
- package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
- package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
- package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
- package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
- package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
- package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
- package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
- package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
- package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
- package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
- package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
- package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
- package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
- package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
- package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
- package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
- package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
- package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
- package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
- package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
- package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
- package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
- package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
- package/drizzle/0289_session_composer_policy_authority.sql +597 -0
- package/drizzle/0290_organization_membership_backfill.sql +268 -0
- package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
- package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
- package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
- package/drizzle/0294_preference_activation_authority.sql +237 -0
- package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
- package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
- package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
- package/drizzle/0298_organization_tenancy_parity.sql +609 -0
- package/drizzle/0299_organization_membership_lock_order.sql +291 -0
- package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
- package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
- package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
- package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
- package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
- package/drizzle/0305_personal_resource_grant_management.sql +584 -0
- package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
- package/drizzle/0307_session_attention_state.sql +51 -0
- package/drizzle/0308_session_archives.sql +35 -0
- package/drizzle/0309_channel_project_pins.sql +6 -0
- package/drizzle/0310_channel_project_order.sql +27 -0
- package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
- package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
- package/drizzle/0313_private_child_session_authority.sql +365 -0
- package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
- package/drizzle/0315_personal_github_repository_selection.sql +624 -0
- package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
- package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
- package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
- package/drizzle/0319_event_triggered_automations.sql +254 -0
- package/drizzle/0320_pr_review_pack.sql +113 -0
- package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
- package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
- package/drizzle/0323_organization_private_session_enablement.sql +489 -0
- package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
- package/drizzle/0325_child_lifecycle_notices.sql +44 -0
- package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
- package/drizzle/0327_slack_first_task_hint.sql +29 -0
- package/drizzle/0328_session_background_commands.sql +590 -0
- package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
- package/drizzle/0330_api_key_descriptions.sql +20 -0
- package/drizzle/0331_managed_organization_creation.sql +163 -0
- package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
- package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
- package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
- package/drizzle/0335_slack_workspace_routing.sql +460 -0
- package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
- package/drizzle/0337_slack_routed_action_handles.sql +189 -0
- package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
- package/drizzle/0339_document_authority_reclassification.sql +1153 -0
- package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
- package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
- package/package.json +10 -6
- package/src/attached-browser-devices.ts +233 -5
- package/src/automations.ts +911 -0
- package/src/browser-sessions.ts +10 -0
- package/src/capability-integrations.ts +175 -1
- package/src/child-lifecycle-notices.ts +241 -0
- package/src/codemode-operations.ts +1 -35
- package/src/company-brain-context-selection-schema.ts +100 -0
- package/src/company-brain-context-selection.ts +359 -0
- package/src/company-brain-governed-writes.ts +1170 -0
- package/src/company-brain.ts +309 -0
- package/src/company-profile-agent-admin.ts +225 -0
- package/src/company-profile-schema.ts +121 -1
- package/src/company-profile.ts +21 -0
- package/src/computer-sessions.ts +10 -1
- package/src/connection-authority.ts +290 -0
- package/src/connection-scopes.ts +20 -0
- package/src/connection-tenancy-backfill.ts +81 -0
- package/src/connection-token-resolver.ts +263 -31
- package/src/database.ts +75 -2
- package/src/editable-artifact-durable-export.ts +27 -0
- package/src/editable-artifacts-schema.ts +10 -5
- package/src/editable-artifacts.ts +21 -11
- package/src/governed-learning-activation-schema.ts +121 -0
- package/src/governed-learning-activation.ts +588 -0
- package/src/governed-learning-evaluator-schema.ts +72 -0
- package/src/governed-learning-evaluator.ts +217 -0
- package/src/index.ts +12001 -1699
- package/src/insights.ts +107 -0
- package/src/integration-bindings.ts +88 -6
- package/src/integration-facets.ts +287 -84
- package/src/interaction-placement-loss.ts +234 -0
- package/src/knowledge-source-sync-schema.ts +83 -0
- package/src/knowledge-source-sync.ts +530 -0
- package/src/managed-human-provisioning.ts +116 -0
- package/src/memory-domain.ts +27 -32
- package/src/migrate.ts +77 -1
- package/src/new-session-drafts.ts +229 -35
- package/src/organization-membership-backfill.ts +735 -0
- package/src/organization-membership-lifecycle.ts +985 -0
- package/src/pr-review.ts +890 -0
- package/src/preference-registry.ts +31 -1
- package/src/provision-roles.ts +931 -24
- package/src/runtime-posture-cli.ts +2 -0
- package/src/runtime-posture.ts +1315 -52
- package/src/schema.ts +3336 -438
- package/src/scoped-knowledge-schema.ts +16 -8
- package/src/scoped-knowledge.ts +1 -1
- package/src/session-background-commands.ts +759 -0
- package/src/session-control.ts +640 -74
- package/src/session-goal-pacing.ts +126 -0
- package/src/session-queue-commands.ts +450 -98
- package/src/session-realtime-context.ts +19 -3
- package/src/session-realtime-ledger.ts +75 -12
- package/src/session-realtime-mirror.ts +1 -1
- package/src/session-tenancy.ts +403 -49
- package/src/session-tool-call-settlement.ts +38 -29
- package/src/slack-routing-personal-workspace.ts +67 -0
- package/src/slack-routing.ts +469 -0
- package/src/slack-task-policy-schema.ts +5 -0
- package/src/slack-task-policy.ts +18 -0
- package/src/slack-user-link-access.ts +17 -1
- package/src/task-notes-schema.ts +74 -0
- package/src/task-notes.ts +124 -2
- package/src/turn-initiator.ts +8 -0
- package/src/user-resource-authority.ts +377 -0
- package/src/workspace-authority.ts +160 -0
- package/src/workspace-instruction-policies-schema.ts +53 -1
- package/src/workspace-instruction-policies.ts +528 -77
- package/src/workspace-learning-policy.ts +21 -5
- package/src/workspace-membership-permissions.ts +9 -0
- package/dist/chunk-5KTIEDX7.js.map +0 -1
- package/dist/chunk-FPVAD4CO.js +0 -117
- package/dist/chunk-FPVAD4CO.js.map +0 -1
- package/dist/chunk-HSOWSYOX.js.map +0 -1
- package/dist/chunk-SK7YYHBI.js.map +0 -1
- package/dist/chunk-VKLB2ZGI.js +0 -2389
- package/dist/chunk-VKLB2ZGI.js.map +0 -1
- package/dist/chunk-Z7VYMNPZ.js.map +0 -1
- /package/dist/{chunk-GYO7ED2Q.js.map → chunk-EULAN2NP.js.map} +0 -0
- /package/dist/{chunk-VTPRIXKG.js.map → chunk-NJVZPWXY.js.map} +0 -0
package/src/memory-domain.ts
CHANGED
|
@@ -428,13 +428,7 @@ export const WORKSPACE_MEMORY_BLOCK_EMPTY = `## Workspace memory
|
|
|
428
428
|
This workspace has shared long-lived memory, currently empty. Your context is lost when the session ends; memory is not. When you learn something durably useful — a preference, an environment fact, a procedure that worked, a decision and its reason — save it with memory_save (one crisp, self-contained fact per record). Never store secrets.`;
|
|
429
429
|
|
|
430
430
|
export const MEMORY_SEARCH_TOOL_DESCRIPTION =
|
|
431
|
-
"Search this workspace's shared long-lived memory (semantic + keyword). Use it before starting a new non-trivial task when the injected notes or current conversation do not already answer how the workspace does something. Results persist in conversation context: do not repeat the same search as routine setup on every continuation, resume, or interrupted turn. Returns scored records with ids.";
|
|
432
|
-
|
|
433
|
-
export const MEMORY_SAVE_TOOL_DESCRIPTION =
|
|
434
|
-
"Save one durable, future-useful fact to this workspace's shared memory: a stable preference, an environment fact, a procedure that worked, or a decision and its reason. Write it compactly (1–3 sentences), self-contained (no 'this session/above' references, absolute dates, name concrete things), so a future session can act on it alone. Do NOT save: session-specific state, speculation, anything derivable from the repo/docs, or near-duplicates of existing memories (search first — to refine or replace an existing record pass replaces_id). Most turns have nothing worth saving.";
|
|
435
|
-
|
|
436
|
-
export const MEMORY_CORRECT_TOOL_DESCRIPTION =
|
|
437
|
-
"Flag a workspace memory as wrong or outdated the moment you discover it — this is the most valuable memory action, because a wrong memory misleads every future session. Pass the record's id (as shown in [brackets]); optionally give replacement_text with the corrected fact, otherwise the record is archived.";
|
|
431
|
+
"Search this workspace's shared long-lived memory (semantic + keyword). Use it before starting a new non-trivial task when the injected notes or current conversation do not already answer how the workspace does something. Results persist in conversation context: do not repeat the same search as routine setup on every continuation, resume, or interrupted turn. Returns scored records with ids. Indexed workspace documents are a separate store: search those with `knowledge_search`/`knowledge_get` (or `search_documents`) on the Document Search (docs) MCP server, not with this tool.";
|
|
438
432
|
|
|
439
433
|
// ---------------------------------------------------------------------------
|
|
440
434
|
// Text normalization + hashing (MUST match migration 0045 backfill exactly)
|
|
@@ -469,7 +463,7 @@ export function isMemoryTextTooLong(text: string): boolean {
|
|
|
469
463
|
// ---------------------------------------------------------------------------
|
|
470
464
|
|
|
471
465
|
export function estimateMemoryTokens(text: string): number {
|
|
472
|
-
return Math.ceil(text
|
|
466
|
+
return Math.ceil(Buffer.byteLength(text, "utf8") / 4);
|
|
473
467
|
}
|
|
474
468
|
|
|
475
469
|
// Short id shown in the block/tool output = first 8 chars of the uuid. Tools
|
|
@@ -491,46 +485,47 @@ export type MemoryBlockRecord = {
|
|
|
491
485
|
// into kind sections. Episodic is excluded. Returns null if nothing renders
|
|
492
486
|
// (no non-episodic records) — the caller substitutes the empty-state block.
|
|
493
487
|
export function renderWorkspaceMemoryBlock(records: readonly MemoryBlockRecord[]): string | null {
|
|
494
|
-
|
|
495
|
-
if (renderable.length === 0) {
|
|
488
|
+
if (!records.some((record) => record.kind !== "episodic")) {
|
|
496
489
|
return null;
|
|
497
490
|
}
|
|
491
|
+
const selected = selectWorkspaceMemoryBlockRecords(records);
|
|
492
|
+
|
|
493
|
+
const lines: string[] = [WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED];
|
|
494
|
+
for (const kind of MEMORY_BLOCK_KIND_ORDER) {
|
|
495
|
+
const inSection = selected.filter((record) => record.kind === kind);
|
|
496
|
+
if (inSection.length === 0) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
lines.push("", `### ${MEMORY_KIND_SECTION_TITLES[kind]}`);
|
|
500
|
+
for (const record of inSection) {
|
|
501
|
+
lines.push(renderMemoryEntry(record));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return lines.join("\n");
|
|
505
|
+
}
|
|
498
506
|
|
|
499
|
-
|
|
500
|
-
|
|
507
|
+
/** Canonical whole-entry prompt-budget selector, shared with receipt validation. */
|
|
508
|
+
export function selectWorkspaceMemoryBlockRecords(
|
|
509
|
+
records: readonly MemoryBlockRecord[],
|
|
510
|
+
): readonly MemoryBlockRecord[] {
|
|
511
|
+
const renderable = records.filter((record) => record.kind !== "episodic");
|
|
501
512
|
const headerTokens = estimateMemoryTokens(WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED);
|
|
502
513
|
let usedTokens = headerTokens;
|
|
503
514
|
const seenSections = new Set<KnowledgeMemoryKind>();
|
|
504
515
|
const selected: MemoryBlockRecord[] = [];
|
|
505
516
|
for (const record of renderable) {
|
|
506
517
|
const entryLine = renderMemoryEntry(record);
|
|
507
|
-
let cost = estimateMemoryTokens(entryLine) + 1;
|
|
518
|
+
let cost = estimateMemoryTokens(entryLine) + 1;
|
|
508
519
|
if (!seenSections.has(record.kind)) {
|
|
509
520
|
const sectionTitle = `### ${MEMORY_KIND_SECTION_TITLES[record.kind]}`;
|
|
510
|
-
cost += estimateMemoryTokens(sectionTitle) + 2;
|
|
511
|
-
}
|
|
512
|
-
if (usedTokens + cost > WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET) {
|
|
513
|
-
// Skip entries that don't fit instead of stopping: one oversized entry
|
|
514
|
-
// must not starve smaller lower-priority records of the remaining budget.
|
|
515
|
-
continue;
|
|
521
|
+
cost += estimateMemoryTokens(sectionTitle) + 2;
|
|
516
522
|
}
|
|
523
|
+
if (usedTokens + cost > WORKSPACE_MEMORY_BLOCK_TOKEN_BUDGET) continue;
|
|
517
524
|
usedTokens += cost;
|
|
518
525
|
seenSections.add(record.kind);
|
|
519
526
|
selected.push(record);
|
|
520
527
|
}
|
|
521
|
-
|
|
522
|
-
const lines: string[] = [WORKSPACE_MEMORY_BLOCK_HEADER_POPULATED];
|
|
523
|
-
for (const kind of MEMORY_BLOCK_KIND_ORDER) {
|
|
524
|
-
const inSection = selected.filter((record) => record.kind === kind);
|
|
525
|
-
if (inSection.length === 0) {
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
lines.push("", `### ${MEMORY_KIND_SECTION_TITLES[kind]}`);
|
|
529
|
-
for (const record of inSection) {
|
|
530
|
-
lines.push(renderMemoryEntry(record));
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
return lines.join("\n");
|
|
528
|
+
return selected;
|
|
534
529
|
}
|
|
535
530
|
|
|
536
531
|
function renderMemoryEntry(record: MemoryBlockRecord): string {
|
package/src/migrate.ts
CHANGED
|
@@ -6,6 +6,12 @@ import postgres from "postgres";
|
|
|
6
6
|
const DEFAULT_DATABASE_URL = "postgres://opengeni:opengeni@127.0.0.1:5432/opengeni";
|
|
7
7
|
const DEFAULT_MAX_NESTED_AGENT_DEPTH = 3;
|
|
8
8
|
const MAX_NESTED_AGENT_DEPTH = 2_147_483_647;
|
|
9
|
+
const DEFAULT_APPLICATION_DATABASE_ROLE = "opengeni_app";
|
|
10
|
+
const GOAL_REVISION_CUTOVER_MIGRATION = "0257_goal_revision_decisions_and_root_constraints.sql";
|
|
11
|
+
const ATOMIC_PERSONAL_RESOURCE_CUTOVER_MIGRATION = "0306_atomic_personal_resource_attachments.sql";
|
|
12
|
+
const UNREGISTERED_INVITATION_CUTOVER_MIGRATION = "0314_unregistered_organization_invitations.sql";
|
|
13
|
+
const ATOMIC_CONNECTED_MACHINE_CUTOVER_MIGRATION = "0338_atomic_connected_machine_attachments.sql";
|
|
14
|
+
const MAX_MIGRATION_APPLICATION_ROLES = 16;
|
|
9
15
|
const batchedBackfillDirective =
|
|
10
16
|
/^-- opengeni:batched-backfill batch-size=(\d+) lock-timeout=(\d+(?:ms|s|min)) statement-timeout=(\d+(?:ms|s|min))$/;
|
|
11
17
|
const concurrentIndexDirective = /^-- opengeni:concurrent-index lock-timeout=(\d+(?:ms|s|min))$/;
|
|
@@ -30,6 +36,12 @@ export interface ConcurrentIndexMigration {
|
|
|
30
36
|
|
|
31
37
|
export type MigrationRuntimeOptions = {
|
|
32
38
|
maxNestedAgentDepth?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Exact database login roles that may run an OpenGeni API or worker against
|
|
41
|
+
* this target. Maintenance cutovers use this list to reject a live mixed-
|
|
42
|
+
* version fleet. Dedicated-schema and custom-role deployments must supply it.
|
|
43
|
+
*/
|
|
44
|
+
applicationDatabaseRoles?: string[];
|
|
33
45
|
};
|
|
34
46
|
|
|
35
47
|
type DeploymentDepthPolicy = {
|
|
@@ -230,6 +242,53 @@ function deploymentDepthPolicy(
|
|
|
230
242
|
return { maxNestedAgentDepth: value, source: "deployment" };
|
|
231
243
|
}
|
|
232
244
|
|
|
245
|
+
function migrationApplicationRoles(
|
|
246
|
+
schema: string | undefined,
|
|
247
|
+
options: MigrationRuntimeOptions | undefined,
|
|
248
|
+
): string[] {
|
|
249
|
+
const configured = options?.applicationDatabaseRoles;
|
|
250
|
+
const environment = process.env.OPENGENI_MIGRATION_APPLICATION_DATABASE_ROLES?.trim();
|
|
251
|
+
const explicit =
|
|
252
|
+
configured !== undefined
|
|
253
|
+
? configured
|
|
254
|
+
: environment
|
|
255
|
+
? environment.split(",").map((role) => role.trim())
|
|
256
|
+
: undefined;
|
|
257
|
+
|
|
258
|
+
if (!explicit) {
|
|
259
|
+
const configuredRuntimeRoles = [
|
|
260
|
+
process.env.OPENGENI_APP_DATABASE_USER?.trim(),
|
|
261
|
+
process.env.OPENGENI_RUNTIME_DATABASE_ROLE?.trim(),
|
|
262
|
+
].filter((role): role is string => Boolean(role) && role !== DEFAULT_APPLICATION_DATABASE_ROLE);
|
|
263
|
+
if (schema || configuredRuntimeRoles.length > 0) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
"Migration 0257 requires the exact application database roles via " +
|
|
266
|
+
"MigrationRuntimeOptions.applicationDatabaseRoles or " +
|
|
267
|
+
"OPENGENI_MIGRATION_APPLICATION_DATABASE_ROLES for dedicated-schema or custom-role deployments",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return [DEFAULT_APPLICATION_DATABASE_ROLE];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (explicit.length < 1 || explicit.length > MAX_MIGRATION_APPLICATION_ROLES) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Migration application database roles must contain 1-${MAX_MIGRATION_APPLICATION_ROLES} entries`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const normalized = [...new Set(explicit)].sort((left, right) =>
|
|
279
|
+
Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8")),
|
|
280
|
+
);
|
|
281
|
+
if (
|
|
282
|
+
normalized.length !== explicit.length ||
|
|
283
|
+
normalized.some((role) => role.length === 0 || Buffer.byteLength(role, "utf8") > 63)
|
|
284
|
+
) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
"Migration application database roles must be unique, non-empty Postgres role names of at most 63 UTF-8 bytes",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return normalized;
|
|
290
|
+
}
|
|
291
|
+
|
|
233
292
|
async function persistDeploymentDepthPolicy(
|
|
234
293
|
sql: postgres.Sql,
|
|
235
294
|
policy: DeploymentDepthPolicy,
|
|
@@ -277,6 +336,9 @@ async function persistDeploymentDepthPolicy(
|
|
|
277
336
|
* 2. `public` stays LAST on the path so `gen_random_uuid()` (pgcrypto) and the
|
|
278
337
|
* `vector` type — both installed into `public` by 0000 — still resolve. The
|
|
279
338
|
* `opengeni_private.*` helpers are always called with an absolute prefix.
|
|
339
|
+
* Dedicated-schema callers must also pass the exact API/worker database login
|
|
340
|
+
* identities in `applicationDatabaseRoles`; maintenance cutovers cannot infer
|
|
341
|
+
* host-managed or scoped runtime roles from the admin connection.
|
|
280
342
|
*
|
|
281
343
|
* `OPENGENI_DB_SCHEMA` defaults UNSET → `public` → standalone, so the default
|
|
282
344
|
* binding never regresses.
|
|
@@ -312,6 +374,19 @@ export async function migrate(
|
|
|
312
374
|
);
|
|
313
375
|
const appliedRows = await sql`SELECT "name" FROM "schema_migrations"`;
|
|
314
376
|
const applied = new Set(appliedRows.map((row) => row.name as string));
|
|
377
|
+
if (
|
|
378
|
+
!applied.has(GOAL_REVISION_CUTOVER_MIGRATION) ||
|
|
379
|
+
!applied.has(ATOMIC_PERSONAL_RESOURCE_CUTOVER_MIGRATION) ||
|
|
380
|
+
!applied.has(UNREGISTERED_INVITATION_CUTOVER_MIGRATION) ||
|
|
381
|
+
!applied.has(ATOMIC_CONNECTED_MACHINE_CUTOVER_MIGRATION)
|
|
382
|
+
) {
|
|
383
|
+
const applicationRoles = migrationApplicationRoles(schema, runtimeOptions);
|
|
384
|
+
await sql`select set_config(
|
|
385
|
+
'opengeni.migration_application_roles',
|
|
386
|
+
${JSON.stringify(applicationRoles)},
|
|
387
|
+
false
|
|
388
|
+
)`;
|
|
389
|
+
}
|
|
315
390
|
for (const file of files) {
|
|
316
391
|
if (applied.has(file)) {
|
|
317
392
|
continue;
|
|
@@ -332,7 +407,8 @@ export async function migrate(
|
|
|
332
407
|
* SDK entry point (Step I): run the migration chain over a host-supplied admin
|
|
333
408
|
* connection string against an explicit target schema. This is the embedded
|
|
334
409
|
* topology's named entry — a host calls `runMigrations(adminConnection,
|
|
335
|
-
* targetSchema)` from its own provisioning code
|
|
410
|
+
* targetSchema, { applicationDatabaseRoles })` from its own provisioning code
|
|
411
|
+
* instead of relying on env vars.
|
|
336
412
|
* `targetSchema` undefined → `public` → standalone behavior. Thin wrapper over
|
|
337
413
|
* `migrate` so there is one migration engine.
|
|
338
414
|
*/
|
|
@@ -1,17 +1,44 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
NewSessionDraftOptions,
|
|
3
|
+
NewSessionSelectionHistory,
|
|
3
4
|
LatencyMode,
|
|
4
5
|
ReasoningEffort,
|
|
5
6
|
RepositoryResourceRef,
|
|
6
7
|
ResourceRef,
|
|
7
8
|
ToolRef,
|
|
8
9
|
} from "@opengeni/contracts";
|
|
9
|
-
import {
|
|
10
|
+
import { stableJson } from "@opengeni/contracts";
|
|
11
|
+
import { and, eq, sql } from "drizzle-orm";
|
|
10
12
|
import type { Database } from "./database";
|
|
11
13
|
import * as schema from "./schema";
|
|
14
|
+
import {
|
|
15
|
+
accountIdInRlsScope,
|
|
16
|
+
subjectHasLiveWorkspaceAuthorityInScope,
|
|
17
|
+
} from "./workspace-authority";
|
|
12
18
|
|
|
13
19
|
export type NewSessionDraftRow = typeof schema.newSessionDrafts.$inferSelect;
|
|
14
20
|
|
|
21
|
+
async function lockNewSessionDraftIdentity(
|
|
22
|
+
db: Database,
|
|
23
|
+
workspaceId: string,
|
|
24
|
+
subjectId: string,
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
await db.execute(
|
|
27
|
+
sql`select pg_advisory_xact_lock(hashtextextended(${`new-session-draft:${workspaceId}:${subjectId}`}, 0))`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type NewSessionDraftSnapshot = {
|
|
32
|
+
text: string;
|
|
33
|
+
resources: ResourceRef[];
|
|
34
|
+
tools: ToolRef[];
|
|
35
|
+
toolsProvided: boolean;
|
|
36
|
+
model: string;
|
|
37
|
+
reasoningEffort: ReasoningEffort;
|
|
38
|
+
latencyMode: LatencyMode;
|
|
39
|
+
options: NewSessionDraftOptions;
|
|
40
|
+
};
|
|
41
|
+
|
|
15
42
|
export class NewSessionDraftConflictError extends Error {
|
|
16
43
|
readonly name = "NewSessionDraftConflictError";
|
|
17
44
|
|
|
@@ -31,13 +58,54 @@ export class NewSessionDraftAccessError extends Error {
|
|
|
31
58
|
type StoredNewSessionDraftOptions = NewSessionDraftOptions & {
|
|
32
59
|
/** JSONB-only compatibility marker; deliberately not part of public options. */
|
|
33
60
|
toolsProvided?: boolean;
|
|
61
|
+
/** Successful-create preference state, separate from transient draft edits. */
|
|
62
|
+
selectionHistory?: NewSessionSelectionHistory;
|
|
34
63
|
};
|
|
35
64
|
|
|
65
|
+
const REMEMBERED_WORKING_DIR_MAX_LENGTH = 4096;
|
|
66
|
+
|
|
36
67
|
function storedOptions(
|
|
37
68
|
options: NewSessionDraftOptions,
|
|
38
69
|
toolsProvided: boolean,
|
|
70
|
+
selectionHistory: NewSessionSelectionHistory = { projects: [] },
|
|
39
71
|
): StoredNewSessionDraftOptions {
|
|
40
|
-
return { ...options, toolsProvided };
|
|
72
|
+
return { ...options, toolsProvided, selectionHistory };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function newSessionSelectionHistory(row: NewSessionDraftRow): NewSessionSelectionHistory {
|
|
76
|
+
const raw = (row.sessionOptions as StoredNewSessionDraftOptions).selectionHistory;
|
|
77
|
+
if (!raw || !Array.isArray(raw.projects)) return { projects: [] };
|
|
78
|
+
return {
|
|
79
|
+
projects: raw.projects.slice(0, 50).flatMap((project) => {
|
|
80
|
+
if (!project || (project.channelId !== null && typeof project.channelId !== "string")) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
channelId: project.channelId,
|
|
86
|
+
targetSandboxId:
|
|
87
|
+
typeof project.targetSandboxId === "string" ? project.targetSandboxId : null,
|
|
88
|
+
machines: Array.isArray(project.machines)
|
|
89
|
+
? project.machines.slice(0, 20).flatMap((machine) =>
|
|
90
|
+
machine && typeof machine.sandboxId === "string"
|
|
91
|
+
? [
|
|
92
|
+
{
|
|
93
|
+
sandboxId: machine.sandboxId,
|
|
94
|
+
workingDir:
|
|
95
|
+
typeof machine.workingDir === "string" &&
|
|
96
|
+
machine.workingDir.length > 0 &&
|
|
97
|
+
machine.workingDir.length <= REMEMBERED_WORKING_DIR_MAX_LENGTH
|
|
98
|
+
? machine.workingDir
|
|
99
|
+
: null,
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
: [],
|
|
103
|
+
)
|
|
104
|
+
: [],
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
41
109
|
}
|
|
42
110
|
|
|
43
111
|
export function newSessionDraftToolsProvided(row: NewSessionDraftRow): boolean {
|
|
@@ -53,6 +121,7 @@ export function newSessionDraftToolsProvided(row: NewSessionDraftRow): boolean {
|
|
|
53
121
|
export function publicNewSessionDraftOptions(row: NewSessionDraftRow): NewSessionDraftOptions {
|
|
54
122
|
const options = { ...(row.sessionOptions as StoredNewSessionDraftOptions) };
|
|
55
123
|
delete options.toolsProvided;
|
|
124
|
+
delete options.selectionHistory;
|
|
56
125
|
return options;
|
|
57
126
|
}
|
|
58
127
|
|
|
@@ -87,10 +156,16 @@ export async function saveNewSessionDraftInTransaction(
|
|
|
87
156
|
toolsProvided: boolean;
|
|
88
157
|
model: string;
|
|
89
158
|
reasoningEffort: ReasoningEffort;
|
|
90
|
-
latencyMode
|
|
159
|
+
latencyMode: LatencyMode;
|
|
91
160
|
options: NewSessionDraftOptions;
|
|
92
161
|
/** API-key and delegated service subjects have no workspace-membership row. */
|
|
93
162
|
requireWorkspaceMembership?: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* May this caller use the owner-only managed personal-workspace exception?
|
|
165
|
+
* See `PersonalWorkspaceOwnerException` in `@opengeni/db`. Absent means the
|
|
166
|
+
* historical bare-membership fence, unchanged.
|
|
167
|
+
*/
|
|
168
|
+
personalWorkspaceOwnerException?: boolean;
|
|
94
169
|
},
|
|
95
170
|
): Promise<NewSessionDraftRow> {
|
|
96
171
|
if (input.requireWorkspaceMembership !== false) {
|
|
@@ -109,9 +184,36 @@ export async function saveNewSessionDraftInTransaction(
|
|
|
109
184
|
)
|
|
110
185
|
.for("key share")
|
|
111
186
|
.limit(1);
|
|
112
|
-
|
|
187
|
+
// The owner of a managed personal workspace never has a membership row there
|
|
188
|
+
// (migration 0219 raises on one), so the bare probe above would 403 the
|
|
189
|
+
// composer inside the one workspace they always belong to. Ask the canonical
|
|
190
|
+
// resolver instead of re-deriving that rule — it runs on this same
|
|
191
|
+
// transaction handle, and there is no membership row for removeWorkspaceMember()
|
|
192
|
+
// to delete in a personal workspace, so nothing is lost by taking no
|
|
193
|
+
// `FOR KEY SHARE` on this path.
|
|
194
|
+
if (
|
|
195
|
+
!membership &&
|
|
196
|
+
!(
|
|
197
|
+
input.personalWorkspaceOwnerException === true &&
|
|
198
|
+
(await subjectHasLiveWorkspaceAuthorityInScope(db, {
|
|
199
|
+
// Scope-derived, NOT `input.accountId`: the applied RLS GUC is the
|
|
200
|
+
// tenant this transaction actually runs under, so an authority
|
|
201
|
+
// decision never reads a caller-supplied account. Matches
|
|
202
|
+
// listSessionsForSubject and setSessionPin.
|
|
203
|
+
accountId: await accountIdInRlsScope(db),
|
|
204
|
+
workspaceId: input.workspaceId,
|
|
205
|
+
subjectId: input.subjectId,
|
|
206
|
+
}))
|
|
207
|
+
)
|
|
208
|
+
) {
|
|
209
|
+
throw new NewSessionDraftAccessError();
|
|
210
|
+
}
|
|
113
211
|
}
|
|
114
|
-
|
|
212
|
+
await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
|
|
213
|
+
const current = await getNewSessionDraftInTransaction(db, {
|
|
214
|
+
...input,
|
|
215
|
+
lock: true,
|
|
216
|
+
});
|
|
115
217
|
const currentRevision = current?.revision ?? 0;
|
|
116
218
|
if (currentRevision !== input.expectedRevision) {
|
|
117
219
|
throw new NewSessionDraftConflictError(currentRevision);
|
|
@@ -128,10 +230,14 @@ export async function saveNewSessionDraftInTransaction(
|
|
|
128
230
|
tools: input.tools,
|
|
129
231
|
model: input.model,
|
|
130
232
|
reasoningEffort: input.reasoningEffort,
|
|
131
|
-
latencyMode: input.latencyMode
|
|
233
|
+
latencyMode: input.latencyMode,
|
|
132
234
|
// Keep the explicit/omitted policy in the existing JSONB extension point;
|
|
133
235
|
// adding a column here would turn a client preference into a migration.
|
|
134
|
-
sessionOptions: storedOptions(
|
|
236
|
+
sessionOptions: storedOptions(
|
|
237
|
+
input.options,
|
|
238
|
+
input.toolsProvided,
|
|
239
|
+
current ? newSessionSelectionHistory(current) : { projects: [] },
|
|
240
|
+
),
|
|
135
241
|
updatedAt: new Date(),
|
|
136
242
|
};
|
|
137
243
|
if (current) {
|
|
@@ -155,7 +261,10 @@ export async function saveNewSessionDraftInTransaction(
|
|
|
155
261
|
})
|
|
156
262
|
.returning();
|
|
157
263
|
if (inserted) return inserted;
|
|
158
|
-
const raced = await getNewSessionDraftInTransaction(db, {
|
|
264
|
+
const raced = await getNewSessionDraftInTransaction(db, {
|
|
265
|
+
...input,
|
|
266
|
+
lock: true,
|
|
267
|
+
});
|
|
159
268
|
throw new NewSessionDraftConflictError(raced?.revision ?? 0);
|
|
160
269
|
}
|
|
161
270
|
|
|
@@ -177,38 +286,94 @@ function safeRepositoryResource(resource: RepositoryResourceRef): RepositoryReso
|
|
|
177
286
|
};
|
|
178
287
|
}
|
|
179
288
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
289
|
+
export type AcceptedNewSessionSelection = {
|
|
290
|
+
channelId: string | null;
|
|
291
|
+
targetSandboxId: string | null;
|
|
292
|
+
workingDir: string | null;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export function rememberNewSessionSelection(
|
|
296
|
+
history: NewSessionSelectionHistory,
|
|
297
|
+
selection: AcceptedNewSessionSelection,
|
|
298
|
+
): NewSessionSelectionHistory {
|
|
299
|
+
const existingProject = history.projects.find(
|
|
300
|
+
(project) => project.channelId === selection.channelId,
|
|
301
|
+
);
|
|
302
|
+
let machines = existingProject?.machines ?? [];
|
|
303
|
+
if (selection.targetSandboxId) {
|
|
304
|
+
machines = [
|
|
305
|
+
{
|
|
306
|
+
sandboxId: selection.targetSandboxId,
|
|
307
|
+
workingDir:
|
|
308
|
+
selection.workingDir && selection.workingDir.length <= REMEMBERED_WORKING_DIR_MAX_LENGTH
|
|
309
|
+
? selection.workingDir
|
|
310
|
+
: null,
|
|
311
|
+
},
|
|
312
|
+
...machines.filter((machine) => machine.sandboxId !== selection.targetSandboxId),
|
|
313
|
+
].slice(0, 20);
|
|
197
314
|
}
|
|
198
|
-
return
|
|
315
|
+
return {
|
|
316
|
+
projects: [
|
|
317
|
+
{
|
|
318
|
+
channelId: selection.channelId,
|
|
319
|
+
targetSandboxId: selection.targetSandboxId,
|
|
320
|
+
machines,
|
|
321
|
+
},
|
|
322
|
+
...history.projects.filter((project) => project.channelId !== selection.channelId),
|
|
323
|
+
].slice(0, 50),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Record a successful create without consuming the editable draft. Realtime
|
|
329
|
+
* session creation uses this path because it must preserve the actor's pending
|
|
330
|
+
* text/files for a later ordinary session. The hidden preference update shares
|
|
331
|
+
* the draft identity lock with saves, and deliberately leaves its OCC revision
|
|
332
|
+
* unchanged because no browser-editable field changed.
|
|
333
|
+
*/
|
|
334
|
+
export async function rememberNewSessionSelectionInTransaction(
|
|
335
|
+
db: Database,
|
|
336
|
+
input: {
|
|
337
|
+
workspaceId: string;
|
|
338
|
+
subjectId: string;
|
|
339
|
+
acceptedSelection: AcceptedNewSessionSelection;
|
|
340
|
+
},
|
|
341
|
+
): Promise<boolean> {
|
|
342
|
+
await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
|
|
343
|
+
const current = await getNewSessionDraftInTransaction(db, { ...input, lock: true });
|
|
344
|
+
if (!current) return false;
|
|
345
|
+
const [updated] = await db
|
|
346
|
+
.update(schema.newSessionDrafts)
|
|
347
|
+
.set({
|
|
348
|
+
sessionOptions: storedOptions(
|
|
349
|
+
publicNewSessionDraftOptions(current),
|
|
350
|
+
newSessionDraftToolsProvided(current),
|
|
351
|
+
rememberNewSessionSelection(newSessionSelectionHistory(current), input.acceptedSelection),
|
|
352
|
+
),
|
|
353
|
+
})
|
|
354
|
+
.where(eq(schema.newSessionDrafts.id, current.id))
|
|
355
|
+
.returning({ id: schema.newSessionDrafts.id });
|
|
356
|
+
return Boolean(updated);
|
|
199
357
|
}
|
|
200
358
|
|
|
201
359
|
/**
|
|
202
360
|
* Replace one exact accepted draft with the next-create safe seed. The row is
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
361
|
+
* identity-serialized before the revision check so even the absent-row
|
|
362
|
+
* revision-zero case cannot race a first save. Exact create callers receive a
|
|
363
|
+
* typed conflict; legacy revision-only callers retain the old false/no-op
|
|
364
|
+
* result. A missing row is an idempotent no-op.
|
|
206
365
|
*/
|
|
207
366
|
export async function seedNewSessionDraftInTransaction(
|
|
208
367
|
db: Database,
|
|
209
|
-
input: {
|
|
368
|
+
input: {
|
|
369
|
+
workspaceId: string;
|
|
370
|
+
subjectId: string;
|
|
371
|
+
expectedRevision: number;
|
|
372
|
+
expectedSnapshot?: NewSessionDraftSnapshot;
|
|
373
|
+
acceptedSelection?: AcceptedNewSessionSelection;
|
|
374
|
+
},
|
|
210
375
|
): Promise<boolean> {
|
|
211
|
-
|
|
376
|
+
await lockNewSessionDraftIdentity(db, input.workspaceId, input.subjectId);
|
|
212
377
|
const [current] = await db
|
|
213
378
|
.select()
|
|
214
379
|
.from(schema.newSessionDrafts)
|
|
@@ -220,7 +385,27 @@ export async function seedNewSessionDraftInTransaction(
|
|
|
220
385
|
)
|
|
221
386
|
.for("update")
|
|
222
387
|
.limit(1);
|
|
223
|
-
if (!current || current.revision !== input.expectedRevision)
|
|
388
|
+
if (!current || current.revision !== input.expectedRevision) {
|
|
389
|
+
if (input.expectedSnapshot) {
|
|
390
|
+
throw new NewSessionDraftConflictError(current?.revision ?? 0);
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
if (
|
|
395
|
+
input.expectedSnapshot &&
|
|
396
|
+
stableJson({
|
|
397
|
+
text: current.text,
|
|
398
|
+
resources: current.resources,
|
|
399
|
+
tools: newSessionDraftToolsProvided(current) ? current.tools : [],
|
|
400
|
+
toolsProvided: newSessionDraftToolsProvided(current),
|
|
401
|
+
model: current.model,
|
|
402
|
+
reasoningEffort: current.reasoningEffort,
|
|
403
|
+
latencyMode: current.latencyMode,
|
|
404
|
+
options: publicNewSessionDraftOptions(current),
|
|
405
|
+
}) !== stableJson(input.expectedSnapshot)
|
|
406
|
+
) {
|
|
407
|
+
throw new NewSessionDraftConflictError(current.revision);
|
|
408
|
+
}
|
|
224
409
|
|
|
225
410
|
const options = current.sessionOptions as StoredNewSessionDraftOptions;
|
|
226
411
|
const targetSandboxId =
|
|
@@ -228,8 +413,8 @@ export async function seedNewSessionDraftInTransaction(
|
|
|
228
413
|
const safeOptions: NewSessionDraftOptions = {
|
|
229
414
|
...(options.sandboxBackend ? { sandboxBackend: options.sandboxBackend } : {}),
|
|
230
415
|
...(targetSandboxId ? { targetSandboxId } : {}),
|
|
231
|
-
...(
|
|
232
|
-
? { workingDir:
|
|
416
|
+
...(targetSandboxId && typeof options.workingDir === "string"
|
|
417
|
+
? { workingDir: options.workingDir }
|
|
233
418
|
: {}),
|
|
234
419
|
...(options.variableSetId ? { variableSetId: options.variableSetId } : {}),
|
|
235
420
|
...(options.rigId ? { rigId: options.rigId } : {}),
|
|
@@ -252,7 +437,16 @@ export async function seedNewSessionDraftInTransaction(
|
|
|
252
437
|
model: current.model,
|
|
253
438
|
reasoningEffort: current.reasoningEffort,
|
|
254
439
|
latencyMode: current.latencyMode,
|
|
255
|
-
sessionOptions: storedOptions(
|
|
440
|
+
sessionOptions: storedOptions(
|
|
441
|
+
safeOptions,
|
|
442
|
+
newSessionDraftToolsProvided(current),
|
|
443
|
+
input.acceptedSelection
|
|
444
|
+
? rememberNewSessionSelection(
|
|
445
|
+
newSessionSelectionHistory(current),
|
|
446
|
+
input.acceptedSelection,
|
|
447
|
+
)
|
|
448
|
+
: newSessionSelectionHistory(current),
|
|
449
|
+
),
|
|
256
450
|
updatedAt: new Date(),
|
|
257
451
|
})
|
|
258
452
|
.where(
|