@opengeni/core 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,735 @@
1
+ import { Settings } from '@opengeni/config';
2
+ import { ScheduledTask, Document, Permission, AccessContext, AccessGrant, LimitAction, LimitDecision, CapabilityCatalogResponse, CreateCapabilityCatalogItemRequest, CapabilityCatalogItem, CapabilityInstallation, EnableCapabilityRequest, WorkspaceEnvironment, CapabilityPack, SocialConnection, ScheduledTaskAgentConfig, ToolRef, ResourceRef, CreateScheduledTaskRequest, UpdateScheduledTaskRequest, ReasoningEffort, SessionEvent, SessionTurn, GoalSpec, Session, WorkspaceMember } from '@opengeni/contracts';
3
+ export { mergeToolRefs, stableJson } from '@opengeni/contracts';
4
+ import { Database, SandboxRecord, EnabledMcpCapabilityServer, UpdateScheduledTaskInput } from '@opengeni/db';
5
+ import { DocumentServices } from '@opengeni/documents';
6
+ import { EventBus } from '@opengeni/events';
7
+ import { Observability } from '@opengeni/observability';
8
+ import { createObjectStorage } from '@opengeni/storage';
9
+ import { Auth } from 'better-auth';
10
+ import { SelfhostedRelayConfig, EstablishedSandboxSession } from '@opengeni/runtime/sandbox';
11
+ import { Context } from 'hono';
12
+
13
+ type ManagedAuth = Auth<any>;
14
+
15
+ type ApiSandboxSession = {
16
+ state?: Record<string, unknown> & {
17
+ sandboxId?: string;
18
+ };
19
+ running?(): Promise<boolean>;
20
+ exec?(args: {
21
+ cmd: string;
22
+ workdir?: string;
23
+ runAs?: string;
24
+ yieldTimeMs?: number;
25
+ maxOutputTokens?: number;
26
+ }): Promise<unknown>;
27
+ execCommand?(args: {
28
+ cmd: string;
29
+ workdir?: string;
30
+ runAs?: string;
31
+ yieldTimeMs?: number;
32
+ maxOutputTokens?: number;
33
+ }): Promise<string>;
34
+ shutdown?(options?: unknown): Promise<void>;
35
+ delete?(options?: unknown): Promise<void>;
36
+ close?(): Promise<void>;
37
+ };
38
+ type ApiSandboxClient = {
39
+ backendId: string;
40
+ deserializeSessionState?(state: Record<string, unknown>): Promise<unknown>;
41
+ resume?(state: unknown, options?: unknown): Promise<ApiSandboxSession>;
42
+ delete?(state: unknown): Promise<void>;
43
+ };
44
+ type ResumeBoxByIdInput = {
45
+ /**
46
+ * The backend the box was created on — the lease's `resume_backend_id`. Must
47
+ * match the API's configured sandbox client backendId, or the resume is
48
+ * rejected (a cross-backend envelope can never deserialize correctly).
49
+ */
50
+ backend: string;
51
+ /**
52
+ * The serialized resume-state envelope — the lease's `resume_state` jsonb
53
+ * (the record produced by `client.serializeSessionState(state)`). This is the
54
+ * box identity + reattach descriptor; resume() reattaches to the live box by
55
+ * id (warm reattach) or cold-restores from its snapshot.
56
+ */
57
+ resumeState: Record<string, unknown>;
58
+ };
59
+ /**
60
+ * A live, resumed sandbox session for a SINGLE in-process op. The caller
61
+ * resumes → uses (exec/readFile/resolvePort) → drops it; lifecycle/refcount is
62
+ * the lease's job (P1.x), NOT this handle's. The session is non-owned by
63
+ * construction (resume-by-id never owns the box), so dropping it does not
64
+ * terminate the box.
65
+ */
66
+ type ResumedSandboxSession = ApiSandboxSession;
67
+
68
+ type SessionWorkflowClient = {
69
+ signalUserMessage: (input: {
70
+ sessionId: string;
71
+ eventId: string;
72
+ workflowId: string;
73
+ }) => Promise<void>;
74
+ wakeSessionWorkflow: (input: {
75
+ accountId: string;
76
+ workspaceId: string;
77
+ sessionId: string;
78
+ workflowId: string;
79
+ }) => Promise<void>;
80
+ signalApprovalDecision: (input: {
81
+ sessionId: string;
82
+ eventId: string;
83
+ workflowId: string;
84
+ }) => Promise<void>;
85
+ signalInterrupt: (input: {
86
+ accountId: string;
87
+ workspaceId: string;
88
+ sessionId: string;
89
+ eventId: string;
90
+ workflowId: string;
91
+ }) => Promise<void>;
92
+ syncScheduledTask: (input: {
93
+ task: ScheduledTask;
94
+ }) => Promise<void>;
95
+ deleteScheduledTaskSchedule: (input: {
96
+ temporalScheduleId: string;
97
+ }) => Promise<void>;
98
+ triggerScheduledTask: (input: {
99
+ task: ScheduledTask;
100
+ agentRunUsageIdempotencyKey?: string;
101
+ triggerWorkflowId?: string;
102
+ }) => Promise<void>;
103
+ };
104
+ type DocumentIndexClient = {
105
+ indexDocument: (input: {
106
+ accountId: string;
107
+ workspaceId: string;
108
+ documentId: string;
109
+ }) => Promise<Document | void>;
110
+ };
111
+ type AppDependencies = {
112
+ settings: Settings;
113
+ db: Database;
114
+ bus: EventBus;
115
+ workflowClient: SessionWorkflowClient;
116
+ documentIndexer?: DocumentIndexClient;
117
+ documentServices?: DocumentServices;
118
+ observability?: Observability;
119
+ githubStateSecret?: string;
120
+ managedAuth?: ManagedAuth | null;
121
+ sandboxClient?: ApiSandboxClient;
122
+ /**
123
+ * Resume a box by id from a serialized resume_state envelope (the lease's
124
+ * `resume_state` + `resume_backend_id` from P1.1) and return a live session
125
+ * for a single in-process op. resume → use → drop; the lease owns lifecycle,
126
+ * the returned handle does NOT own the box. Throws SandboxResumeError on a
127
+ * backend mismatch or a resume failure.
128
+ */
129
+ resumeBoxById?: (input: ResumeBoxByIdInput) => Promise<ResumedSandboxSession>;
130
+ };
131
+ type ObjectStorageDependency = ReturnType<typeof createObjectStorage>;
132
+ type ApiRouteDeps = AppDependencies & {
133
+ objectStorage: ObjectStorageDependency;
134
+ githubStateSecret: string;
135
+ documentIndexer: DocumentIndexClient;
136
+ getDocumentServices: () => DocumentServices;
137
+ resumeBoxById: (input: ResumeBoxByIdInput) => Promise<ResumedSandboxSession>;
138
+ };
139
+
140
+ type FleetServices = {
141
+ db: Database;
142
+ settings: Settings;
143
+ bus?: EventBus;
144
+ };
145
+ type FleetContext = {
146
+ accountId: string;
147
+ workspaceId: string;
148
+ /** The calling session (the pointer the attach/swap mutates + whose group box
149
+ * is the default fleet member). */
150
+ sessionId: string;
151
+ /** The session's own group sandbox backend (modal/selfhosted/…). */
152
+ sessionBackend: string;
153
+ /** The session's own group sandbox id (the lease group). */
154
+ sessionGroupId: string;
155
+ };
156
+ /**
157
+ * Build a session-scoped {@link FleetContext}: load the session (workspace-
158
+ * scoped), reject a session with no box (backend:none — the fleet is only
159
+ * meaningful for a sandboxed session), and project its group backend/id. Shared
160
+ * by the worker-signed MCP fleet tools and the user-authenticated swap REST
161
+ * route so both resolve the SAME context (no drift). The `accountId`/`workspaceId`/
162
+ * `sessionId` come from the trusted grant/route; the backend + group id come from
163
+ * the session row.
164
+ */
165
+ declare function buildFleetContextForSession(deps: {
166
+ db: Database;
167
+ }, ctx: {
168
+ accountId: string;
169
+ workspaceId: string;
170
+ sessionId: string;
171
+ }): Promise<FleetContext>;
172
+ /** The dominant liveness of a fleet member, surfaced to the dock + the agent. */
173
+ type FleetLiveness = "online" | "reconnecting" | "offline";
174
+ /**
175
+ * A fleet member as the agent + the dock see it (the M8b/M9 UI seam — the
176
+ * `sandboxes_list` response entry the dock renders). STABLE shape: the dock keys
177
+ * on `id`, renders `name`/`kind`/`liveness`, and marks `active`. The session's own
178
+ * Modal group box is a synthetic entry with `id: groupId`, `kind: "modal"`, and a
179
+ * null `enrollmentId`; an enrolled machine carries its sandbox + enrollment ids.
180
+ */
181
+ type FleetSandboxEntry = {
182
+ /** The sandbox id used as the attach/swap/run_on `target`. For the session's
183
+ * own group box this is the group id (a null active pointer == this box). */
184
+ id: string;
185
+ kind: "modal" | "selfhosted";
186
+ name: string;
187
+ liveness: FleetLiveness;
188
+ /** True for the session's currently-active sandbox (the routing target). */
189
+ active: boolean;
190
+ /** True for the session's own group box (the default/home sandbox). */
191
+ isSessionGroup: boolean;
192
+ enrollmentId: string | null;
193
+ /** Whether this target can be attached/swapped to right now (live + addressable). */
194
+ attachable: boolean;
195
+ /** Selfhosted only: whether whole-machine + screen-control consent is acked. */
196
+ consented?: boolean;
197
+ /** Selfhosted only: whether a display (real/Xvfb) is present. */
198
+ hasDisplay?: boolean;
199
+ lastSeenAt?: string | null;
200
+ };
201
+ type FleetListResult = {
202
+ /** The session's currently-active sandbox id, or null == the group box. */
203
+ activeSandboxId: string | null;
204
+ activeEpoch: number;
205
+ sandboxes: FleetSandboxEntry[];
206
+ };
207
+ /** A swap/attach outcome the tool returns. */
208
+ type FleetSwapResult = {
209
+ swapped: boolean;
210
+ activeSandboxId: string | null;
211
+ activeEpoch: number;
212
+ reason?: string;
213
+ };
214
+ /**
215
+ * List the fleet: the session's own Modal group box (a synthetic entry) + the
216
+ * workspace's first-class selfhosted sandboxes (each probed for liveness), each
217
+ * with an `active` marker derived from the session's active pointer.
218
+ */
219
+ declare function listFleet(services: FleetServices, ctx: FleetContext): Promise<FleetListResult>;
220
+ /**
221
+ * THE SWAP (and attach — identical mechanic). Validate the target's ownership +
222
+ * liveness, then repoint the session via the epoch-fenced CAS `setActiveSandbox`:
223
+ * read the current epoch, then CAS on it. A concurrent double-swap lets exactly
224
+ * one win; the loser re-reads + may retry. The bumped epoch fences any in-flight
225
+ * op cached against the old pointer, which then retries against the new active
226
+ * sandbox (the routing proxy's fenced-retry role).
227
+ */
228
+ declare function swapActiveSandbox(services: FleetServices, ctx: FleetContext, target: string, workingDir?: string | null): Promise<FleetSwapResult>;
229
+ type RunOnOp = {
230
+ kind: "exec";
231
+ cmd: string;
232
+ workdir?: string;
233
+ } | {
234
+ kind: "read";
235
+ path: string;
236
+ } | {
237
+ kind: "write";
238
+ path: string;
239
+ content: string;
240
+ };
241
+ type RunOnResult = {
242
+ target: string;
243
+ kind: string;
244
+ ok: boolean;
245
+ stdout?: string;
246
+ stderr?: string;
247
+ exitCode?: number | null;
248
+ content?: string;
249
+ bytesWritten?: number;
250
+ reason?: string;
251
+ };
252
+ /**
253
+ * Run a ONE-OFF op against a SPECIFIC target WITHOUT changing the active pointer
254
+ * (the dossier `run_on`). Only selfhosted targets are routable as a one-off here
255
+ * (a Modal target is the session's group box, reached via the normal Channel-A /
256
+ * turn path — `run_on` is for reaching a NON-active enrolled machine without
257
+ * swapping). The op is fenced under the target's enrollment, addressed to its
258
+ * agent subject; an offline machine surfaces a clear reason, never a wrong-box
259
+ * landing.
260
+ */
261
+ declare function runOnSandbox(services: FleetServices, ctx: FleetContext, target: string, op: RunOnOp): Promise<RunOnResult>;
262
+ type ProvisionResult = {
263
+ kind: "selfhosted";
264
+ instructions: string;
265
+ installCommandUnix: string;
266
+ installCommandWindows: string;
267
+ verificationUri: string;
268
+ note: string;
269
+ } | {
270
+ kind: "modal";
271
+ sandbox: SandboxRecord;
272
+ note: string;
273
+ };
274
+ /**
275
+ * Provision a new fleet member.
276
+ * - selfhosted → return the device-flow enrollment instructions (the agent
277
+ * surfaces them to a HUMAN, who installs the agent + enrolls — the agent
278
+ * cannot click the loud whole-machine consent itself).
279
+ * - modal → create a first-class named modal `sandboxes` record (a swap target).
280
+ * NOTE: the Modal BOX is materialized lazily when first swapped-to (Modal
281
+ * lifecycle is owned by the lease — unchanged per dossier §21).
282
+ */
283
+ declare function provisionSandbox(services: FleetServices, ctx: FleetContext, input: {
284
+ kind: "selfhosted" | "modal";
285
+ name?: string;
286
+ }): Promise<ProvisionResult>;
287
+
288
+ type ChannelARoutingServices = {
289
+ db: Database;
290
+ settings: Settings;
291
+ bus?: EventBus;
292
+ };
293
+ /** Map the deployment relay URL to the leaf's `SelfhostedRelayConfig` shape. The
294
+ * relay URL (`OPENGENI_SELFHOSTED_RELAY_URL`) may carry a path (the relay's wss
295
+ * route); a path-less URL defaults to the relay's `/stream` route (M8b). */
296
+ declare function relayConfigFromSettings(settings: Settings): SelfhostedRelayConfig;
297
+ /** The canonical relay dial-BASE URL (`scheme://host[:port]/stream`) handed to the
298
+ * agent PRODUCER. The agent's relay channel appends ONLY its routing query to
299
+ * this base (`channel.rs`: `format!("{relay_url}{sep}{query}")`) and relies on the
300
+ * base ALREADY carrying the relay's `/stream` route. `OPENGENI_SELFHOSTED_RELAY_URL`
301
+ * is frequently pathless (e.g. `wss://relay.<env>.app.opengeni.ai`), which made the
302
+ * producer dial a path-less URL the relay 400s. Derive the base from the SAME parser
303
+ * the CONSUMER uses (`relayConfigFromSettings`) so producer + consumer always agree
304
+ * on `/stream` — even when the configured URL omits it. An unconfigured relay maps to
305
+ * `""` (graceful degrade: the agent reports no-relay rather than dialing a synthetic
306
+ * host). Fixes preview AND managed prod with no agent rebuild (dossier §V5/§V6). */
307
+ declare function relayDialBaseFromSettings(settings: Settings): string;
308
+ /** Whether the routing proxy should wrap the Channel-A box: gated by the
309
+ * selfhosted flag (the active pointer + swap are only meaningful then). */
310
+ declare function routingEnabled(settings: Settings): boolean;
311
+ /**
312
+ * Wrap an established group-box session in a `RoutingSandboxSession` so a
313
+ * Channel-A op routes to the session's currently-active sandbox. Returns the
314
+ * established handle with its `session` replaced by the stable proxy. With the
315
+ * default pointer (active_sandbox_id == null) this routes to the group box
316
+ * unchanged; a selfhosted active pointer routes the op to the machine.
317
+ */
318
+ declare function wrapChannelABoxWithRouting(services: ChannelARoutingServices, ids: {
319
+ workspaceId: string;
320
+ sessionId: string;
321
+ }, established: EstablishedSandboxSession): EstablishedSandboxSession;
322
+
323
+ type AccessDeps = {
324
+ db: Database;
325
+ settings: Settings;
326
+ managedAuth?: ManagedAuth | null;
327
+ };
328
+ declare function requireAccessContext(c: Context, deps: AccessDeps): Promise<AccessContext>;
329
+ declare function requireAccessGrant(c: Context, deps: AccessDeps, workspaceId: string, permission?: Permission): Promise<AccessGrant>;
330
+ declare function requirePermission(grant: AccessGrant, permission: Permission): void;
331
+ declare function hasPermission(permissions: Permission[], permission: Permission): boolean;
332
+
333
+ type LimitCheckInput = {
334
+ accountId: string;
335
+ workspaceId?: string;
336
+ action: LimitAction;
337
+ quantity?: number;
338
+ model?: string | null;
339
+ };
340
+ declare function requireLimit(deps: ApiRouteDeps, input: LimitCheckInput): Promise<void>;
341
+ declare function checkLimit(deps: ApiRouteDeps, input: LimitCheckInput): Promise<LimitDecision>;
342
+ declare function recordWorkspaceUsage(deps: ApiRouteDeps, input: {
343
+ accountId: string;
344
+ workspaceId: string;
345
+ subjectId?: string | null;
346
+ eventType: "agent_run.created" | "file.uploaded" | "document.indexed" | "scheduled_task.fired";
347
+ quantity: number;
348
+ unit: string;
349
+ sourceResourceType: string;
350
+ sourceResourceId: string;
351
+ idempotencyKey: string;
352
+ }): Promise<void>;
353
+
354
+ declare const officialMcpRegistryUrl = "https://registry.modelcontextprotocol.io";
355
+ declare function buildCapabilityCatalog(input: {
356
+ db: Database;
357
+ workspaceId: string;
358
+ settings: Settings;
359
+ }): Promise<CapabilityCatalogResponse>;
360
+ declare function createCatalogItem(input: {
361
+ db: Database;
362
+ accountId: string;
363
+ workspaceId: string;
364
+ payload: CreateCapabilityCatalogItemRequest;
365
+ }): Promise<CapabilityCatalogItem>;
366
+ declare function enableCapability(input: {
367
+ db: Database;
368
+ grant: AccessGrant;
369
+ accountId: string;
370
+ workspaceId: string;
371
+ settings: Settings;
372
+ capabilityId: string;
373
+ payload: EnableCapabilityRequest;
374
+ probeMcpServer?: McpCapabilityProbe;
375
+ }): Promise<CapabilityInstallation>;
376
+ type McpCapabilityProbeInput = {
377
+ id: string;
378
+ name: string;
379
+ url: string;
380
+ timeoutMs: number;
381
+ headers?: Record<string, string>;
382
+ };
383
+ type McpCapabilityProbeResult = {
384
+ toolCount: number;
385
+ };
386
+ type McpCapabilityProbe = (input: McpCapabilityProbeInput) => Promise<McpCapabilityProbeResult>;
387
+ declare function validateMcpCapabilityConnection(item: CapabilityCatalogItem, probe?: McpCapabilityProbe, headers?: Record<string, string>): Promise<Record<string, unknown>>;
388
+ declare function disableCapability(input: {
389
+ db: Database;
390
+ accountId: string;
391
+ workspaceId: string;
392
+ settings: Settings;
393
+ capabilityId: string;
394
+ }): Promise<CapabilityInstallation>;
395
+ declare function settingsWithEnabledCapabilityMcpServers(db: Database, workspaceId: string, settings: Settings): Promise<Settings>;
396
+ declare function settingsWithMcpCapabilityServers(settings: Settings, enabled: EnabledMcpCapabilityServer[]): Settings;
397
+ declare function discoverMcpRegistryCapabilities(input: {
398
+ query?: string;
399
+ limit?: number;
400
+ fetchImpl?: McpRegistryFetch;
401
+ timeoutMs?: number;
402
+ }): Promise<CapabilityCatalogItem[]>;
403
+
404
+ type McpRegistryFetch = (input: URL, init?: RequestInit) => Promise<Response>;
405
+
406
+ declare const MAX_ENVIRONMENTS_PER_WORKSPACE = 25;
407
+ declare const MAX_VARIABLES_PER_ENVIRONMENT = 100;
408
+ declare function assertAllowedEnvironmentVariableName(name: string): void;
409
+ declare function requireEnvironmentEncryption(settings: Settings): Uint8Array;
410
+ declare function requireEnvironmentForApi(db: Database, workspaceId: string, environmentId: string): Promise<WorkspaceEnvironment>;
411
+ /**
412
+ * Validates an environment attachment supplied in a request payload (session
413
+ * create, scheduled task create/update, pack enable). Requires the
414
+ * `environments:use` permission unless the attachment was already authorized
415
+ * (pack-installation-inherited attachments), and maps a missing or
416
+ * cross-workspace environment to 422 because the id is payload, not the route
417
+ * target. RLS plus the workspace_id clause make cross-workspace ids
418
+ * indistinguishable from missing ones.
419
+ */
420
+ declare function validateEnvironmentAttachment(deps: {
421
+ settings: Settings;
422
+ db: Database;
423
+ }, grant: AccessGrant, workspaceId: string, environmentId: string, options?: {
424
+ preauthorized?: boolean;
425
+ }): Promise<WorkspaceEnvironment>;
426
+ declare function recordEnvironmentAuditEvent(db: Database, input: {
427
+ grant: AccessGrant;
428
+ action: "environment.created" | "environment.updated" | "environment.deleted" | "environment.variable.set" | "environment.variable.deleted";
429
+ environmentId: string;
430
+ variableName?: string;
431
+ }): Promise<void>;
432
+
433
+ declare const MARKETING_SOCIAL_PACK_ID = "marketing-social-daily-analysis";
434
+ declare function listCapabilityPacks(): CapabilityPack[];
435
+ declare function getCapabilityPack(packId: string): CapabilityPack | null;
436
+ declare function isBuiltInCapabilityPack(packId: string): boolean;
437
+ /**
438
+ * Built-in packs plus the manifests registered for this workspace. Stored
439
+ * manifests were validated at registration time; rows that no longer parse
440
+ * (for example after a contract tightening) are skipped instead of breaking
441
+ * the whole catalog.
442
+ */
443
+ declare function listWorkspaceCapabilityPacks(db: Database, workspaceId: string): Promise<CapabilityPack[]>;
444
+ declare function resolveCapabilityPack(db: Database, workspaceId: string, packId: string): Promise<CapabilityPack | null>;
445
+ /**
446
+ * v1 pack-scoped runtime rule: at most one enabled pack per workspace may
447
+ * declare a `sandboxImage` — there is deliberately no image composition or
448
+ * layering. Enforced when a pack is enabled (both the packs endpoint and the
449
+ * generic capability enable path) and re-checked at session start by the
450
+ * worker, which also covers manifests re-registered after enablement.
451
+ */
452
+ declare function assertPackSandboxImageCompatible(db: Database, workspaceId: string, pack: CapabilityPack): Promise<void>;
453
+ declare function buildMarketingDailyAnalysisAgentConfig(input: {
454
+ connections: SocialConnection[];
455
+ documentBaseIds: string[];
456
+ promptInstructions?: string;
457
+ }): ScheduledTaskAgentConfig;
458
+
459
+ declare function validateToolRefs(tools: ToolRef[], settings: Settings): ToolRef[];
460
+ type McpSettings = Pick<Settings, "mcpServers">;
461
+ declare function enabledCapabilityMcpToolRefs(settings: McpSettings, runtimeSettings: McpSettings): ToolRef[];
462
+ declare function withDefaultEnabledCapabilityMcpTools(tools: ToolRef[], settings: McpSettings, runtimeSettings: McpSettings): ToolRef[];
463
+ declare function normalizeResources(resources: ResourceRef[]): ResourceRef[];
464
+ declare function mergeResourceRefs(existing: ResourceRef[], additions: ResourceRef[]): ResourceRef[];
465
+ declare function validateGitHubRepositorySelectionShape(resources: ResourceRef[]): number | null;
466
+ declare function validateGitHubRepositorySelection(db: Database, workspaceId: string, resources: ResourceRef[]): Promise<void>;
467
+ declare function validateFileResources(db: Database, workspaceId: string, resources: ResourceRef[]): Promise<void>;
468
+
469
+ /**
470
+ * Whether a raw scheduled-task payload explicitly set agentConfig.tools.
471
+ * Zod's `.default([])` erases the distinction between "absent" and
472
+ * "explicitly empty", so callers detect it on the raw payload — the same
473
+ * contract sessions use: absent tools mean "give me the workspace defaults
474
+ * (enabled capability MCP servers)", an explicit list (even empty) is taken
475
+ * verbatim.
476
+ */
477
+ declare function scheduledTaskToolsProvided(rawPayload: unknown): boolean;
478
+ declare function createValidatedScheduledTask(input: {
479
+ settings: Settings;
480
+ db: Database;
481
+ objectStorage: ObjectStorageDependency;
482
+ grant: AccessGrant;
483
+ payload: CreateScheduledTaskRequest;
484
+ toolsProvided?: boolean;
485
+ environmentPreauthorized?: boolean;
486
+ }): Promise<ScheduledTask>;
487
+ declare function validatedScheduledTaskUpdate(input: {
488
+ settings: Settings;
489
+ db: Database;
490
+ objectStorage: ObjectStorageDependency;
491
+ grant: AccessGrant;
492
+ existing: ScheduledTask;
493
+ payload: UpdateScheduledTaskRequest;
494
+ /** See createValidatedScheduledTask; only consulted when agentConfig is updated. */
495
+ toolsProvided?: boolean;
496
+ }): Promise<UpdateScheduledTaskInput>;
497
+ declare function requireScheduledTaskForApi(db: Database, workspaceId: string, taskId: string): Promise<ScheduledTask>;
498
+ declare function restoreScheduledTask(db: Database, task: ScheduledTask): Promise<ScheduledTask>;
499
+ declare function syncCreatedScheduledTask(input: {
500
+ db: Database;
501
+ workflowClient: SessionWorkflowClient;
502
+ task: ScheduledTask;
503
+ }): Promise<void>;
504
+ declare function syncUpdatedScheduledTask(input: {
505
+ db: Database;
506
+ workflowClient: SessionWorkflowClient;
507
+ previous: ScheduledTask;
508
+ task: ScheduledTask;
509
+ }): Promise<void>;
510
+ declare function scheduledTaskTemporalScheduleId(taskId: string): string;
511
+ /**
512
+ * Stable token that identifies a single logical manual trigger. A client that
513
+ * retries a `/trigger` POST (network blip, lambda re-invocation) passes the
514
+ * SAME token so the retry is idempotent — one usage charge, one workflow run.
515
+ * When the client supplies nothing we mint one UUID PER REQUEST and reuse it
516
+ * for both the idempotency key and the workflowId, so a single request stays
517
+ * internally consistent while two genuinely-distinct manual triggers (no token,
518
+ * fired a second apart) still each get their own run. The token is sanitized to
519
+ * the Temporal workflow-id-safe charset so a client value cannot smuggle a
520
+ * collision into a different task's id space.
521
+ */
522
+ declare function scheduledTaskTriggerToken(clientTriggerId?: string | null): string;
523
+ /**
524
+ * Deterministic Temporal workflow id for a manual trigger. Derived purely from
525
+ * the task id and the stable trigger token, so a retry with the same token maps
526
+ * to the same id and `workflowIdReusePolicy: "REJECT_DUPLICATE"` collapses the
527
+ * second start into a no-op instead of spawning a second run.
528
+ */
529
+ declare function manualScheduledTaskTriggerWorkflowId(taskId: string, triggerToken: string): string;
530
+ /**
531
+ * Deterministic usage idempotency key for a manual trigger's agent_run.created
532
+ * charge. Shares the stable trigger token with the workflow id so the charge
533
+ * and the run dedupe together under retry.
534
+ */
535
+ declare function manualScheduledTaskTriggerUsageKey(workspaceId: string, taskId: string, triggerToken: string): string;
536
+
537
+ declare function createAndStartSession(input: {
538
+ db: Database;
539
+ bus: EventBus;
540
+ workflowClient: SessionWorkflowClient;
541
+ accountId: string;
542
+ workspaceId: string;
543
+ initialMessage: string;
544
+ resources: ResourceRef[];
545
+ tools: ToolRef[];
546
+ clientEventId?: string;
547
+ model: string;
548
+ reasoningEffort: Settings["openaiReasoningEffort"];
549
+ sandboxBackend: Settings["sandboxBackend"];
550
+ metadata: Record<string, unknown>;
551
+ environment?: {
552
+ id: string;
553
+ name: string;
554
+ } | null;
555
+ goal?: GoalSpec | null;
556
+ firstPartyMcpPermissions?: Permission[] | null;
557
+ parentSessionId?: string | null;
558
+ createIdempotencyKey?: string | null;
559
+ sandboxGroupId?: string | null;
560
+ sandboxOs?: Session["sandboxOs"];
561
+ seedTargetSandbox?: {
562
+ sandboxId: string;
563
+ settings: Settings;
564
+ workingDir?: string | null;
565
+ } | null;
566
+ }): Promise<{
567
+ id: string;
568
+ workspaceId: string;
569
+ accountId: string;
570
+ status: "queued" | "running" | "idle" | "requires_action" | "failed" | "cancelled";
571
+ initialMessage: string;
572
+ title: string | null;
573
+ titleSource: "user" | "agent" | null;
574
+ resources: ({
575
+ kind: "repository";
576
+ uri: string;
577
+ ref: string;
578
+ mountPath?: string | undefined;
579
+ subpath?: string | undefined;
580
+ githubInstallationId?: number | undefined;
581
+ githubRepositoryId?: number | undefined;
582
+ } | {
583
+ kind: "file";
584
+ fileId: string;
585
+ mountPath?: string | undefined;
586
+ })[];
587
+ tools: {
588
+ kind: "mcp";
589
+ id: string;
590
+ optional?: boolean | undefined;
591
+ }[];
592
+ metadata: Record<string, unknown>;
593
+ model: string;
594
+ sandboxBackend: "docker" | "modal" | "local" | "none" | "daytona" | "runloop" | "e2b" | "blaxel" | "cloudflare" | "vercel" | "selfhosted";
595
+ sandboxOs: "linux" | "macos" | "windows";
596
+ sandboxGroupId: string;
597
+ activeSandboxId: string | null;
598
+ activeEpoch: number;
599
+ environmentId: string | null;
600
+ firstPartyMcpPermissions: ("account:read" | "account:admin" | "members:manage" | "workspace:create" | "billing:read" | "billing:manage" | "workspace:read" | "workspace:admin" | "sessions:create" | "sessions:read" | "sessions:control" | "stream:view" | "stream:control" | "stream:acknowledge" | "files:upload" | "files:read" | "files:write" | "terminal:attach" | "documents:manage" | "documents:search" | "scheduled_tasks:manage" | "scheduled_tasks:run" | "github:manage" | "github:use" | "api_keys:manage" | "environments:manage" | "environments:use" | "goals:manage" | "enrollments:read" | "enrollments:manage")[] | null;
601
+ parentSessionId: string | null;
602
+ createIdempotencyKey: string | null;
603
+ temporalWorkflowId: string | null;
604
+ activeTurnId: string | null;
605
+ lastInputTokens: number | null;
606
+ lastSequence: number;
607
+ codexPinnedCredentialId: string | null;
608
+ codexLastCredentialId: string | null;
609
+ createdAt: string;
610
+ updatedAt: string;
611
+ }>;
612
+ declare function workflowIdForSession(sessionId: string): string;
613
+ /**
614
+ * Reject an explicit model that the host does not expose. The set of usable
615
+ * models is the union surfaced by `configuredAllowedModels` (the built-in
616
+ * provider's allow-list plus every registry provider's ids); a `model` outside
617
+ * it cannot be resolved to a provider at run time, so we fail the request at
618
+ * the API edge with 422 rather than enqueuing a turn the worker can't honor.
619
+ *
620
+ * `model` is the explicit, caller-supplied value (null/undefined when omitted).
621
+ * An omitted model defaults to `settings.openaiModel` downstream — which is
622
+ * always first in `configuredAllowedModels` — so only an explicit value is
623
+ * checked. Centralized here so every model-carrying choke point
624
+ * (create-session, user-message/turn-accept, queued-turn update, and
625
+ * scheduled-task agentConfig — a scheduled task is a session the worker runs
626
+ * later) and the MCP surfaces that share them validate identically and cannot
627
+ * drift.
628
+ */
629
+ declare function assertConfiguredModel(settings: Settings, model: string | null | undefined): void;
630
+ declare function requireQueuedTurnForApi(db: Database, workspaceId: string, sessionId: string, turnId: string): Promise<SessionTurn>;
631
+ declare function reasoningEffortForSession(metadata: Record<string, unknown>, fallback: Settings["openaiReasoningEffort"]): Settings["openaiReasoningEffort"];
632
+ /**
633
+ * Appends a `user.message` to an existing session and enqueues the resulting
634
+ * turn, merging requested resources/tools into the session and waking the
635
+ * workflow. Shared by the public events route and the first-party MCP
636
+ * `session_send_message` tool so the two surfaces cannot drift. Callers own
637
+ * resource/tool validation and the per-message usage limit before calling.
638
+ */
639
+ declare function postUserMessageTurn(input: {
640
+ db: Database;
641
+ bus: EventBus;
642
+ workflowClient: SessionWorkflowClient;
643
+ settings: Settings;
644
+ accountId: string;
645
+ workspaceId: string;
646
+ sessionId: string;
647
+ text: string;
648
+ resources: ResourceRef[];
649
+ tools: ToolRef[];
650
+ model?: string | null;
651
+ reasoningEffort?: Settings["openaiReasoningEffort"] | null;
652
+ clientEventId?: string;
653
+ }): Promise<{
654
+ accepted: SessionEvent;
655
+ turn: SessionTurn;
656
+ }>;
657
+ /**
658
+ * Full create-session flow shared by `POST /sessions` and the first-party MCP
659
+ * `session_create` tool: payload validation, resource/tool/environment
660
+ * checks, usage limits, session start, and usage recording. `rawPayload` is
661
+ * the unparsed request body so absent-vs-empty `tools` keeps its meaning
662
+ * (absent applies the workspace's default capability MCP tools).
663
+ */
664
+ declare function createSessionForRequest(deps: ApiRouteDeps, grant: AccessGrant, workspaceId: string, rawPayload: unknown): Promise<Session>;
665
+ /**
666
+ * Full accept-user-message flow shared by the `user.message` branch of
667
+ * `POST /sessions/:id/events` and the first-party MCP `session_send_message`
668
+ * tool: resource/tool validation, usage limits, the locked append + turn
669
+ * enqueue, and usage recording. `toolsProvided: false` applies the
670
+ * workspace's default capability MCP tools, matching an absent `tools` key.
671
+ */
672
+ declare function acceptSessionUserMessage(deps: ApiRouteDeps, grant: AccessGrant, workspaceId: string, sessionId: string, input: {
673
+ text: string;
674
+ resources?: ResourceRef[];
675
+ tools?: ToolRef[];
676
+ toolsProvided: boolean;
677
+ model?: string | null;
678
+ reasoningEffort?: ReasoningEffort | null;
679
+ clientEventId?: string;
680
+ }): Promise<{
681
+ accepted: SessionEvent;
682
+ turn: SessionTurn;
683
+ }>;
684
+ /**
685
+ * Shared title-write path for the manual rename route AND both MCP tools
686
+ * (set_session_title / set_other_session_title). The clobber guard lives in
687
+ * the db `updateSessionTitle` UPDATE: an agent write is skipped when a user
688
+ * title already pinned the session. On a real write we emit `session.title_set`
689
+ * exactly like goal mutations emit their events; when nothing changed (agent
690
+ * write blocked by the user lock) we emit nothing. Returns whether a write
691
+ * happened so callers can avoid double work.
692
+ */
693
+ declare function updateSessionTitle(deps: {
694
+ db: Database;
695
+ bus: EventBus;
696
+ }, workspaceId: string, sessionId: string, title: string, source: "user" | "agent"): Promise<{
697
+ updated: boolean;
698
+ title: string | null;
699
+ }>;
700
+
701
+ /** A member can manage other members (directly or via the admin wildcard). */
702
+ declare function memberCanAdminister(member: Pick<WorkspaceMember, "permissions">): boolean;
703
+ /** Only `user:` subjects are people; `api_key:` subjects belong to API keys. */
704
+ declare function isUserMember(member: Pick<WorkspaceMember, "subjectId">): boolean;
705
+ /**
706
+ * Turn an email lookup result into the membership subject id. A null id means
707
+ * no registered user matched the email — email invites for not-yet-registered
708
+ * users are deferred, so that is a 404 (not a 400) at the API surface.
709
+ */
710
+ declare function resolveMemberSubjectId(userId: string | null): string;
711
+ /**
712
+ * Guard the member-remove path. Refuses (409) to remove the caller's own
713
+ * membership and refuses to remove the last member that still holds an admin
714
+ * permission, so a workspace can never be orphaned with no one able to manage
715
+ * it. `members` is the full roster (every subject, including api_key ones —
716
+ * an api_key with workspace:admin still counts as an administering subject).
717
+ */
718
+ declare function assertWorkspaceMemberRemovable(input: {
719
+ members: WorkspaceMember[];
720
+ subjectId: string;
721
+ callerSubjectId: string;
722
+ }): void;
723
+ /**
724
+ * Guard the workspace-delete path before any external/DB mutation. Refuses
725
+ * (409) to delete the account's last workspace, and refuses while any session
726
+ * could still be running in Temporal (there is no clean per-session terminate
727
+ * to call first, so we will not orphan a workflow — the operator must stop the
728
+ * sessions first).
729
+ */
730
+ declare function assertWorkspaceDeletable(input: {
731
+ workspaceCountForAccount: number;
732
+ activeSessionCount: number;
733
+ }): void;
734
+
735
+ export { type AccessDeps, type ApiRouteDeps, type ApiSandboxClient, type ApiSandboxSession, type AppDependencies, type ChannelARoutingServices, type DocumentIndexClient, type FleetContext, type FleetListResult, type FleetLiveness, type FleetSandboxEntry, type FleetServices, type FleetSwapResult, type LimitCheckInput, MARKETING_SOCIAL_PACK_ID, MAX_ENVIRONMENTS_PER_WORKSPACE, MAX_VARIABLES_PER_ENVIRONMENT, type ManagedAuth, type McpCapabilityProbe, type McpCapabilityProbeInput, type McpCapabilityProbeResult, type ObjectStorageDependency, type ProvisionResult, type ResumeBoxByIdInput, type ResumedSandboxSession, type RunOnOp, type RunOnResult, type SessionWorkflowClient, acceptSessionUserMessage, assertAllowedEnvironmentVariableName, assertConfiguredModel, assertPackSandboxImageCompatible, assertWorkspaceDeletable, assertWorkspaceMemberRemovable, buildCapabilityCatalog, buildFleetContextForSession, buildMarketingDailyAnalysisAgentConfig, checkLimit, createAndStartSession, createCatalogItem, createSessionForRequest, createValidatedScheduledTask, disableCapability, discoverMcpRegistryCapabilities, enableCapability, enabledCapabilityMcpToolRefs, getCapabilityPack, hasPermission, isBuiltInCapabilityPack, isUserMember, listCapabilityPacks, listFleet, listWorkspaceCapabilityPacks, manualScheduledTaskTriggerUsageKey, manualScheduledTaskTriggerWorkflowId, memberCanAdminister, mergeResourceRefs, normalizeResources, officialMcpRegistryUrl, postUserMessageTurn, provisionSandbox, reasoningEffortForSession, recordEnvironmentAuditEvent, recordWorkspaceUsage, relayConfigFromSettings, relayDialBaseFromSettings, requireAccessContext, requireAccessGrant, requireEnvironmentEncryption, requireEnvironmentForApi, requireLimit, requirePermission, requireQueuedTurnForApi, requireScheduledTaskForApi, resolveCapabilityPack, resolveMemberSubjectId, restoreScheduledTask, routingEnabled, runOnSandbox, scheduledTaskTemporalScheduleId, scheduledTaskToolsProvided, scheduledTaskTriggerToken, settingsWithEnabledCapabilityMcpServers, settingsWithMcpCapabilityServers, swapActiveSandbox, syncCreatedScheduledTask, syncUpdatedScheduledTask, updateSessionTitle, validateEnvironmentAttachment, validateFileResources, validateGitHubRepositorySelection, validateGitHubRepositorySelectionShape, validateMcpCapabilityConnection, validateToolRefs, validatedScheduledTaskUpdate, withDefaultEnabledCapabilityMcpTools, workflowIdForSession, wrapChannelABoxWithRouting };