@opengeni/api-router 0.17.0 → 0.21.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/app.js +1 -1
- package/dist/{chunk-MWBF2GXL.js → chunk-5EEC7Q6C.js} +852 -111
- package/dist/chunk-5EEC7Q6C.js.map +1 -0
- package/dist/codex-realtime.d.ts +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/integrations/google-drive.d.ts +16 -1
- package/dist/mcp/documents.d.ts +3 -3
- package/dist/workspace-state-projection.d.ts +29 -1
- package/package.json +11 -11
- package/src/app.ts +33 -2
- package/src/codex-realtime.ts +4 -0
- package/src/index.ts +3 -2
- package/src/integrations/google-drive.ts +385 -20
- package/src/mcp/documents.ts +49 -27
- package/src/routes/connections.ts +61 -9
- package/src/routes/documents.ts +134 -43
- package/src/routes/workspace-instruction-policies.ts +118 -0
- package/src/routes/workspace-state.ts +46 -1
- package/src/workspace-state-projection.ts +203 -4
- package/dist/chunk-MWBF2GXL.js.map +0 -1
package/dist/codex-realtime.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export type CodexRealtimeBrokerInput = {
|
|
|
31
31
|
request: Pick<CodexRealtimeWebrtcRequest, "sdp" | "version" | "instructions" | "voice">;
|
|
32
32
|
signal?: AbortSignal | undefined;
|
|
33
33
|
};
|
|
34
|
-
export declare const OPENGENI_REALTIME_BASE_INSTRUCTIONS = "## Identity, tone, and role\n\nYou are the realtime conversational interface for the current session.\n\nBe concise, clear, and efficient. Keep responses tight and useful, with no fluff. Talk naturally like a trusted collaborator: warm, supportive, and easy to follow.\n\n## Interface and operating model\n\nThe backend handles execution and produces durable output and artifacts. You are the conversational surface of the same system.\n\nTreat the system as one unified assistant. Do not mention the backend, delegation, or that the system is composed of separate parts. Present execution work and results as work done by you.\n\nPass execution work to the backend. Do not block, filter, or withhold an execution request that should instead be passed through. Never refuse an execution request at the conversational layer: the backend makes the final judgment about feasibility, safety, permissions, approvals, and available tools.\n\nTreat backend outputs as authoritative. Do not override, contradict, embellish, or invent them.\n\nUse conversation to support execution: clarify briefly when necessary, acknowledge meaningful progress, answer succinctly, and make the next step clear. Do not use conversation as a substitute for execution or artifact generation.\n\n## Session context\n\nThe initial conversation items are authoritative context from the current session. Respect their roles and instruction hierarchy, use them for continuity, and continue naturally. Do not announce, summarize, or read the context aloud merely because it was added.\n\nLive context wrapped in <session_user_message> is an authoritative user message already routed to the current session. A status of queued_for_execution means it is waiting behind existing work; accepted_for_execution means it is next with no existing work ahead; accepted_for_steering means it was given priority as a change of direction, while any prior work may still be yielding. Incorporate it immediately as conversation context, but never delegate it again or treat the wrapper metadata as user-authored text.\n\nLive session updates may describe work that started before this realtime conversation, work sent directly by the user, or work delegated during an earlier realtime connection. Treat those updates as part of this same session even when they have no current delegation identity.\n\n## Backend use\n\nFor actions or tasks, always use the backend. If it is unclear whether backend use would help, use it.\n\nRespond directly only when the request is clearly self-contained and backend use would not meaningfully help.\n\nDo not claim that you cannot perform an action or lack access to tools, session state, workspace state, files, code, terminals, deployments, connected services, or other execution capabilities. Pass the request to the backend and let it determine what is available.\n\nAsk a clarifying question only when needed to avoid a materially harmful mistake or when essential information cannot reasonably be inferred. Otherwise, make a reasonable assumption and use the backend.\n\nGive the backend a complete standalone task containing the user's requested outcome, constraints, and all relevant context already established in the conversation. Do not make the user repeat information you already have.\n\nCreate only one delegation for one execution request. Do not submit duplicates while waiting. If the user supplies corrections, constraints, or updated context while work is running, immediately pass the update to the backend and identify the affected work.\n\n## Progress and completion\n\nBackend messages may be intermediate progress or final output. A completion result or error indicates that the delegated work has finished.\n\nDo not claim success, completion, or a changed state until authoritative backend output confirms it. If execution fails, explain the failure briefly and give the clearest supported next step without exposing raw internal errors.\n\nUse at most one short spoken acknowledgement before work that may take noticeable time. After that, speak only when a progress update is genuinely useful or the user explicitly asks for frequent updates. Do not fill waiting time with repeated reassurance.\n\n## Presenting results\n\nTreat backend output and artifacts as the authoritative execution record. Briefly tell the user the key takeaway, status, or next step without unnecessarily repeating detailed content unless asked.\n\nDo not read out or recreate tables, diffs, plots, code blocks, structured data, or other heavily formatted content by default. Present detailed backend content only when the user explicitly asks. If the user wants substantial output reformatted, transformed, or presented differently, use the backend.\n\n## Task-level user preferences\n\nTreat instructions about update frequency, verbosity, pacing, detail level, and presentation style as active task-level preferences. Continue following them until the task completes or the user changes them.\n\n## Voice behavior\n\nKeep direct answers to one or two short sentences by default. Ask one clarification question at a time. Give tool or execution results as the outcome first, followed only by the next useful action.\n\nOnly act on audio you understand with sufficient confidence. If speech is unclear, incomplete, ambiguous, or likely background conversation, ask for a brief clarification instead of guessing, reasoning from missing words, or using the backend.\n\n## Communication style\n\nWhen the user makes a clear request, proceed directly. Do not paraphrase the request, announce a plan, or add unnecessary framing.\n\nAvoid repetitive confirmation, filler, re-acknowledgement, and obvious play-by-play. By default, share progress only when it is brief, grounded, and genuinely useful.";
|
|
34
|
+
export declare const OPENGENI_REALTIME_BASE_INSTRUCTIONS = "## Identity, tone, and role\n\nYou are the realtime conversational interface for the current session.\n\nBe concise, clear, and efficient. Keep responses tight and useful, with no fluff. Talk naturally like a trusted collaborator: warm, supportive, and easy to follow.\n\n## Interface and operating model\n\nThe backend handles execution and produces durable output and artifacts. You are the conversational surface of the same system.\n\nTreat the system as one unified assistant. Do not mention the backend, delegation, or that the system is composed of separate parts. Present execution work and results as work done by you.\n\nPass execution work to the backend. Do not block, filter, or withhold an execution request that should instead be passed through. Never refuse an execution request at the conversational layer: the backend makes the final judgment about feasibility, safety, permissions, approvals, and available tools.\n\nTreat backend outputs as authoritative. Do not override, contradict, embellish, or invent them.\n\nUse conversation to support execution: clarify briefly when necessary, acknowledge meaningful progress, answer succinctly, and make the next step clear. Do not use conversation as a substitute for execution or artifact generation.\n\n## Session context\n\nThe initial conversation items are authoritative context from the current session. Respect their roles and instruction hierarchy, use them for continuity, and continue naturally. Do not announce, summarize, or read the context aloud merely because it was added.\n\nLive context wrapped in <session_user_message> is an authoritative user message already routed to the current session. A status of queued_for_execution means it is waiting behind existing work; accepted_for_execution means it is next with no existing work ahead; accepted_for_steering means it was given priority as a change of direction, while any prior work may still be yielding. Incorporate it immediately as conversation context, but never delegate it again or treat the wrapper metadata as user-authored text.\n\nLive context wrapped in <session_human_input_request> means current work is paused for the user's answer. Preserve the exact question meaning and options. Ask one question at a time when useful. The user may answer in the visible form or answer conversationally. If the user answers conversationally, create exactly one delegation containing the relevant question and the user's answer so the session agent can continue with complete context. If the user changes direction instead, delegate the new direction normally. Do not claim work resumed until session context confirms it.\n\nLive context wrapped in <session_human_input_response> is the authoritative outcome of that pending question. An answered or skipped response came through the structured session UI and is already routed; incorporate it, never delegate it again, and acknowledge briefly only if useful. An expired or cancelled response means the question is no longer active.\n\nLive session updates may describe work that started before this realtime conversation, work sent directly by the user, or work delegated during an earlier realtime connection. Treat those updates as part of this same session even when they have no current delegation identity.\n\n## Backend use\n\nFor actions or tasks, always use the backend. If it is unclear whether backend use would help, use it.\n\nRespond directly only when the request is clearly self-contained and backend use would not meaningfully help.\n\nDo not claim that you cannot perform an action or lack access to tools, session state, workspace state, files, code, terminals, deployments, connected services, or other execution capabilities. Pass the request to the backend and let it determine what is available.\n\nAsk a clarifying question only when needed to avoid a materially harmful mistake or when essential information cannot reasonably be inferred. Otherwise, make a reasonable assumption and use the backend.\n\nGive the backend a complete standalone task containing the user's requested outcome, constraints, and all relevant context already established in the conversation. Do not make the user repeat information you already have.\n\nCreate only one delegation for one execution request. Do not submit duplicates while waiting. If the user supplies corrections, constraints, or updated context while work is running, immediately pass the update to the backend and identify the affected work.\n\n## Progress and completion\n\nBackend messages may be intermediate progress or final output. A completion result or error indicates that the delegated work has finished.\n\nDo not claim success, completion, or a changed state until authoritative backend output confirms it. If execution fails, explain the failure briefly and give the clearest supported next step without exposing raw internal errors.\n\nUse at most one short spoken acknowledgement before work that may take noticeable time. After that, speak only when a progress update is genuinely useful or the user explicitly asks for frequent updates. Do not fill waiting time with repeated reassurance.\n\n## Presenting results\n\nTreat backend output and artifacts as the authoritative execution record. Briefly tell the user the key takeaway, status, or next step without unnecessarily repeating detailed content unless asked.\n\nDo not read out or recreate tables, diffs, plots, code blocks, structured data, or other heavily formatted content by default. Present detailed backend content only when the user explicitly asks. If the user wants substantial output reformatted, transformed, or presented differently, use the backend.\n\n## Task-level user preferences\n\nTreat instructions about update frequency, verbosity, pacing, detail level, and presentation style as active task-level preferences. Continue following them until the task completes or the user changes them.\n\n## Voice behavior\n\nKeep direct answers to one or two short sentences by default. Ask one clarification question at a time. Give tool or execution results as the outcome first, followed only by the next useful action.\n\nOnly act on audio you understand with sufficient confidence. If speech is unclear, incomplete, ambiguous, or likely background conversation, ask for a brief clarification instead of guessing, reasoning from missing words, or using the backend.\n\n## Communication style\n\nWhen the user makes a clear request, proceed directly. Do not paraphrase the request, announce a plan, or add unnecessary framing.\n\nAvoid repetitive confirmation, filler, re-acknowledgement, and obvious play-by-play. By default, share progress only when it is brief, grounded, and genuinely useful.";
|
|
35
35
|
export declare function openGeniRealtimeInstructions(additional?: string): string;
|
|
36
36
|
/**
|
|
37
37
|
* Credential-bound server broker. Selection is identical to a turn (pin then
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAppComposition,
|
|
3
3
|
startSlackInteractionPump
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5EEC7Q6C.js";
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import {
|
|
@@ -623,12 +623,13 @@ async function createTemporalWorkflowClient(settings, db) {
|
|
|
623
623
|
}
|
|
624
624
|
};
|
|
625
625
|
const documentIndexer = {
|
|
626
|
-
indexDocument: async (
|
|
626
|
+
indexDocument: async (input) => {
|
|
627
|
+
const { documentId } = input;
|
|
627
628
|
const workflowId = `document-index-${documentId}-${crypto.randomUUID()}`;
|
|
628
629
|
await temporal.workflow.start("documentIndexWorkflow", {
|
|
629
630
|
taskQueue: settings.temporalTaskQueue,
|
|
630
631
|
workflowId,
|
|
631
|
-
args: [
|
|
632
|
+
args: [input]
|
|
632
633
|
});
|
|
633
634
|
}
|
|
634
635
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/observability.ts","../src/sandbox/auth-callout.ts","../src/sandbox/metrics-ingestion.ts"],"sourcesContent":["import {\n dbSearchPath,\n getSettings,\n resolveNatsCalloutConfig,\n resolveNatsControlPlaneAuth,\n retryStartupDependency,\n startupRetryOptions,\n temporalConnectionOptions,\n} from \"@opengeni/config\";\nimport type {\n ScheduledTask,\n ScheduledTaskOverlapPolicy,\n ScheduledTaskScheduleSpec,\n} from \"@opengeni/contracts\";\nimport {\n assertRuntimeDatabasePosture,\n createDb,\n markSessionWorkflowWakeDelivered,\n runtimeDatabaseReadyCheck,\n type Database,\n} from \"@opengeni/db\";\nimport { createNatsEventBus, type ResponderConnection } from \"@opengeni/events\";\nimport { createObservability, logStartupDependencyRetry } from \"@opengeni/observability\";\nimport { SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID } from \"@opengeni/core\";\nimport {\n Connection,\n Client as TemporalClient,\n ScheduleNotFoundError,\n ScheduleOverlapPolicy,\n WorkflowExecutionAlreadyStartedError,\n} from \"@temporalio/client\";\nimport type { ScheduleOptions, ScheduleSpec, ScheduleUpdateOptions } from \"@temporalio/client\";\nimport { createAppComposition, type DocumentIndexClient, type SessionWorkflowClient } from \"./app\";\nimport { observabilityEventLogger } from \"./observability\";\nimport { startAuthCalloutResponder } from \"./sandbox/auth-callout\";\nimport { startHelloIngestion, startMetricsIngestion } from \"./sandbox/metrics-ingestion\";\nimport { startSlackInteractionPump } from \"./integrations/slack-interactions\";\n\n/**\n * A REJECT_DUPLICATE start collides on the deterministic workflowId when the\n * same manual trigger token fires twice. Temporal surfaces that as\n * WorkflowExecutionAlreadyStartedError; the caller treats it as an idempotent\n * no-op rather than a failure.\n */\nfunction isWorkflowAlreadyStarted(error: unknown): boolean {\n return error instanceof WorkflowExecutionAlreadyStartedError;\n}\n\nconst TEMPORAL_MONTHS = [\n \"JANUARY\",\n \"FEBRUARY\",\n \"MARCH\",\n \"APRIL\",\n \"MAY\",\n \"JUNE\",\n \"JULY\",\n \"AUGUST\",\n \"SEPTEMBER\",\n \"OCTOBER\",\n \"NOVEMBER\",\n \"DECEMBER\",\n] as const;\n\nexport async function createTemporalWorkflowClient(\n settings: ReturnType<typeof getSettings>,\n db: Database,\n): Promise<{\n client: SessionWorkflowClient;\n documentIndexer: DocumentIndexClient;\n close: () => Promise<void>;\n}> {\n const connection = await Connection.connect(temporalConnectionOptions(settings));\n const temporal = new TemporalClient({\n connection,\n namespace: settings.temporalNamespace,\n });\n const client: SessionWorkflowClient = {\n signalUserMessage: async ({ eventId, workflowId }) => {\n await temporal.workflow.getHandle(workflowId).signal(\"userMessage\", eventId);\n },\n wakeSessionWorkflow: async ({\n accountId,\n workspaceId,\n sessionId,\n workflowId,\n wakeRevision,\n interruptionRequested,\n }) => {\n await temporal.workflow.signalWithStart(\"sessionWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n workflowIdReusePolicy: \"ALLOW_DUPLICATE\",\n args: [{ accountId, workspaceId, sessionId }],\n signal: interruptionRequested ? \"sessionControl\" : \"queueChanged\",\n });\n await markSessionWorkflowWakeDelivered(db, {\n accountId,\n workspaceId,\n sessionId,\n temporalWorkflowId: workflowId,\n wakeRevision,\n });\n },\n requestSessionWorkflowWakeDispatch: async () => {\n await temporal.schedule\n .getHandle(SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID)\n .trigger(ScheduleOverlapPolicy.BUFFER_ONE);\n },\n signalCodexCapacity: async ({\n accountId,\n workspaceId,\n sessionId,\n workflowId,\n wakeRevision,\n workflowWakeRevision,\n }) => {\n await temporal.workflow.signalWithStart(\"sessionWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n workflowIdReusePolicy: \"ALLOW_DUPLICATE\",\n args: [{ accountId, workspaceId, sessionId }],\n signal: \"codexCapacityChanged\",\n signalArgs: [wakeRevision],\n });\n await markSessionWorkflowWakeDelivered(db, {\n accountId,\n workspaceId,\n sessionId,\n temporalWorkflowId: workflowId,\n wakeRevision: workflowWakeRevision,\n });\n },\n signalApprovalDecision: async ({\n accountId,\n workspaceId,\n sessionId,\n eventId,\n workflowId,\n workflowWakeRevision,\n }) => {\n await temporal.workflow.signalWithStart(\"sessionWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n workflowIdReusePolicy: \"ALLOW_DUPLICATE\",\n args: [{ accountId, workspaceId, sessionId }],\n signal: \"approvalDecision\",\n signalArgs: [eventId],\n });\n await markSessionWorkflowWakeDelivered(db, {\n accountId,\n workspaceId,\n sessionId,\n temporalWorkflowId: workflowId,\n wakeRevision: workflowWakeRevision,\n });\n },\n syncScheduledTask: async ({ task }) => {\n const schedule = temporal.schedule.getHandle(task.temporalScheduleId);\n const options = temporalScheduleOptions(task, settings.temporalTaskQueue);\n try {\n await schedule.update(() => temporalScheduleUpdateOptions(options));\n } catch (error) {\n if (!shouldCreateScheduleAfterUpdateError(error)) {\n throw error;\n }\n await temporal.schedule.create(options);\n }\n },\n deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {\n await temporal.schedule\n .getHandle(temporalScheduleId)\n .delete()\n .catch(() => undefined);\n },\n triggerScheduledTask: async ({\n task,\n agentRunUsageIdempotencyKey,\n triggerWorkflowId,\n initiator,\n }) => {\n // Deterministic workflowId (derived from the trigger token by the\n // caller) + REJECT_DUPLICATE makes a retried manual trigger idempotent:\n // the second start collides on the id and is rejected instead of\n // spawning a second run. The shared idempotency key dedupes the charge.\n const workflowId = triggerWorkflowId;\n try {\n await temporal.workflow.start(\"scheduledTaskFireWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n workflowIdReusePolicy: \"REJECT_DUPLICATE\",\n args: [\n {\n accountId: task.accountId,\n workspaceId: task.workspaceId,\n taskId: task.id,\n triggerType: \"manual\",\n agentRunUsageIdempotencyKey,\n initiator,\n },\n ],\n });\n } catch (error) {\n // A duplicate trigger token started this run already; treat the retry\n // as a no-op so the (idempotent) usage charge stays the only effect.\n if (isWorkflowAlreadyStarted(error)) {\n return;\n }\n throw error;\n }\n },\n startRigVerification: async ({ workspaceId, changeId, versionId, workflowId }) => {\n const targetId = changeId ?? versionId;\n if (!targetId) {\n throw new Error(\"rig verification requires changeId or versionId\");\n }\n await temporal.workflow.start(\"rigVerificationWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId: workflowId ?? `rig-verification-${targetId}-${crypto.randomUUID()}`,\n workflowIdReusePolicy: \"ALLOW_DUPLICATE\",\n args: [\n {\n workspaceId,\n ...(changeId ? { changeId } : {}),\n ...(versionId ? { versionId } : {}),\n },\n ],\n });\n },\n check: async () => {\n await connection.workflowService.getSystemInfo({});\n },\n };\n const documentIndexer: DocumentIndexClient = {\n indexDocument: async ({ accountId, workspaceId, documentId }) => {\n const workflowId = `document-index-${documentId}-${crypto.randomUUID()}`;\n await temporal.workflow.start(\"documentIndexWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n args: [{ accountId, workspaceId, documentId }],\n });\n },\n };\n return {\n client,\n documentIndexer,\n close: async () => {\n await connection.close();\n },\n };\n}\n\nexport async function startApi() {\n const settings = getSettings();\n const observability = createObservability(settings, { component: \"api\" });\n // Step I: standalone → dbSchema unset → searchPath undefined → today's plain\n // handle (public). Embedded → scoped to the dedicated schema + the host's RLS\n // strategy.\n const searchPath = dbSearchPath(settings);\n const dbClient = createDb(settings.databaseUrl, {\n ...(searchPath ? { searchPath } : {}),\n rlsStrategy: settings.rlsStrategy,\n });\n let bus: Awaited<ReturnType<typeof createNatsEventBus>> | undefined;\n let workflowClient: Awaited<ReturnType<typeof createTemporalWorkflowClient>> | undefined;\n const retryOptions = startupRetryOptions(settings);\n const onRetry = (event: Parameters<typeof logStartupDependencyRetry>[1]) =>\n logStartupDependencyRetry(observability, event);\n const databasePosture = {\n rlsStrategy: settings.rlsStrategy,\n expectedRole: settings.runtimeDatabaseRole,\n targetSchema: settings.dbSchema.trim() || \"public\",\n } as const;\n // The PRIVILEGED control-plane NATS login (M-AUTH): when the server runs with\n // auth_callout, api/worker authenticate as a static account user permitted to\n // request `agent.*.rpc`. Null in local dev (anonymous connect — the bus default).\n const controlPlaneAuth = resolveNatsControlPlaneAuth(settings);\n try {\n await retryStartupDependency(\n \"PostgreSQL runtime posture\",\n () => assertRuntimeDatabasePosture(dbClient.db, databasePosture),\n { ...retryOptions, onRetry },\n );\n bus = await retryStartupDependency(\n \"NATS\",\n () =>\n createNatsEventBus(\n settings.natsUrl,\n controlPlaneAuth\n ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password }\n : undefined,\n { logger: observabilityEventLogger(observability) },\n ),\n {\n ...retryOptions,\n onRetry,\n },\n );\n workflowClient = await retryStartupDependency(\n \"Temporal\",\n () => createTemporalWorkflowClient(settings, dbClient.db),\n {\n ...retryOptions,\n onRetry,\n },\n );\n } catch (error) {\n await Promise.allSettled([bus?.close(), workflowClient?.close(), dbClient.close()]);\n throw error;\n }\n if (!bus || !workflowClient) {\n await dbClient.close();\n throw new Error(\"OpenGeni API startup dependencies were not initialized\");\n }\n const { app, routeDeps } = createAppComposition({\n settings,\n db: dbClient.db,\n bus,\n workflowClient: workflowClient.client,\n documentIndexer: workflowClient.documentIndexer,\n observability,\n readinessChecks: {\n db: runtimeDatabaseReadyCheck(dbClient.db, databasePosture),\n },\n });\n const server = Bun.serve({\n hostname: settings.apiHost,\n port: settings.apiPort,\n idleTimeout: 255,\n fetch: app.fetch,\n });\n const stopSlackInteractionPump = settings.slackSigningSecret\n ? startSlackInteractionPump(routeDeps)\n : undefined;\n // M10 — start the metrics-ingestion consumer (agent heartbeats → DB last-sample\n // + downsampled series), gated on the selfhosted flag. A no-op when disabled.\n let stopMetricsIngestion: (() => void) | undefined;\n // Reconcile enrollments.has_display to the LIVE capability the agent reports in\n // its connect Hello (has_display was frozen at the enroll-time snapshot). Gated\n // on the same selfhosted flag.\n let stopHelloIngestion: (() => void) | undefined;\n // M-AUTH — start the NATS auth-callout responder (the tenancy boundary): it\n // validates an agent's enrollment bearer presented at NATS connect and mints a\n // workspace-scoped user JWT. Gated on the selfhosted flag + a resolvable callout\n // config; without the callout plane it never starts (selfhosted agents simply\n // cannot connect — graceful). It runs on its OWN connection (the callout auth\n // user), separate from the privileged control-plane bus.\n let authCalloutResponder: ResponderConnection | undefined;\n if (settings.sandboxSelfhostedEnabled) {\n stopMetricsIngestion = startMetricsIngestion({\n db: dbClient.db,\n bus,\n observability,\n });\n stopHelloIngestion = startHelloIngestion({\n db: dbClient.db,\n bus,\n observability,\n });\n observability.info(\"OpenGeni machine-metrics + hello ingestion consumers started\", {});\n\n const callout = resolveNatsCalloutConfig(settings);\n if (callout) {\n try {\n authCalloutResponder = await startAuthCalloutResponder(\n { db: dbClient.db, settings, callout, observability },\n settings.natsUrl,\n );\n } catch (error) {\n // A responder start failure must not crash the API (other planes work); log\n // loudly — selfhosted agents will fail to connect until it is up.\n observability.error(\"OpenGeni NATS auth-callout responder failed to start\", {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n } else {\n observability.warn(\n \"OpenGeni selfhosted enabled but the NATS auth-callout plane is not configured; selfhosted agents cannot connect\",\n {},\n );\n }\n }\n observability.info(\"OpenGeni API listening\", {\n host: settings.apiHost,\n port: settings.apiPort,\n });\n return {\n server,\n close: async () => {\n server.stop(true);\n stopSlackInteractionPump?.();\n stopMetricsIngestion?.();\n stopHelloIngestion?.();\n await Promise.allSettled([\n authCalloutResponder?.close(),\n bus.close(),\n workflowClient.close(),\n dbClient.close(),\n ]);\n },\n };\n}\n\nif (import.meta.main) {\n await startApi();\n}\n\nexport function temporalOverlapPolicy(policy: ScheduledTaskOverlapPolicy): ScheduleOverlapPolicy {\n if (policy === \"skip\") {\n return ScheduleOverlapPolicy.SKIP;\n }\n if (policy === \"buffer_one\") {\n return ScheduleOverlapPolicy.BUFFER_ONE;\n }\n return ScheduleOverlapPolicy.ALLOW_ALL;\n}\n\nexport function shouldCreateScheduleAfterUpdateError(error: unknown): boolean {\n return error instanceof ScheduleNotFoundError;\n}\n\nexport function temporalScheduleSpec(schedule: ScheduledTaskScheduleSpec): ScheduleSpec {\n if (schedule.type === \"interval\") {\n return {\n intervals: [{ every: `${schedule.everySeconds}s` }],\n ...(schedule.startAt ? { startAt: new Date(schedule.startAt) } : {}),\n ...(schedule.endAt ? { endAt: new Date(schedule.endAt) } : {}),\n };\n }\n if (schedule.type === \"calendar\") {\n return {\n calendars: [\n {\n hour: schedule.hour,\n minute: schedule.minute,\n second: 0,\n ...(schedule.daysOfWeek ? { dayOfWeek: schedule.daysOfWeek } : {}),\n },\n ],\n timezone: schedule.timeZone,\n };\n }\n const runAt = new Date(schedule.runAt);\n return {\n calendars: [\n {\n year: runAt.getUTCFullYear(),\n month: temporalMonth(runAt.getUTCMonth()),\n dayOfMonth: runAt.getUTCDate(),\n hour: runAt.getUTCHours(),\n minute: runAt.getUTCMinutes(),\n second: runAt.getUTCSeconds(),\n },\n ],\n timezone: \"UTC\",\n };\n}\n\nfunction temporalMonth(monthIndex: number) {\n return TEMPORAL_MONTHS[monthIndex]!;\n}\n\nfunction temporalScheduleOptions(task: ScheduledTask, taskQueue: string): ScheduleOptions {\n return {\n scheduleId: task.temporalScheduleId,\n spec: temporalScheduleSpec(task.schedule),\n action: {\n type: \"startWorkflow\",\n workflowType: \"scheduledTaskFireWorkflow\",\n taskQueue,\n args: [\n {\n accountId: task.accountId,\n workspaceId: task.workspaceId,\n taskId: task.id,\n triggerType: \"scheduled\",\n },\n ],\n },\n policies: {\n overlap: temporalOverlapPolicy(task.overlapPolicy),\n catchupWindow: \"24h\",\n pauseOnFailure: false,\n },\n state: {\n paused: task.status === \"paused\",\n ...(task.schedule.type === \"once\" ? { remainingActions: 1 } : {}),\n },\n memo: {\n accountId: task.accountId,\n workspaceId: task.workspaceId,\n scheduledTaskId: task.id,\n name: task.name,\n },\n };\n}\n\nfunction temporalScheduleUpdateOptions(options: ScheduleOptions): ScheduleUpdateOptions {\n return {\n spec: options.spec,\n action: options.action,\n ...(options.policies ? { policies: options.policies } : {}),\n state: options.state ?? {},\n ...(options.searchAttributes ? { searchAttributes: options.searchAttributes } : {}),\n ...(options.typedSearchAttributes\n ? { typedSearchAttributes: options.typedSearchAttributes }\n : {}),\n };\n}\n","import type { EventLogger } from \"@opengeni/events\";\nimport type { Attributes, AttributeValue, Observability } from \"@opengeni/observability\";\n\nexport function observabilityEventLogger(observability: Observability): EventLogger {\n return {\n debug: (message, attributes) => observability.debug(message, eventAttributes(attributes)),\n warn: (message, attributes) => observability.warn(message, eventAttributes(attributes)),\n };\n}\n\nfunction eventAttributes(attributes: Record<string, unknown> | undefined): Attributes | undefined {\n if (!attributes) {\n return undefined;\n }\n const sanitized: Attributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n sanitized[key] = eventAttributeValue(value);\n }\n return sanitized;\n}\n\nfunction eventAttributeValue(value: unknown): AttributeValue {\n if (\n value === null ||\n value === undefined ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n","// apps/api/src/sandbox/auth-callout.ts — the NATS AUTH-CALLOUT responder (the\n// bring-your-own-compute M-AUTH tenancy boundary; NATS Accounts per\n// workspace + §17 the isolation smoke + §19 the NATS-Accounts-misconfig leak risk).\n//\n// THE BOUNDARY THIS CLOSES: an external agent connects to NATS presenting its\n// `oge_` enrollment bearer as the connect auth-token. nats-server (configured with\n// `auth_callout`) issues an authorization request on $SYS.REQ.USER.AUTH. THIS\n// responder:\n// 1. decodes the authorization request (the `user_nkey` the response must scope\n// to, the `server_id` for the response `aud`, and the presented `auth_token`);\n// 2. VALIDATES the bearer with verifyEnrollmentBearer (HMAC, via\n// resolveEnrollmentSigningSecret) — an invalid/expired/forged bearer is denied;\n// 3. confirms the enrollment is still ACTIVE in the DB (a revoked machine is\n// denied even with a still-unexpired bearer);\n// 4. signs a NATS user JWT granting pub/sub ONLY `agent.<ws>.>` + `_INBOX.>`\n// (deny-all-else by an allow-list) and returns it inside a signed\n// authorization-response JWT.\n//\n// That per-subject scope is the per-workspace ISOLATION: workspace A's agent is\n// cryptographically incapable of pub/sub on `agent.B.>`. nats-server enforces the\n// signed permission set; the boundary does not rely on subject naming alone (the M4\n// transport test proved the CODE constructs scoped subjects; THIS proves the SERVER\n// refuses cross-workspace access).\n//\n// SECURITY (§18): the bearer value + the account signing seed are NEVER logged. A\n// validation failure → a DENIAL response (the server refuses the connection); a\n// responder error → the request is left UNANSWERED (fail-closed; the server denies\n// on its callout timeout). The bearer's `exp` caps the minted credential's life so a\n// revoked/expired enrollment cannot outlive its bearer.\n\nimport {\n resolveEnrollmentSigningSecret,\n type NatsCalloutConfig,\n type Settings,\n} from \"@opengeni/config\";\nimport { verifyEnrollmentBearer } from \"@opengeni/contracts\";\nimport { getEnrollment, type Database } from \"@opengeni/db\";\nimport {\n createResponderConnection,\n decodeAuthRequest,\n mintAuthResponse,\n mintUserJwt,\n workspaceAgentPermissions,\n type ResponderConnection,\n} from \"@opengeni/events\";\nimport type { Observability } from \"@opengeni/observability\";\nimport { observabilityEventLogger } from \"../observability\";\n\n/** The NATS subject nats-server publishes authorization requests on (ADR-26). */\nexport const AUTH_CALLOUT_SUBJECT = \"$SYS.REQ.USER.AUTH\";\n\nexport interface AuthCalloutDeps {\n db: Database;\n settings: Settings;\n callout: NatsCalloutConfig;\n observability?: Observability;\n}\n\n/**\n * The pure validate→scoped-JWT decision, isolated from the NATS transport so it is\n * unit-testable. Given the raw authorization-request JWT bytes, returns the signed\n * authorization-response JWT bytes to reply with — a GRANT (embedding a scoped user\n * JWT) on success, a DENIAL (carrying `nats.error`, no user JWT) otherwise. NEVER\n * throws on a bad/invalid request: every failure becomes a signed denial (the\n * server then refuses the connection cleanly).\n */\nexport async function handleAuthorizationRequest(\n deps: AuthCalloutDeps,\n requestBytes: Uint8Array,\n): Promise<Uint8Array> {\n const requestJwt = Buffer.from(requestBytes).toString(\"utf8\");\n const decoded = decodeAuthRequest(requestJwt);\n if (!decoded) {\n // A malformed request we cannot even read the user_nkey/server_id from — there\n // is nothing to scope a response to. Leave it for the server's timeout by\n // throwing (the transport leaves it unanswered, fail-closed).\n deps.observability?.warn?.(\"auth-callout: undecodable authorization request\", {});\n throw new Error(\"undecodable authorization request\");\n }\n\n const deny = (reason: string): Uint8Array => {\n // A SIGNED denial: the server reads `nats.error` and refuses the connection.\n const response = mintAuthResponse({\n userPublicKey: decoded.userNkey,\n serverId: decoded.serverId,\n accountSeed: deps.callout.accountSeed,\n error: reason,\n });\n return Buffer.from(response, \"utf8\");\n };\n\n const bearer = decoded.authToken;\n if (!bearer) {\n return deny(\"missing enrollment bearer\");\n }\n\n const secret = resolveEnrollmentSigningSecret(deps.settings);\n if (!secret) {\n // The credential plane is off for this deployment — deny rather than mint an\n // unscoped credential. (The responder should not even be running in this case,\n // but fail-closed regardless.)\n return deny(\"enrollment credential plane disabled\");\n }\n\n const claims = await verifyEnrollmentBearer(secret, bearer);\n if (!claims) {\n // Invalid signature / malformed / expired bearer. NEVER log the bearer value.\n deps.observability?.warn?.(\"auth-callout: rejected an invalid enrollment bearer\", {});\n return deny(\"invalid or expired enrollment bearer\");\n }\n\n // Confirm the enrollment is still ACTIVE — a revoked machine is denied even with a\n // still-unexpired bearer (the revoke path flips status; this re-checks at connect).\n const enrollment = await getEnrollment(deps.db, claims.workspaceId, claims.enrollmentId);\n if (!enrollment || enrollment.status !== \"active\") {\n deps.observability?.warn?.(\"auth-callout: denied a revoked or unknown enrollment\", {\n workspaceId: claims.workspaceId,\n agentId: claims.agentId,\n });\n return deny(\"enrollment is not active\");\n }\n\n // Belt-and-braces: the bearer's agentId/enrollmentId must match the row we found.\n // (verifyEnrollmentBearer already binds them; this guards a future schema where\n // agentId != enrollmentId.)\n if (enrollment.id !== claims.enrollmentId) {\n return deny(\"enrollment identity mismatch\");\n }\n\n // GRANT: a user JWT scoped to ONLY this workspace's agent subtree + the reply\n // inbox. This allow-list IS the per-workspace isolation boundary.\n const permissions = workspaceAgentPermissions(claims.workspaceId);\n const userJwt = mintUserJwt({\n userPublicKey: decoded.userNkey,\n accountSeed: deps.callout.accountSeed,\n name: claims.agentId,\n permissions,\n // Server-config-mode placement: the embedded user JWT's `aud` is the account\n // the user binds to (the configured `auth_callout.account`). All agents +\n // the privileged control plane share this account so subjects route; the\n // per-workspace isolation is carried by the subject permissions above.\n audienceAccount: deps.callout.accountName,\n // Tie the credential's life to the bearer's remaining life: a revoked/expired\n // enrollment cannot outlive its bearer at the NATS layer either.\n expiresAtSeconds: claims.exp,\n });\n const response = mintAuthResponse({\n userPublicKey: decoded.userNkey,\n serverId: decoded.serverId,\n accountSeed: deps.callout.accountSeed,\n userJwt,\n });\n deps.observability?.info?.(\"auth-callout: granted a workspace-scoped NATS credential\", {\n workspaceId: claims.workspaceId,\n agentId: claims.agentId,\n });\n return Buffer.from(response, \"utf8\");\n}\n\n/**\n * Start the auth-callout responder: open a SEPARATE NATS connection authenticated\n * as the callout `auth_users` user, subscribe $SYS.REQ.USER.AUTH, and answer every\n * authorization request via {@link handleAuthorizationRequest}. Returns a handle\n * whose `close()` drains the connection. Gated by the caller (sandboxSelfhostedEnabled\n * + a resolvable callout config); a deployment without the callout plane never starts\n * it.\n */\nexport async function startAuthCalloutResponder(\n deps: AuthCalloutDeps,\n natsUrl: string,\n): Promise<ResponderConnection> {\n const connection = await createResponderConnection(\n natsUrl,\n { kind: \"user-password\", user: deps.callout.user, pass: deps.callout.password },\n AUTH_CALLOUT_SUBJECT,\n (bytes) => handleAuthorizationRequest(deps, bytes),\n {\n name: \"opengeni-auth-callout\",\n ...(deps.observability ? { logger: observabilityEventLogger(deps.observability) } : {}),\n },\n );\n deps.observability?.info?.(\"OpenGeni NATS auth-callout responder started\", {\n subject: AUTH_CALLOUT_SUBJECT,\n });\n return connection;\n}\n","// apps/api/src/sandbox/metrics-ingestion.ts — the M10 metrics INGESTION consumer\n// + the connect-Hello DISPLAY-REFRESH consumer. The\n// enrolled agent piggybacks a `MetricsSample` on its ~5s heartbeat (an\n// `AgentEvent` published one-way on `agent.<ws>.<id>.events`) and publishes a\n// `Hello` (its live self-description) on `agent.<ws>.<id>.hello` on every connect\n// /reconnect. This module owns the two agent→control-plane inbound consumers:\n//\n// `agent.*.*.events` (heartbeat) →\n// 1. touchEnrollmentLastSeen — the liveness cursor (online/reconnecting/offline\n// derivation + the M3 probe disambiguation).\n// 2. ingestMachineMetricsSample — UPSERT machine_metrics_latest (the \"now\" row)\n// + APPEND a machine_metrics_series row downsampled to ~1/min.\n// A GOING-OFFLINE event is not a metrics point — liveness flips via the lease/\n// probe path; we skip it here (no-op).\n//\n// `agent.*.*.hello` (connect) →\n// refreshEnrollmentDisplay — reconcile `enrollments.has_display` to the LIVE\n// capability the Hello reports. `has_display` was previously FROZEN at the\n// enroll-time offer snapshot; a machine that GAINS a display later (a Mac that\n// grants Screen Recording, a box whose Xvfb starts) or LOSES one never\n// re-surfaced. Consuming the Hello's `capabilities.desktop` / `display` makes\n// `has_display` track reality (both directions), which the desktop-capability\n// gate (packages/runtime capabilities.ts) keys off.\n// refreshEnrollmentOpStream — reconcile `enrollments.op_stream` to the LIVE\n// runner capability the Hello reports, leaving legacy request/reply exec as the\n// fallback unless the runner advertises the streaming engine.\n//\n// Both consumers are BEST-EFFORT and fail-soft: a decode/DB error for one message\n// is logged + swallowed (the bus subscription already swallows handler throws) so\n// a metrics blip / a display-refresh write failure never tears down the consumer,\n// back-pressures the agent, or breaks its connect.\n\nimport {\n clearEnrollmentWentOffline,\n getEnrollment,\n ingestMachineMetricsSample,\n sessionsWithActiveOpOnEnrollment,\n setEnrollmentDisplayState,\n setEnrollmentOpStreamState,\n setEnrollmentWentOffline,\n touchEnrollmentLastSeen,\n type AppendEventInput,\n type Database,\n type MachineMetricsSample,\n} from \"@opengeni/db\";\nimport { appendAndPublishEvents, type EventBus } from \"@opengeni/events\";\nimport type { Observability } from \"@opengeni/observability\";\nimport {\n AgentEvent,\n GoingOfflineReason,\n Hello,\n goingOfflineReasonToJSON,\n type MetricsSample,\n} from \"@opengeni/agent-proto\";\n\n/** The wildcard subject the agent event plane publishes heartbeats on. */\nexport const AGENT_EVENTS_SUBJECT = \"agent.*.*.events\";\n\n/** The wildcard subject the agent publishes its connect Hello on. */\nexport const AGENT_HELLO_SUBJECT = \"agent.*.*.hello\";\n\n/**\n * Parse `agent.<ws>.<id>.<tail>` → `{ workspaceId, agentId }`, requiring the\n * expected tail token. Returns null for a subject that does not match the shape\n * (defensive — the subscription pattern already constrains it).\n */\nfunction parseAgentSubject(\n subject: string,\n tail: \"events\" | \"hello\",\n): { workspaceId: string; agentId: string } | null {\n const parts = subject.split(\".\");\n if (parts.length !== 4 || parts[0] !== \"agent\" || parts[3] !== tail) {\n return null;\n }\n return { workspaceId: parts[1]!, agentId: parts[2]! };\n}\n\n/** Parse `agent.<ws>.<id>.events` → `{ workspaceId, agentId }` (heartbeat plane). */\nexport function parseAgentEventSubject(\n subject: string,\n): { workspaceId: string; agentId: string } | null {\n return parseAgentSubject(subject, \"events\");\n}\n\n/** Parse `agent.<ws>.<id>.hello` → `{ workspaceId, agentId }` (connect plane). */\nexport function parseAgentHelloSubject(\n subject: string,\n): { workspaceId: string; agentId: string } | null {\n return parseAgentSubject(subject, \"hello\");\n}\n\n/**\n * Project a wire `MetricsSample` (proto, ms-stamped, GPU as a repeated list) to\n * the DB `MachineMetricsSample`. The proto byte/count fields are protobuf-encoded\n * as decimal strings (uint64) on the TS side (ts-proto `string`); coerce to\n * numbers. The DB carries a single `gpuUtilPercent` + `gpuMemUsedBytes`/Total —\n * we take the FIRST GPU (the dashboard surfaces the primary accelerator); absent\n * GPUs stay null (the not-reported contract). A zero on a non-GPU field is the\n * agent's \"not reported\" (we keep it null-friendly via `nullIfZero` only for the\n * GPU plane; cpu/mem/disk 0 is a legitimate reading the dashboard shows as 0).\n */\nexport function wireSampleToDbSample(wire: MetricsSample): MachineMetricsSample {\n const num = (v: string | number): number => (typeof v === \"number\" ? v : Number(v));\n const firstGpu = wire.gpus[0];\n return {\n cpuPercent: wire.cpuPercent,\n load1: wire.load1,\n load5: wire.load5,\n load15: wire.load15,\n memUsedBytes: num(wire.memUsedBytes),\n memTotalBytes: num(wire.memTotalBytes),\n diskUsedBytes: num(wire.diskUsedBytes),\n diskTotalBytes: num(wire.diskTotalBytes),\n gpuUtilPercent: firstGpu ? firstGpu.utilPercent : null,\n gpuMemUsedBytes: firstGpu ? num(firstGpu.memUsedBytes) : null,\n gpuMemTotalBytes: firstGpu ? num(firstGpu.memTotalBytes) : null,\n contention: wire.runQueue,\n // The sample carries its own wall-clock stamp (epoch ms); fall back to now on\n // a missing/zero stamp so a series row is never NULL-dated.\n sampledAt:\n wire.sampledAtMs && Number(wire.sampledAtMs) > 0\n ? new Date(Number(wire.sampledAtMs))\n : new Date(),\n };\n}\n\n/**\n * Ingest ONE decoded heartbeat for an enrolled machine. Resolves the enrollment's\n * accountId (needed for the RLS-scoped writes) from the enrollment row; an\n * unknown/cross-workspace agentId is ignored (no row → no write). Touches\n * last-seen + upserts latest + downsamples the series.\n */\nexport async function ingestHeartbeat(\n db: Database,\n input: { workspaceId: string; agentId: string; sample: MetricsSample },\n): Promise<{ ingested: boolean; seriesAppended: boolean }> {\n // The enrollment row is the source of the accountId (the RLS principal) and the\n // existence check. A revoked machine still reports its accountId, so we ingest\n // (the dashboard shows its last sample); a truly unknown id is a no-op.\n const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);\n if (!enrollment) {\n return { ingested: false, seriesAppended: false };\n }\n const sample = wireSampleToDbSample(input.sample);\n await touchEnrollmentLastSeen(db, {\n accountId: enrollment.accountId,\n workspaceId: input.workspaceId,\n enrollmentId: input.agentId,\n });\n const result = await ingestMachineMetricsSample(db, {\n accountId: enrollment.accountId,\n workspaceId: input.workspaceId,\n enrollmentId: input.agentId,\n sample,\n });\n return { ingested: true, seriesAppended: result.seriesAppended };\n}\n\n/**\n * Fan out one or more machine-LINK session events to the sessions that had an\n * active op running on the machine when its control link changed (per\n * `sessionsWithActiveOpOnEnrollment`) — the announce-only failure-visibility\n * plane. Each session's events are stamped on its OWN active turn. No matching\n * session ⇒ nothing is emitted (an idle-machine blip must never spam idle /\n * historical sessions). Called best-effort inside the handlers' fail-soft blocks.\n *\n * Each session's emission is ISOLATED: one session's append failing (a\n * session-specific constraint like a sequence collision from a racing writer, a\n * transient write error) is logged with that sessionId and skipped, never\n * aborting the fan-out — one session's failure must never cost the OTHER matching\n * sessions their events. A partial fan-out stays visible per-session in the logs.\n */\nasync function fanOutMachineLinkEvents(\n db: Database,\n bus: EventBus,\n observability: Observability | undefined,\n workspaceId: string,\n enrollmentId: string,\n build: (activeTurnId: string) => AppendEventInput[],\n): Promise<void> {\n const sessions = await sessionsWithActiveOpOnEnrollment(db, { workspaceId, enrollmentId });\n for (const session of sessions) {\n try {\n await appendAndPublishEvents(\n db,\n bus,\n workspaceId,\n session.sessionId,\n build(session.activeTurnId),\n );\n } catch (error) {\n observability?.warn?.(\"Failed to fan out a machine-link event to a session\", {\n workspaceId,\n sessionId: session.sessionId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n\n/**\n * Decode a raw `AgentEvent` payload + ingest it (the per-message handler). A\n * heartbeat carrying a metrics sample is ingested; a going-offline records the\n * machine-plane marker + fans out the link-plane session events. Decode failures\n * are reported + swallowed. `bus` (when present) enables the session-event\n * fan-out; the live consumer always supplies it, pure unit tests may omit it.\n */\nexport async function handleAgentEventPayload(\n db: Database,\n observability: Observability | undefined,\n payload: Uint8Array,\n subject: string,\n bus?: EventBus,\n): Promise<void> {\n const ids = parseAgentEventSubject(subject);\n if (!ids) {\n return;\n }\n let event: AgentEvent;\n try {\n event = AgentEvent.decode(payload);\n } catch (error) {\n observability?.warn?.(\"Failed to decode an agent event for metrics ingestion\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n // A clean GoingOffline is the machine-plane's typed shutdown signal. Two things\n // happen, in this order:\n // 1. Record it ALWAYS on the machine plane (a Prometheus counter keyed by the\n // typed reason) so a fleet operator can see clean stops / self-updates /\n // host shutdowns. This fires unconditionally, independent of the DB.\n // 2. Stamp the enrollment's clean going-offline marker so the liveness\n // derivation reads the machine OFFLINE immediately instead of waiting out\n // the last_seen dead-detect window. Best-effort + fail-soft (like the rest\n // of this module): an unknown enrollment is a no-op and a DB error is\n // swallowed so a bad write never tears down the consumer. Deliberately does\n // NOT touch last-seen (a shutdown must not look \"more recently alive\").\n if (event.event?.$case === \"goingOffline\") {\n const reason = goingOfflineReasonToJSON(event.event.goingOffline.reason);\n observability?.incrementCounter({\n name: \"opengeni_machine_going_offline_total\",\n help: \"Total Connected Machine clean GoingOffline signals by typed reason.\",\n labels: { reason },\n });\n try {\n const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId);\n if (enrollment) {\n await setEnrollmentWentOffline(db, {\n accountId: enrollment.accountId,\n workspaceId: ids.workspaceId,\n enrollmentId: ids.agentId,\n reason,\n });\n // Fan out the link-plane events to the sessions with an active op on this\n // machine: machine.link.lost (its control link is going away) for every\n // clean going-offline, PLUS machine.runner.restarted when the reason is a\n // self-update restart specifically (link.lost fires for it too; this\n // distinguishes a restart from a plain stop / host shutdown).\n if (bus) {\n const isSelfUpdate =\n event.event.goingOffline.reason === GoingOfflineReason.GOING_OFFLINE_REASON_UPDATE;\n await fanOutMachineLinkEvents(\n db,\n bus,\n observability,\n ids.workspaceId,\n ids.agentId,\n (activeTurnId) => {\n const events: AppendEventInput[] = [\n { type: \"machine.link.lost\", turnId: activeTurnId, payload: { reason } },\n ];\n if (isSelfUpdate) {\n events.push({\n type: \"machine.runner.restarted\",\n turnId: activeTurnId,\n payload: {},\n });\n }\n return events;\n },\n );\n }\n }\n } catch (error) {\n observability?.warn?.(\"Failed to record a machine clean going-offline\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n return;\n }\n if (event.event?.$case !== \"heartbeat\") {\n return; // an unknown event kind → not a metrics point.\n }\n const metrics = event.event.heartbeat.metrics;\n if (!metrics) {\n return; // a heartbeat without a sample → liveness already touched elsewhere.\n }\n try {\n await ingestHeartbeat(db, {\n workspaceId: ids.workspaceId,\n agentId: ids.agentId,\n sample: metrics,\n });\n } catch (error) {\n observability?.warn?.(\"Failed to ingest a machine metrics heartbeat\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Start the metrics-ingestion consumer: subscribe `agent.*.*.events` and ingest\n * every heartbeat. Gated by sandboxSelfhostedEnabled (the caller checks the flag;\n * a disabled deployment never starts the consumer). Returns the unsubscribe fn.\n */\nexport function startMetricsIngestion(deps: {\n db: Database;\n bus: EventBus;\n observability?: Observability;\n}): () => void {\n return deps.bus.subscribeAgentEvents(AGENT_EVENTS_SUBJECT, (payload, subject) =>\n handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus),\n );\n}\n\n// ── Connect-Hello display refresh ─────────────────────────────────────────────\n\n/**\n * The LIVE display presence the agent's Hello reports: a desktop framebuffer is\n * available (`capabilities.desktop`, which the agent sets true only when a display\n * probes AND it can stream it) OR a `Display` detail is present. An unset\n * Capabilities (or a headless machine) → false. This is what `has_display` should\n * track, replacing the enroll-time snapshot.\n */\nexport function helloReportsDisplay(hello: Hello): boolean {\n const caps = hello.capabilities;\n if (!caps) {\n return false;\n }\n // A CAPTURE-BLOCKED display is NOT a usable display: a Mac reports a display but\n // withholds `desktop` and sets `desktopUnavailableReason` when Screen Recording\n // (TCC) is not granted. Treating it as \"has display\" is exactly how the 0.1.3\n // incident hid — the machine claimed a desktop it could not capture, so it was\n // offered for computer-use and the model saw a blank. Gate it out here (the single\n // source of truth for `has_display`, consumed by both the machine state and the\n // capability negotiation). The `display`-present fallback is preserved for every\n // other case (e.g. a relay-less agent that reports a display but not `desktop`).\n if (caps.desktopUnavailableReason) {\n return false;\n }\n return caps.desktop === true || caps.display != null;\n}\n\n/**\n * The human, actionable reason a display is present but UNUSABLE (macOS Screen\n * Recording / TCC not granted), or null when capture is permitted / the machine is\n * headless. Normalizes the proto's non-optional \"\" empty string to null so the DB\n * carries a clean tri-state (a real reason vs. no reason) — the Machines dashboard\n * shows \"display: capture not granted\" only when this is non-null.\n */\nexport function helloDesktopUnavailableReason(hello: Hello): string | null {\n const reason = hello.capabilities?.desktopUnavailableReason;\n return reason ? reason : null;\n}\n\n/** Whether the runner's current Hello advertises the op-stream engine. */\nexport function helloReportsOpStream(hello: Hello): boolean {\n return hello.capabilities?.opStream === true;\n}\n\n/**\n * Reconcile `enrollments.has_display` (+ the capture-blocked reason) to what a Hello\n * reports. Resolves the enrollment (the accountId is the RLS principal + the\n * existence check + the current values). A no-change Hello short-circuits BEFORE\n * issuing any write (and the DB writer is itself change-guarded on BOTH fields as a\n * backstop), so a steady state never churns. An unknown/cross-workspace agentId is a\n * no-op.\n */\nexport async function refreshEnrollmentDisplay(\n db: Database,\n input: {\n workspaceId: string;\n agentId: string;\n hasDisplay: boolean;\n desktopUnavailableReason?: string | null;\n },\n): Promise<{ updated: boolean }> {\n const desktopUnavailableReason = input.desktopUnavailableReason ?? null;\n const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);\n if (!enrollment) {\n return { updated: false };\n }\n if (\n enrollment.hasDisplay === input.hasDisplay &&\n (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason\n ) {\n // Both fields unchanged — do not even issue the UPDATE (no churn on a\n // steady-state Hello).\n return { updated: false };\n }\n return await setEnrollmentDisplayState(db, {\n accountId: enrollment.accountId,\n workspaceId: input.workspaceId,\n enrollmentId: input.agentId,\n hasDisplay: input.hasDisplay,\n desktopUnavailableReason,\n });\n}\n\n/**\n * Reconcile `enrollments.op_stream` to what a Hello reports. Resolves the\n * enrollment first so the accountId remains the RLS principal and so a no-change\n * Hello short-circuits BEFORE issuing any write (the DB writer is itself\n * change-guarded as a backstop). An unknown/cross-workspace agentId is a no-op.\n */\nexport async function refreshEnrollmentOpStream(\n db: Database,\n input: {\n workspaceId: string;\n agentId: string;\n opStream: boolean;\n },\n): Promise<{ updated: boolean }> {\n const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);\n if (!enrollment) {\n return { updated: false };\n }\n if (enrollment.opStream === input.opStream) {\n // The capability is unchanged — do not even issue the UPDATE (no churn on a\n // steady-state Hello).\n return { updated: false };\n }\n return await setEnrollmentOpStreamState(db, {\n accountId: enrollment.accountId,\n workspaceId: input.workspaceId,\n enrollmentId: input.agentId,\n opStream: input.opStream,\n });\n}\n\n/**\n * Decode a raw `Hello` payload + refresh the enrollment's display cursor + clear\n * any pending clean going-offline marker and, when the reconnect actually cleared\n * one, fan out machine.link.restored to the sessions with an active op on the\n * machine (the per-message handler for the hello plane). Decode failures + write\n * failures are reported + swallowed — a Hello must NEVER break the agent's connect.\n * `bus` (when present) enables the link.restored fan-out.\n */\nexport async function handleHelloPayload(\n db: Database,\n observability: Observability | undefined,\n payload: Uint8Array,\n subject: string,\n bus?: EventBus,\n): Promise<void> {\n const ids = parseAgentHelloSubject(subject);\n if (!ids) {\n return;\n }\n let hello: Hello;\n try {\n hello = Hello.decode(payload);\n } catch (error) {\n observability?.warn?.(\"Failed to decode an agent Hello for display refresh\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n try {\n await refreshEnrollmentDisplay(db, {\n workspaceId: ids.workspaceId,\n agentId: ids.agentId,\n hasDisplay: helloReportsDisplay(hello),\n desktopUnavailableReason: helloDesktopUnavailableReason(hello),\n });\n await refreshEnrollmentOpStream(db, {\n workspaceId: ids.workspaceId,\n agentId: ids.agentId,\n opStream: helloReportsOpStream(hello),\n });\n } catch (error) {\n observability?.warn?.(\"Failed to refresh an enrollment's capabilities from a Hello\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n // A reconnect Hello re-announces the machine, so any pending clean going-offline\n // marker no longer holds — clear it so the liveness derivation stops reading the\n // machine offline. Best-effort + fail-soft, and change-guarded in the DB (a\n // steady-state Hello with no marker writes nothing), so this never breaks the\n // agent's connect and never churns. When a marker was ACTUALLY cleared (the\n // machine had been reported link.lost), fan out machine.link.restored to the\n // sessions with an active op on it — a restored only ever pairs a prior lost, so\n // a routine connect Hello (no marker) emits nothing.\n try {\n const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId);\n if (enrollment) {\n const { cleared } = await clearEnrollmentWentOffline(db, {\n accountId: enrollment.accountId,\n workspaceId: ids.workspaceId,\n enrollmentId: ids.agentId,\n });\n if (cleared && bus) {\n await fanOutMachineLinkEvents(\n db,\n bus,\n observability,\n ids.workspaceId,\n ids.agentId,\n (activeTurnId) => [{ type: \"machine.link.restored\", turnId: activeTurnId, payload: {} }],\n );\n }\n }\n } catch (error) {\n observability?.warn?.(\"Failed to clear a machine going-offline marker on a Hello\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Start the Hello display-refresh consumer: subscribe `agent.*.*.hello` and\n * reconcile `has_display` to the live capability the agent reports on every\n * connect. Gated by sandboxSelfhostedEnabled (the caller checks the flag). Returns\n * the unsubscribe fn.\n */\nexport function startHelloIngestion(deps: {\n db: Database;\n bus: EventBus;\n observability?: Observability;\n}): () => void {\n return deps.bus.subscribeAgentEvents(AGENT_HELLO_SUBJECT, (payload, subject) =>\n handleHelloPayload(deps.db, deps.observability, payload, subject, deps.bus),\n );\n}\n"],"mappings":"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","names":["response","getEnrollment"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/observability.ts","../src/sandbox/auth-callout.ts","../src/sandbox/metrics-ingestion.ts"],"sourcesContent":["import {\n dbSearchPath,\n getSettings,\n resolveNatsCalloutConfig,\n resolveNatsControlPlaneAuth,\n retryStartupDependency,\n startupRetryOptions,\n temporalConnectionOptions,\n} from \"@opengeni/config\";\nimport type {\n ScheduledTask,\n ScheduledTaskOverlapPolicy,\n ScheduledTaskScheduleSpec,\n} from \"@opengeni/contracts\";\nimport {\n assertRuntimeDatabasePosture,\n createDb,\n markSessionWorkflowWakeDelivered,\n runtimeDatabaseReadyCheck,\n type Database,\n} from \"@opengeni/db\";\nimport { createNatsEventBus, type ResponderConnection } from \"@opengeni/events\";\nimport { createObservability, logStartupDependencyRetry } from \"@opengeni/observability\";\nimport { SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID } from \"@opengeni/core\";\nimport {\n Connection,\n Client as TemporalClient,\n ScheduleNotFoundError,\n ScheduleOverlapPolicy,\n WorkflowExecutionAlreadyStartedError,\n} from \"@temporalio/client\";\nimport type { ScheduleOptions, ScheduleSpec, ScheduleUpdateOptions } from \"@temporalio/client\";\nimport { createAppComposition, type DocumentIndexClient, type SessionWorkflowClient } from \"./app\";\nimport { observabilityEventLogger } from \"./observability\";\nimport { startAuthCalloutResponder } from \"./sandbox/auth-callout\";\nimport { startHelloIngestion, startMetricsIngestion } from \"./sandbox/metrics-ingestion\";\nimport { startSlackInteractionPump } from \"./integrations/slack-interactions\";\n\n/**\n * A REJECT_DUPLICATE start collides on the deterministic workflowId when the\n * same manual trigger token fires twice. Temporal surfaces that as\n * WorkflowExecutionAlreadyStartedError; the caller treats it as an idempotent\n * no-op rather than a failure.\n */\nfunction isWorkflowAlreadyStarted(error: unknown): boolean {\n return error instanceof WorkflowExecutionAlreadyStartedError;\n}\n\nconst TEMPORAL_MONTHS = [\n \"JANUARY\",\n \"FEBRUARY\",\n \"MARCH\",\n \"APRIL\",\n \"MAY\",\n \"JUNE\",\n \"JULY\",\n \"AUGUST\",\n \"SEPTEMBER\",\n \"OCTOBER\",\n \"NOVEMBER\",\n \"DECEMBER\",\n] as const;\n\nexport async function createTemporalWorkflowClient(\n settings: ReturnType<typeof getSettings>,\n db: Database,\n): Promise<{\n client: SessionWorkflowClient;\n documentIndexer: DocumentIndexClient;\n close: () => Promise<void>;\n}> {\n const connection = await Connection.connect(temporalConnectionOptions(settings));\n const temporal = new TemporalClient({\n connection,\n namespace: settings.temporalNamespace,\n });\n const client: SessionWorkflowClient = {\n signalUserMessage: async ({ eventId, workflowId }) => {\n await temporal.workflow.getHandle(workflowId).signal(\"userMessage\", eventId);\n },\n wakeSessionWorkflow: async ({\n accountId,\n workspaceId,\n sessionId,\n workflowId,\n wakeRevision,\n interruptionRequested,\n }) => {\n await temporal.workflow.signalWithStart(\"sessionWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n workflowIdReusePolicy: \"ALLOW_DUPLICATE\",\n args: [{ accountId, workspaceId, sessionId }],\n signal: interruptionRequested ? \"sessionControl\" : \"queueChanged\",\n });\n await markSessionWorkflowWakeDelivered(db, {\n accountId,\n workspaceId,\n sessionId,\n temporalWorkflowId: workflowId,\n wakeRevision,\n });\n },\n requestSessionWorkflowWakeDispatch: async () => {\n await temporal.schedule\n .getHandle(SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID)\n .trigger(ScheduleOverlapPolicy.BUFFER_ONE);\n },\n signalCodexCapacity: async ({\n accountId,\n workspaceId,\n sessionId,\n workflowId,\n wakeRevision,\n workflowWakeRevision,\n }) => {\n await temporal.workflow.signalWithStart(\"sessionWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n workflowIdReusePolicy: \"ALLOW_DUPLICATE\",\n args: [{ accountId, workspaceId, sessionId }],\n signal: \"codexCapacityChanged\",\n signalArgs: [wakeRevision],\n });\n await markSessionWorkflowWakeDelivered(db, {\n accountId,\n workspaceId,\n sessionId,\n temporalWorkflowId: workflowId,\n wakeRevision: workflowWakeRevision,\n });\n },\n signalApprovalDecision: async ({\n accountId,\n workspaceId,\n sessionId,\n eventId,\n workflowId,\n workflowWakeRevision,\n }) => {\n await temporal.workflow.signalWithStart(\"sessionWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n workflowIdReusePolicy: \"ALLOW_DUPLICATE\",\n args: [{ accountId, workspaceId, sessionId }],\n signal: \"approvalDecision\",\n signalArgs: [eventId],\n });\n await markSessionWorkflowWakeDelivered(db, {\n accountId,\n workspaceId,\n sessionId,\n temporalWorkflowId: workflowId,\n wakeRevision: workflowWakeRevision,\n });\n },\n syncScheduledTask: async ({ task }) => {\n const schedule = temporal.schedule.getHandle(task.temporalScheduleId);\n const options = temporalScheduleOptions(task, settings.temporalTaskQueue);\n try {\n await schedule.update(() => temporalScheduleUpdateOptions(options));\n } catch (error) {\n if (!shouldCreateScheduleAfterUpdateError(error)) {\n throw error;\n }\n await temporal.schedule.create(options);\n }\n },\n deleteScheduledTaskSchedule: async ({ temporalScheduleId }) => {\n await temporal.schedule\n .getHandle(temporalScheduleId)\n .delete()\n .catch(() => undefined);\n },\n triggerScheduledTask: async ({\n task,\n agentRunUsageIdempotencyKey,\n triggerWorkflowId,\n initiator,\n }) => {\n // Deterministic workflowId (derived from the trigger token by the\n // caller) + REJECT_DUPLICATE makes a retried manual trigger idempotent:\n // the second start collides on the id and is rejected instead of\n // spawning a second run. The shared idempotency key dedupes the charge.\n const workflowId = triggerWorkflowId;\n try {\n await temporal.workflow.start(\"scheduledTaskFireWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n workflowIdReusePolicy: \"REJECT_DUPLICATE\",\n args: [\n {\n accountId: task.accountId,\n workspaceId: task.workspaceId,\n taskId: task.id,\n triggerType: \"manual\",\n agentRunUsageIdempotencyKey,\n initiator,\n },\n ],\n });\n } catch (error) {\n // A duplicate trigger token started this run already; treat the retry\n // as a no-op so the (idempotent) usage charge stays the only effect.\n if (isWorkflowAlreadyStarted(error)) {\n return;\n }\n throw error;\n }\n },\n startRigVerification: async ({ workspaceId, changeId, versionId, workflowId }) => {\n const targetId = changeId ?? versionId;\n if (!targetId) {\n throw new Error(\"rig verification requires changeId or versionId\");\n }\n await temporal.workflow.start(\"rigVerificationWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId: workflowId ?? `rig-verification-${targetId}-${crypto.randomUUID()}`,\n workflowIdReusePolicy: \"ALLOW_DUPLICATE\",\n args: [\n {\n workspaceId,\n ...(changeId ? { changeId } : {}),\n ...(versionId ? { versionId } : {}),\n },\n ],\n });\n },\n check: async () => {\n await connection.workflowService.getSystemInfo({});\n },\n };\n const documentIndexer: DocumentIndexClient = {\n indexDocument: async (input) => {\n const { documentId } = input;\n const workflowId = `document-index-${documentId}-${crypto.randomUUID()}`;\n await temporal.workflow.start(\"documentIndexWorkflow\", {\n taskQueue: settings.temporalTaskQueue,\n workflowId,\n args: [input],\n });\n },\n };\n return {\n client,\n documentIndexer,\n close: async () => {\n await connection.close();\n },\n };\n}\n\nexport async function startApi() {\n const settings = getSettings();\n const observability = createObservability(settings, { component: \"api\" });\n // Step I: standalone → dbSchema unset → searchPath undefined → today's plain\n // handle (public). Embedded → scoped to the dedicated schema + the host's RLS\n // strategy.\n const searchPath = dbSearchPath(settings);\n const dbClient = createDb(settings.databaseUrl, {\n ...(searchPath ? { searchPath } : {}),\n rlsStrategy: settings.rlsStrategy,\n });\n let bus: Awaited<ReturnType<typeof createNatsEventBus>> | undefined;\n let workflowClient: Awaited<ReturnType<typeof createTemporalWorkflowClient>> | undefined;\n const retryOptions = startupRetryOptions(settings);\n const onRetry = (event: Parameters<typeof logStartupDependencyRetry>[1]) =>\n logStartupDependencyRetry(observability, event);\n const databasePosture = {\n rlsStrategy: settings.rlsStrategy,\n expectedRole: settings.runtimeDatabaseRole,\n targetSchema: settings.dbSchema.trim() || \"public\",\n } as const;\n // The PRIVILEGED control-plane NATS login (M-AUTH): when the server runs with\n // auth_callout, api/worker authenticate as a static account user permitted to\n // request `agent.*.rpc`. Null in local dev (anonymous connect — the bus default).\n const controlPlaneAuth = resolveNatsControlPlaneAuth(settings);\n try {\n await retryStartupDependency(\n \"PostgreSQL runtime posture\",\n () => assertRuntimeDatabasePosture(dbClient.db, databasePosture),\n { ...retryOptions, onRetry },\n );\n bus = await retryStartupDependency(\n \"NATS\",\n () =>\n createNatsEventBus(\n settings.natsUrl,\n controlPlaneAuth\n ? { user: controlPlaneAuth.user, pass: controlPlaneAuth.password }\n : undefined,\n { logger: observabilityEventLogger(observability) },\n ),\n {\n ...retryOptions,\n onRetry,\n },\n );\n workflowClient = await retryStartupDependency(\n \"Temporal\",\n () => createTemporalWorkflowClient(settings, dbClient.db),\n {\n ...retryOptions,\n onRetry,\n },\n );\n } catch (error) {\n await Promise.allSettled([bus?.close(), workflowClient?.close(), dbClient.close()]);\n throw error;\n }\n if (!bus || !workflowClient) {\n await dbClient.close();\n throw new Error(\"OpenGeni API startup dependencies were not initialized\");\n }\n const { app, routeDeps } = createAppComposition({\n settings,\n db: dbClient.db,\n bus,\n workflowClient: workflowClient.client,\n documentIndexer: workflowClient.documentIndexer,\n observability,\n readinessChecks: {\n db: runtimeDatabaseReadyCheck(dbClient.db, databasePosture),\n },\n });\n const server = Bun.serve({\n hostname: settings.apiHost,\n port: settings.apiPort,\n idleTimeout: 255,\n fetch: app.fetch,\n });\n const stopSlackInteractionPump = settings.slackSigningSecret\n ? startSlackInteractionPump(routeDeps)\n : undefined;\n // M10 — start the metrics-ingestion consumer (agent heartbeats → DB last-sample\n // + downsampled series), gated on the selfhosted flag. A no-op when disabled.\n let stopMetricsIngestion: (() => void) | undefined;\n // Reconcile enrollments.has_display to the LIVE capability the agent reports in\n // its connect Hello (has_display was frozen at the enroll-time snapshot). Gated\n // on the same selfhosted flag.\n let stopHelloIngestion: (() => void) | undefined;\n // M-AUTH — start the NATS auth-callout responder (the tenancy boundary): it\n // validates an agent's enrollment bearer presented at NATS connect and mints a\n // workspace-scoped user JWT. Gated on the selfhosted flag + a resolvable callout\n // config; without the callout plane it never starts (selfhosted agents simply\n // cannot connect — graceful). It runs on its OWN connection (the callout auth\n // user), separate from the privileged control-plane bus.\n let authCalloutResponder: ResponderConnection | undefined;\n if (settings.sandboxSelfhostedEnabled) {\n stopMetricsIngestion = startMetricsIngestion({\n db: dbClient.db,\n bus,\n observability,\n });\n stopHelloIngestion = startHelloIngestion({\n db: dbClient.db,\n bus,\n observability,\n });\n observability.info(\"OpenGeni machine-metrics + hello ingestion consumers started\", {});\n\n const callout = resolveNatsCalloutConfig(settings);\n if (callout) {\n try {\n authCalloutResponder = await startAuthCalloutResponder(\n { db: dbClient.db, settings, callout, observability },\n settings.natsUrl,\n );\n } catch (error) {\n // A responder start failure must not crash the API (other planes work); log\n // loudly — selfhosted agents will fail to connect until it is up.\n observability.error(\"OpenGeni NATS auth-callout responder failed to start\", {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n } else {\n observability.warn(\n \"OpenGeni selfhosted enabled but the NATS auth-callout plane is not configured; selfhosted agents cannot connect\",\n {},\n );\n }\n }\n observability.info(\"OpenGeni API listening\", {\n host: settings.apiHost,\n port: settings.apiPort,\n });\n return {\n server,\n close: async () => {\n server.stop(true);\n stopSlackInteractionPump?.();\n stopMetricsIngestion?.();\n stopHelloIngestion?.();\n await Promise.allSettled([\n authCalloutResponder?.close(),\n bus.close(),\n workflowClient.close(),\n dbClient.close(),\n ]);\n },\n };\n}\n\nif (import.meta.main) {\n await startApi();\n}\n\nexport function temporalOverlapPolicy(policy: ScheduledTaskOverlapPolicy): ScheduleOverlapPolicy {\n if (policy === \"skip\") {\n return ScheduleOverlapPolicy.SKIP;\n }\n if (policy === \"buffer_one\") {\n return ScheduleOverlapPolicy.BUFFER_ONE;\n }\n return ScheduleOverlapPolicy.ALLOW_ALL;\n}\n\nexport function shouldCreateScheduleAfterUpdateError(error: unknown): boolean {\n return error instanceof ScheduleNotFoundError;\n}\n\nexport function temporalScheduleSpec(schedule: ScheduledTaskScheduleSpec): ScheduleSpec {\n if (schedule.type === \"interval\") {\n return {\n intervals: [{ every: `${schedule.everySeconds}s` }],\n ...(schedule.startAt ? { startAt: new Date(schedule.startAt) } : {}),\n ...(schedule.endAt ? { endAt: new Date(schedule.endAt) } : {}),\n };\n }\n if (schedule.type === \"calendar\") {\n return {\n calendars: [\n {\n hour: schedule.hour,\n minute: schedule.minute,\n second: 0,\n ...(schedule.daysOfWeek ? { dayOfWeek: schedule.daysOfWeek } : {}),\n },\n ],\n timezone: schedule.timeZone,\n };\n }\n const runAt = new Date(schedule.runAt);\n return {\n calendars: [\n {\n year: runAt.getUTCFullYear(),\n month: temporalMonth(runAt.getUTCMonth()),\n dayOfMonth: runAt.getUTCDate(),\n hour: runAt.getUTCHours(),\n minute: runAt.getUTCMinutes(),\n second: runAt.getUTCSeconds(),\n },\n ],\n timezone: \"UTC\",\n };\n}\n\nfunction temporalMonth(monthIndex: number) {\n return TEMPORAL_MONTHS[monthIndex]!;\n}\n\nfunction temporalScheduleOptions(task: ScheduledTask, taskQueue: string): ScheduleOptions {\n return {\n scheduleId: task.temporalScheduleId,\n spec: temporalScheduleSpec(task.schedule),\n action: {\n type: \"startWorkflow\",\n workflowType: \"scheduledTaskFireWorkflow\",\n taskQueue,\n args: [\n {\n accountId: task.accountId,\n workspaceId: task.workspaceId,\n taskId: task.id,\n triggerType: \"scheduled\",\n },\n ],\n },\n policies: {\n overlap: temporalOverlapPolicy(task.overlapPolicy),\n catchupWindow: \"24h\",\n pauseOnFailure: false,\n },\n state: {\n paused: task.status === \"paused\",\n ...(task.schedule.type === \"once\" ? { remainingActions: 1 } : {}),\n },\n memo: {\n accountId: task.accountId,\n workspaceId: task.workspaceId,\n scheduledTaskId: task.id,\n name: task.name,\n },\n };\n}\n\nfunction temporalScheduleUpdateOptions(options: ScheduleOptions): ScheduleUpdateOptions {\n return {\n spec: options.spec,\n action: options.action,\n ...(options.policies ? { policies: options.policies } : {}),\n state: options.state ?? {},\n ...(options.searchAttributes ? { searchAttributes: options.searchAttributes } : {}),\n ...(options.typedSearchAttributes\n ? { typedSearchAttributes: options.typedSearchAttributes }\n : {}),\n };\n}\n","import type { EventLogger } from \"@opengeni/events\";\nimport type { Attributes, AttributeValue, Observability } from \"@opengeni/observability\";\n\nexport function observabilityEventLogger(observability: Observability): EventLogger {\n return {\n debug: (message, attributes) => observability.debug(message, eventAttributes(attributes)),\n warn: (message, attributes) => observability.warn(message, eventAttributes(attributes)),\n };\n}\n\nfunction eventAttributes(attributes: Record<string, unknown> | undefined): Attributes | undefined {\n if (!attributes) {\n return undefined;\n }\n const sanitized: Attributes = {};\n for (const [key, value] of Object.entries(attributes)) {\n sanitized[key] = eventAttributeValue(value);\n }\n return sanitized;\n}\n\nfunction eventAttributeValue(value: unknown): AttributeValue {\n if (\n value === null ||\n value === undefined ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n","// apps/api/src/sandbox/auth-callout.ts — the NATS AUTH-CALLOUT responder (the\n// bring-your-own-compute M-AUTH tenancy boundary; NATS Accounts per\n// workspace + §17 the isolation smoke + §19 the NATS-Accounts-misconfig leak risk).\n//\n// THE BOUNDARY THIS CLOSES: an external agent connects to NATS presenting its\n// `oge_` enrollment bearer as the connect auth-token. nats-server (configured with\n// `auth_callout`) issues an authorization request on $SYS.REQ.USER.AUTH. THIS\n// responder:\n// 1. decodes the authorization request (the `user_nkey` the response must scope\n// to, the `server_id` for the response `aud`, and the presented `auth_token`);\n// 2. VALIDATES the bearer with verifyEnrollmentBearer (HMAC, via\n// resolveEnrollmentSigningSecret) — an invalid/expired/forged bearer is denied;\n// 3. confirms the enrollment is still ACTIVE in the DB (a revoked machine is\n// denied even with a still-unexpired bearer);\n// 4. signs a NATS user JWT granting pub/sub ONLY `agent.<ws>.>` + `_INBOX.>`\n// (deny-all-else by an allow-list) and returns it inside a signed\n// authorization-response JWT.\n//\n// That per-subject scope is the per-workspace ISOLATION: workspace A's agent is\n// cryptographically incapable of pub/sub on `agent.B.>`. nats-server enforces the\n// signed permission set; the boundary does not rely on subject naming alone (the M4\n// transport test proved the CODE constructs scoped subjects; THIS proves the SERVER\n// refuses cross-workspace access).\n//\n// SECURITY (§18): the bearer value + the account signing seed are NEVER logged. A\n// validation failure → a DENIAL response (the server refuses the connection); a\n// responder error → the request is left UNANSWERED (fail-closed; the server denies\n// on its callout timeout). The bearer's `exp` caps the minted credential's life so a\n// revoked/expired enrollment cannot outlive its bearer.\n\nimport {\n resolveEnrollmentSigningSecret,\n type NatsCalloutConfig,\n type Settings,\n} from \"@opengeni/config\";\nimport { verifyEnrollmentBearer } from \"@opengeni/contracts\";\nimport { getEnrollment, type Database } from \"@opengeni/db\";\nimport {\n createResponderConnection,\n decodeAuthRequest,\n mintAuthResponse,\n mintUserJwt,\n workspaceAgentPermissions,\n type ResponderConnection,\n} from \"@opengeni/events\";\nimport type { Observability } from \"@opengeni/observability\";\nimport { observabilityEventLogger } from \"../observability\";\n\n/** The NATS subject nats-server publishes authorization requests on (ADR-26). */\nexport const AUTH_CALLOUT_SUBJECT = \"$SYS.REQ.USER.AUTH\";\n\nexport interface AuthCalloutDeps {\n db: Database;\n settings: Settings;\n callout: NatsCalloutConfig;\n observability?: Observability;\n}\n\n/**\n * The pure validate→scoped-JWT decision, isolated from the NATS transport so it is\n * unit-testable. Given the raw authorization-request JWT bytes, returns the signed\n * authorization-response JWT bytes to reply with — a GRANT (embedding a scoped user\n * JWT) on success, a DENIAL (carrying `nats.error`, no user JWT) otherwise. NEVER\n * throws on a bad/invalid request: every failure becomes a signed denial (the\n * server then refuses the connection cleanly).\n */\nexport async function handleAuthorizationRequest(\n deps: AuthCalloutDeps,\n requestBytes: Uint8Array,\n): Promise<Uint8Array> {\n const requestJwt = Buffer.from(requestBytes).toString(\"utf8\");\n const decoded = decodeAuthRequest(requestJwt);\n if (!decoded) {\n // A malformed request we cannot even read the user_nkey/server_id from — there\n // is nothing to scope a response to. Leave it for the server's timeout by\n // throwing (the transport leaves it unanswered, fail-closed).\n deps.observability?.warn?.(\"auth-callout: undecodable authorization request\", {});\n throw new Error(\"undecodable authorization request\");\n }\n\n const deny = (reason: string): Uint8Array => {\n // A SIGNED denial: the server reads `nats.error` and refuses the connection.\n const response = mintAuthResponse({\n userPublicKey: decoded.userNkey,\n serverId: decoded.serverId,\n accountSeed: deps.callout.accountSeed,\n error: reason,\n });\n return Buffer.from(response, \"utf8\");\n };\n\n const bearer = decoded.authToken;\n if (!bearer) {\n return deny(\"missing enrollment bearer\");\n }\n\n const secret = resolveEnrollmentSigningSecret(deps.settings);\n if (!secret) {\n // The credential plane is off for this deployment — deny rather than mint an\n // unscoped credential. (The responder should not even be running in this case,\n // but fail-closed regardless.)\n return deny(\"enrollment credential plane disabled\");\n }\n\n const claims = await verifyEnrollmentBearer(secret, bearer);\n if (!claims) {\n // Invalid signature / malformed / expired bearer. NEVER log the bearer value.\n deps.observability?.warn?.(\"auth-callout: rejected an invalid enrollment bearer\", {});\n return deny(\"invalid or expired enrollment bearer\");\n }\n\n // Confirm the enrollment is still ACTIVE — a revoked machine is denied even with a\n // still-unexpired bearer (the revoke path flips status; this re-checks at connect).\n const enrollment = await getEnrollment(deps.db, claims.workspaceId, claims.enrollmentId);\n if (!enrollment || enrollment.status !== \"active\") {\n deps.observability?.warn?.(\"auth-callout: denied a revoked or unknown enrollment\", {\n workspaceId: claims.workspaceId,\n agentId: claims.agentId,\n });\n return deny(\"enrollment is not active\");\n }\n\n // Belt-and-braces: the bearer's agentId/enrollmentId must match the row we found.\n // (verifyEnrollmentBearer already binds them; this guards a future schema where\n // agentId != enrollmentId.)\n if (enrollment.id !== claims.enrollmentId) {\n return deny(\"enrollment identity mismatch\");\n }\n\n // GRANT: a user JWT scoped to ONLY this workspace's agent subtree + the reply\n // inbox. This allow-list IS the per-workspace isolation boundary.\n const permissions = workspaceAgentPermissions(claims.workspaceId);\n const userJwt = mintUserJwt({\n userPublicKey: decoded.userNkey,\n accountSeed: deps.callout.accountSeed,\n name: claims.agentId,\n permissions,\n // Server-config-mode placement: the embedded user JWT's `aud` is the account\n // the user binds to (the configured `auth_callout.account`). All agents +\n // the privileged control plane share this account so subjects route; the\n // per-workspace isolation is carried by the subject permissions above.\n audienceAccount: deps.callout.accountName,\n // Tie the credential's life to the bearer's remaining life: a revoked/expired\n // enrollment cannot outlive its bearer at the NATS layer either.\n expiresAtSeconds: claims.exp,\n });\n const response = mintAuthResponse({\n userPublicKey: decoded.userNkey,\n serverId: decoded.serverId,\n accountSeed: deps.callout.accountSeed,\n userJwt,\n });\n deps.observability?.info?.(\"auth-callout: granted a workspace-scoped NATS credential\", {\n workspaceId: claims.workspaceId,\n agentId: claims.agentId,\n });\n return Buffer.from(response, \"utf8\");\n}\n\n/**\n * Start the auth-callout responder: open a SEPARATE NATS connection authenticated\n * as the callout `auth_users` user, subscribe $SYS.REQ.USER.AUTH, and answer every\n * authorization request via {@link handleAuthorizationRequest}. Returns a handle\n * whose `close()` drains the connection. Gated by the caller (sandboxSelfhostedEnabled\n * + a resolvable callout config); a deployment without the callout plane never starts\n * it.\n */\nexport async function startAuthCalloutResponder(\n deps: AuthCalloutDeps,\n natsUrl: string,\n): Promise<ResponderConnection> {\n const connection = await createResponderConnection(\n natsUrl,\n { kind: \"user-password\", user: deps.callout.user, pass: deps.callout.password },\n AUTH_CALLOUT_SUBJECT,\n (bytes) => handleAuthorizationRequest(deps, bytes),\n {\n name: \"opengeni-auth-callout\",\n ...(deps.observability ? { logger: observabilityEventLogger(deps.observability) } : {}),\n },\n );\n deps.observability?.info?.(\"OpenGeni NATS auth-callout responder started\", {\n subject: AUTH_CALLOUT_SUBJECT,\n });\n return connection;\n}\n","// apps/api/src/sandbox/metrics-ingestion.ts — the M10 metrics INGESTION consumer\n// + the connect-Hello DISPLAY-REFRESH consumer. The\n// enrolled agent piggybacks a `MetricsSample` on its ~5s heartbeat (an\n// `AgentEvent` published one-way on `agent.<ws>.<id>.events`) and publishes a\n// `Hello` (its live self-description) on `agent.<ws>.<id>.hello` on every connect\n// /reconnect. This module owns the two agent→control-plane inbound consumers:\n//\n// `agent.*.*.events` (heartbeat) →\n// 1. touchEnrollmentLastSeen — the liveness cursor (online/reconnecting/offline\n// derivation + the M3 probe disambiguation).\n// 2. ingestMachineMetricsSample — UPSERT machine_metrics_latest (the \"now\" row)\n// + APPEND a machine_metrics_series row downsampled to ~1/min.\n// A GOING-OFFLINE event is not a metrics point — liveness flips via the lease/\n// probe path; we skip it here (no-op).\n//\n// `agent.*.*.hello` (connect) →\n// refreshEnrollmentDisplay — reconcile `enrollments.has_display` to the LIVE\n// capability the Hello reports. `has_display` was previously FROZEN at the\n// enroll-time offer snapshot; a machine that GAINS a display later (a Mac that\n// grants Screen Recording, a box whose Xvfb starts) or LOSES one never\n// re-surfaced. Consuming the Hello's `capabilities.desktop` / `display` makes\n// `has_display` track reality (both directions), which the desktop-capability\n// gate (packages/runtime capabilities.ts) keys off.\n// refreshEnrollmentOpStream — reconcile `enrollments.op_stream` to the LIVE\n// runner capability the Hello reports, leaving legacy request/reply exec as the\n// fallback unless the runner advertises the streaming engine.\n//\n// Both consumers are BEST-EFFORT and fail-soft: a decode/DB error for one message\n// is logged + swallowed (the bus subscription already swallows handler throws) so\n// a metrics blip / a display-refresh write failure never tears down the consumer,\n// back-pressures the agent, or breaks its connect.\n\nimport {\n clearEnrollmentWentOffline,\n getEnrollment,\n ingestMachineMetricsSample,\n sessionsWithActiveOpOnEnrollment,\n setEnrollmentDisplayState,\n setEnrollmentOpStreamState,\n setEnrollmentWentOffline,\n touchEnrollmentLastSeen,\n type AppendEventInput,\n type Database,\n type MachineMetricsSample,\n} from \"@opengeni/db\";\nimport { appendAndPublishEvents, type EventBus } from \"@opengeni/events\";\nimport type { Observability } from \"@opengeni/observability\";\nimport {\n AgentEvent,\n GoingOfflineReason,\n Hello,\n goingOfflineReasonToJSON,\n type MetricsSample,\n} from \"@opengeni/agent-proto\";\n\n/** The wildcard subject the agent event plane publishes heartbeats on. */\nexport const AGENT_EVENTS_SUBJECT = \"agent.*.*.events\";\n\n/** The wildcard subject the agent publishes its connect Hello on. */\nexport const AGENT_HELLO_SUBJECT = \"agent.*.*.hello\";\n\n/**\n * Parse `agent.<ws>.<id>.<tail>` → `{ workspaceId, agentId }`, requiring the\n * expected tail token. Returns null for a subject that does not match the shape\n * (defensive — the subscription pattern already constrains it).\n */\nfunction parseAgentSubject(\n subject: string,\n tail: \"events\" | \"hello\",\n): { workspaceId: string; agentId: string } | null {\n const parts = subject.split(\".\");\n if (parts.length !== 4 || parts[0] !== \"agent\" || parts[3] !== tail) {\n return null;\n }\n return { workspaceId: parts[1]!, agentId: parts[2]! };\n}\n\n/** Parse `agent.<ws>.<id>.events` → `{ workspaceId, agentId }` (heartbeat plane). */\nexport function parseAgentEventSubject(\n subject: string,\n): { workspaceId: string; agentId: string } | null {\n return parseAgentSubject(subject, \"events\");\n}\n\n/** Parse `agent.<ws>.<id>.hello` → `{ workspaceId, agentId }` (connect plane). */\nexport function parseAgentHelloSubject(\n subject: string,\n): { workspaceId: string; agentId: string } | null {\n return parseAgentSubject(subject, \"hello\");\n}\n\n/**\n * Project a wire `MetricsSample` (proto, ms-stamped, GPU as a repeated list) to\n * the DB `MachineMetricsSample`. The proto byte/count fields are protobuf-encoded\n * as decimal strings (uint64) on the TS side (ts-proto `string`); coerce to\n * numbers. The DB carries a single `gpuUtilPercent` + `gpuMemUsedBytes`/Total —\n * we take the FIRST GPU (the dashboard surfaces the primary accelerator); absent\n * GPUs stay null (the not-reported contract). A zero on a non-GPU field is the\n * agent's \"not reported\" (we keep it null-friendly via `nullIfZero` only for the\n * GPU plane; cpu/mem/disk 0 is a legitimate reading the dashboard shows as 0).\n */\nexport function wireSampleToDbSample(wire: MetricsSample): MachineMetricsSample {\n const num = (v: string | number): number => (typeof v === \"number\" ? v : Number(v));\n const firstGpu = wire.gpus[0];\n return {\n cpuPercent: wire.cpuPercent,\n load1: wire.load1,\n load5: wire.load5,\n load15: wire.load15,\n memUsedBytes: num(wire.memUsedBytes),\n memTotalBytes: num(wire.memTotalBytes),\n diskUsedBytes: num(wire.diskUsedBytes),\n diskTotalBytes: num(wire.diskTotalBytes),\n gpuUtilPercent: firstGpu ? firstGpu.utilPercent : null,\n gpuMemUsedBytes: firstGpu ? num(firstGpu.memUsedBytes) : null,\n gpuMemTotalBytes: firstGpu ? num(firstGpu.memTotalBytes) : null,\n contention: wire.runQueue,\n // The sample carries its own wall-clock stamp (epoch ms); fall back to now on\n // a missing/zero stamp so a series row is never NULL-dated.\n sampledAt:\n wire.sampledAtMs && Number(wire.sampledAtMs) > 0\n ? new Date(Number(wire.sampledAtMs))\n : new Date(),\n };\n}\n\n/**\n * Ingest ONE decoded heartbeat for an enrolled machine. Resolves the enrollment's\n * accountId (needed for the RLS-scoped writes) from the enrollment row; an\n * unknown/cross-workspace agentId is ignored (no row → no write). Touches\n * last-seen + upserts latest + downsamples the series.\n */\nexport async function ingestHeartbeat(\n db: Database,\n input: { workspaceId: string; agentId: string; sample: MetricsSample },\n): Promise<{ ingested: boolean; seriesAppended: boolean }> {\n // The enrollment row is the source of the accountId (the RLS principal) and the\n // existence check. A revoked machine still reports its accountId, so we ingest\n // (the dashboard shows its last sample); a truly unknown id is a no-op.\n const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);\n if (!enrollment) {\n return { ingested: false, seriesAppended: false };\n }\n const sample = wireSampleToDbSample(input.sample);\n await touchEnrollmentLastSeen(db, {\n accountId: enrollment.accountId,\n workspaceId: input.workspaceId,\n enrollmentId: input.agentId,\n });\n const result = await ingestMachineMetricsSample(db, {\n accountId: enrollment.accountId,\n workspaceId: input.workspaceId,\n enrollmentId: input.agentId,\n sample,\n });\n return { ingested: true, seriesAppended: result.seriesAppended };\n}\n\n/**\n * Fan out one or more machine-LINK session events to the sessions that had an\n * active op running on the machine when its control link changed (per\n * `sessionsWithActiveOpOnEnrollment`) — the announce-only failure-visibility\n * plane. Each session's events are stamped on its OWN active turn. No matching\n * session ⇒ nothing is emitted (an idle-machine blip must never spam idle /\n * historical sessions). Called best-effort inside the handlers' fail-soft blocks.\n *\n * Each session's emission is ISOLATED: one session's append failing (a\n * session-specific constraint like a sequence collision from a racing writer, a\n * transient write error) is logged with that sessionId and skipped, never\n * aborting the fan-out — one session's failure must never cost the OTHER matching\n * sessions their events. A partial fan-out stays visible per-session in the logs.\n */\nasync function fanOutMachineLinkEvents(\n db: Database,\n bus: EventBus,\n observability: Observability | undefined,\n workspaceId: string,\n enrollmentId: string,\n build: (activeTurnId: string) => AppendEventInput[],\n): Promise<void> {\n const sessions = await sessionsWithActiveOpOnEnrollment(db, { workspaceId, enrollmentId });\n for (const session of sessions) {\n try {\n await appendAndPublishEvents(\n db,\n bus,\n workspaceId,\n session.sessionId,\n build(session.activeTurnId),\n );\n } catch (error) {\n observability?.warn?.(\"Failed to fan out a machine-link event to a session\", {\n workspaceId,\n sessionId: session.sessionId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n}\n\n/**\n * Decode a raw `AgentEvent` payload + ingest it (the per-message handler). A\n * heartbeat carrying a metrics sample is ingested; a going-offline records the\n * machine-plane marker + fans out the link-plane session events. Decode failures\n * are reported + swallowed. `bus` (when present) enables the session-event\n * fan-out; the live consumer always supplies it, pure unit tests may omit it.\n */\nexport async function handleAgentEventPayload(\n db: Database,\n observability: Observability | undefined,\n payload: Uint8Array,\n subject: string,\n bus?: EventBus,\n): Promise<void> {\n const ids = parseAgentEventSubject(subject);\n if (!ids) {\n return;\n }\n let event: AgentEvent;\n try {\n event = AgentEvent.decode(payload);\n } catch (error) {\n observability?.warn?.(\"Failed to decode an agent event for metrics ingestion\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n // A clean GoingOffline is the machine-plane's typed shutdown signal. Two things\n // happen, in this order:\n // 1. Record it ALWAYS on the machine plane (a Prometheus counter keyed by the\n // typed reason) so a fleet operator can see clean stops / self-updates /\n // host shutdowns. This fires unconditionally, independent of the DB.\n // 2. Stamp the enrollment's clean going-offline marker so the liveness\n // derivation reads the machine OFFLINE immediately instead of waiting out\n // the last_seen dead-detect window. Best-effort + fail-soft (like the rest\n // of this module): an unknown enrollment is a no-op and a DB error is\n // swallowed so a bad write never tears down the consumer. Deliberately does\n // NOT touch last-seen (a shutdown must not look \"more recently alive\").\n if (event.event?.$case === \"goingOffline\") {\n const reason = goingOfflineReasonToJSON(event.event.goingOffline.reason);\n observability?.incrementCounter({\n name: \"opengeni_machine_going_offline_total\",\n help: \"Total Connected Machine clean GoingOffline signals by typed reason.\",\n labels: { reason },\n });\n try {\n const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId);\n if (enrollment) {\n await setEnrollmentWentOffline(db, {\n accountId: enrollment.accountId,\n workspaceId: ids.workspaceId,\n enrollmentId: ids.agentId,\n reason,\n });\n // Fan out the link-plane events to the sessions with an active op on this\n // machine: machine.link.lost (its control link is going away) for every\n // clean going-offline, PLUS machine.runner.restarted when the reason is a\n // self-update restart specifically (link.lost fires for it too; this\n // distinguishes a restart from a plain stop / host shutdown).\n if (bus) {\n const isSelfUpdate =\n event.event.goingOffline.reason === GoingOfflineReason.GOING_OFFLINE_REASON_UPDATE;\n await fanOutMachineLinkEvents(\n db,\n bus,\n observability,\n ids.workspaceId,\n ids.agentId,\n (activeTurnId) => {\n const events: AppendEventInput[] = [\n { type: \"machine.link.lost\", turnId: activeTurnId, payload: { reason } },\n ];\n if (isSelfUpdate) {\n events.push({\n type: \"machine.runner.restarted\",\n turnId: activeTurnId,\n payload: {},\n });\n }\n return events;\n },\n );\n }\n }\n } catch (error) {\n observability?.warn?.(\"Failed to record a machine clean going-offline\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n return;\n }\n if (event.event?.$case !== \"heartbeat\") {\n return; // an unknown event kind → not a metrics point.\n }\n const metrics = event.event.heartbeat.metrics;\n if (!metrics) {\n return; // a heartbeat without a sample → liveness already touched elsewhere.\n }\n try {\n await ingestHeartbeat(db, {\n workspaceId: ids.workspaceId,\n agentId: ids.agentId,\n sample: metrics,\n });\n } catch (error) {\n observability?.warn?.(\"Failed to ingest a machine metrics heartbeat\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Start the metrics-ingestion consumer: subscribe `agent.*.*.events` and ingest\n * every heartbeat. Gated by sandboxSelfhostedEnabled (the caller checks the flag;\n * a disabled deployment never starts the consumer). Returns the unsubscribe fn.\n */\nexport function startMetricsIngestion(deps: {\n db: Database;\n bus: EventBus;\n observability?: Observability;\n}): () => void {\n return deps.bus.subscribeAgentEvents(AGENT_EVENTS_SUBJECT, (payload, subject) =>\n handleAgentEventPayload(deps.db, deps.observability, payload, subject, deps.bus),\n );\n}\n\n// ── Connect-Hello display refresh ─────────────────────────────────────────────\n\n/**\n * The LIVE display presence the agent's Hello reports: a desktop framebuffer is\n * available (`capabilities.desktop`, which the agent sets true only when a display\n * probes AND it can stream it) OR a `Display` detail is present. An unset\n * Capabilities (or a headless machine) → false. This is what `has_display` should\n * track, replacing the enroll-time snapshot.\n */\nexport function helloReportsDisplay(hello: Hello): boolean {\n const caps = hello.capabilities;\n if (!caps) {\n return false;\n }\n // A CAPTURE-BLOCKED display is NOT a usable display: a Mac reports a display but\n // withholds `desktop` and sets `desktopUnavailableReason` when Screen Recording\n // (TCC) is not granted. Treating it as \"has display\" is exactly how the 0.1.3\n // incident hid — the machine claimed a desktop it could not capture, so it was\n // offered for computer-use and the model saw a blank. Gate it out here (the single\n // source of truth for `has_display`, consumed by both the machine state and the\n // capability negotiation). The `display`-present fallback is preserved for every\n // other case (e.g. a relay-less agent that reports a display but not `desktop`).\n if (caps.desktopUnavailableReason) {\n return false;\n }\n return caps.desktop === true || caps.display != null;\n}\n\n/**\n * The human, actionable reason a display is present but UNUSABLE (macOS Screen\n * Recording / TCC not granted), or null when capture is permitted / the machine is\n * headless. Normalizes the proto's non-optional \"\" empty string to null so the DB\n * carries a clean tri-state (a real reason vs. no reason) — the Machines dashboard\n * shows \"display: capture not granted\" only when this is non-null.\n */\nexport function helloDesktopUnavailableReason(hello: Hello): string | null {\n const reason = hello.capabilities?.desktopUnavailableReason;\n return reason ? reason : null;\n}\n\n/** Whether the runner's current Hello advertises the op-stream engine. */\nexport function helloReportsOpStream(hello: Hello): boolean {\n return hello.capabilities?.opStream === true;\n}\n\n/**\n * Reconcile `enrollments.has_display` (+ the capture-blocked reason) to what a Hello\n * reports. Resolves the enrollment (the accountId is the RLS principal + the\n * existence check + the current values). A no-change Hello short-circuits BEFORE\n * issuing any write (and the DB writer is itself change-guarded on BOTH fields as a\n * backstop), so a steady state never churns. An unknown/cross-workspace agentId is a\n * no-op.\n */\nexport async function refreshEnrollmentDisplay(\n db: Database,\n input: {\n workspaceId: string;\n agentId: string;\n hasDisplay: boolean;\n desktopUnavailableReason?: string | null;\n },\n): Promise<{ updated: boolean }> {\n const desktopUnavailableReason = input.desktopUnavailableReason ?? null;\n const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);\n if (!enrollment) {\n return { updated: false };\n }\n if (\n enrollment.hasDisplay === input.hasDisplay &&\n (enrollment.desktopUnavailableReason ?? null) === desktopUnavailableReason\n ) {\n // Both fields unchanged — do not even issue the UPDATE (no churn on a\n // steady-state Hello).\n return { updated: false };\n }\n return await setEnrollmentDisplayState(db, {\n accountId: enrollment.accountId,\n workspaceId: input.workspaceId,\n enrollmentId: input.agentId,\n hasDisplay: input.hasDisplay,\n desktopUnavailableReason,\n });\n}\n\n/**\n * Reconcile `enrollments.op_stream` to what a Hello reports. Resolves the\n * enrollment first so the accountId remains the RLS principal and so a no-change\n * Hello short-circuits BEFORE issuing any write (the DB writer is itself\n * change-guarded as a backstop). An unknown/cross-workspace agentId is a no-op.\n */\nexport async function refreshEnrollmentOpStream(\n db: Database,\n input: {\n workspaceId: string;\n agentId: string;\n opStream: boolean;\n },\n): Promise<{ updated: boolean }> {\n const enrollment = await getEnrollment(db, input.workspaceId, input.agentId);\n if (!enrollment) {\n return { updated: false };\n }\n if (enrollment.opStream === input.opStream) {\n // The capability is unchanged — do not even issue the UPDATE (no churn on a\n // steady-state Hello).\n return { updated: false };\n }\n return await setEnrollmentOpStreamState(db, {\n accountId: enrollment.accountId,\n workspaceId: input.workspaceId,\n enrollmentId: input.agentId,\n opStream: input.opStream,\n });\n}\n\n/**\n * Decode a raw `Hello` payload + refresh the enrollment's display cursor + clear\n * any pending clean going-offline marker and, when the reconnect actually cleared\n * one, fan out machine.link.restored to the sessions with an active op on the\n * machine (the per-message handler for the hello plane). Decode failures + write\n * failures are reported + swallowed — a Hello must NEVER break the agent's connect.\n * `bus` (when present) enables the link.restored fan-out.\n */\nexport async function handleHelloPayload(\n db: Database,\n observability: Observability | undefined,\n payload: Uint8Array,\n subject: string,\n bus?: EventBus,\n): Promise<void> {\n const ids = parseAgentHelloSubject(subject);\n if (!ids) {\n return;\n }\n let hello: Hello;\n try {\n hello = Hello.decode(payload);\n } catch (error) {\n observability?.warn?.(\"Failed to decode an agent Hello for display refresh\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n return;\n }\n try {\n await refreshEnrollmentDisplay(db, {\n workspaceId: ids.workspaceId,\n agentId: ids.agentId,\n hasDisplay: helloReportsDisplay(hello),\n desktopUnavailableReason: helloDesktopUnavailableReason(hello),\n });\n await refreshEnrollmentOpStream(db, {\n workspaceId: ids.workspaceId,\n agentId: ids.agentId,\n opStream: helloReportsOpStream(hello),\n });\n } catch (error) {\n observability?.warn?.(\"Failed to refresh an enrollment's capabilities from a Hello\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n // A reconnect Hello re-announces the machine, so any pending clean going-offline\n // marker no longer holds — clear it so the liveness derivation stops reading the\n // machine offline. Best-effort + fail-soft, and change-guarded in the DB (a\n // steady-state Hello with no marker writes nothing), so this never breaks the\n // agent's connect and never churns. When a marker was ACTUALLY cleared (the\n // machine had been reported link.lost), fan out machine.link.restored to the\n // sessions with an active op on it — a restored only ever pairs a prior lost, so\n // a routine connect Hello (no marker) emits nothing.\n try {\n const enrollment = await getEnrollment(db, ids.workspaceId, ids.agentId);\n if (enrollment) {\n const { cleared } = await clearEnrollmentWentOffline(db, {\n accountId: enrollment.accountId,\n workspaceId: ids.workspaceId,\n enrollmentId: ids.agentId,\n });\n if (cleared && bus) {\n await fanOutMachineLinkEvents(\n db,\n bus,\n observability,\n ids.workspaceId,\n ids.agentId,\n (activeTurnId) => [{ type: \"machine.link.restored\", turnId: activeTurnId, payload: {} }],\n );\n }\n }\n } catch (error) {\n observability?.warn?.(\"Failed to clear a machine going-offline marker on a Hello\", {\n subject,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\n/**\n * Start the Hello display-refresh consumer: subscribe `agent.*.*.hello` and\n * reconcile `has_display` to the live capability the agent reports on every\n * connect. Gated by sandboxSelfhostedEnabled (the caller checks the flag). Returns\n * the unsubscribe fn.\n */\nexport function startHelloIngestion(deps: {\n db: Database;\n bus: EventBus;\n observability?: Observability;\n}): () => void {\n return deps.bus.subscribeAgentEvents(AGENT_HELLO_SUBJECT, (payload, subject) =>\n handleHelloPayload(deps.db, deps.observability, payload, subject, deps.bus),\n );\n}\n"],"mappings":"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","names":["response","getEnrollment"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { GoogleDriveOAuthStartResponse, type GoogleDriveOAuthStartRequest } from "@opengeni/contracts/google-drive";
|
|
1
|
+
import { GoogleDriveOAuthStartResponse, type GoogleDriveDisconnectRequest, type GoogleDriveLifecycleActionRequest, type GoogleDriveOAuthStartRequest } from "@opengeni/contracts/google-drive";
|
|
2
2
|
import type { ApiRouteDeps } from "@opengeni/core";
|
|
3
|
+
import { getConnectionMetadata } from "@opengeni/db";
|
|
4
|
+
type GoogleDriveConnectionRecord = NonNullable<Awaited<ReturnType<typeof getConnectionMetadata>>>;
|
|
3
5
|
export declare function startGoogleDriveOAuth(deps: ApiRouteDeps, input: {
|
|
4
6
|
accountId: string;
|
|
5
7
|
workspaceId: string;
|
|
@@ -15,6 +17,18 @@ export declare function completeGoogleDriveOAuthCallback(deps: ApiRouteDeps, inp
|
|
|
15
17
|
}): Promise<{
|
|
16
18
|
redirectTo: string;
|
|
17
19
|
}>;
|
|
20
|
+
export declare function transitionGoogleDriveLifecycle(deps: ApiRouteDeps, input: {
|
|
21
|
+
workspaceId: string;
|
|
22
|
+
subjectId: string;
|
|
23
|
+
connectionId: string;
|
|
24
|
+
payload: GoogleDriveLifecycleActionRequest;
|
|
25
|
+
}): Promise<import("@opengeni/db").ConnectionMetadataWithVerification>;
|
|
26
|
+
export declare function disconnectGoogleDrive(deps: ApiRouteDeps, input: {
|
|
27
|
+
workspaceId: string;
|
|
28
|
+
subjectId: string;
|
|
29
|
+
connection: GoogleDriveConnectionRecord;
|
|
30
|
+
payload: GoogleDriveDisconnectRequest;
|
|
31
|
+
}): Promise<import("@opengeni/db").ConnectionMetadataWithVerification | null>;
|
|
18
32
|
export declare function browseGoogleDrive(deps: ApiRouteDeps, input: {
|
|
19
33
|
workspaceId: string;
|
|
20
34
|
subjectId: string;
|
|
@@ -74,3 +88,4 @@ export declare function saveGoogleDriveSource(deps: ApiRouteDeps, input: {
|
|
|
74
88
|
connectionId: string;
|
|
75
89
|
payload: unknown;
|
|
76
90
|
}): Promise<import("@opengeni/db").ConnectionMetadataWithVerification>;
|
|
91
|
+
export {};
|
package/dist/mcp/documents.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type DocumentServices } from "@opengeni/documents";
|
|
2
2
|
import { type Database } from "@opengeni/db";
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
export declare function buildDocumentsMcpServer(db: Database, accountId: string, workspaceId: string, documentServices: DocumentServices, options
|
|
4
|
+
export declare function buildDocumentsMcpServer(db: Database, accountId: string, workspaceId: string, documentServices: DocumentServices, options: {
|
|
5
5
|
createdBySessionId?: string | undefined;
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/** Immutable human subject whose agent is making this retrieval request. */
|
|
7
|
+
initiatingSubjectId: string;
|
|
8
8
|
}): McpServer;
|
|
@@ -1,16 +1,44 @@
|
|
|
1
|
-
import { type WorkspaceInstructionPolicyListResponse, type WorkspaceStateResponse as WorkspaceStateResponseType } from "@opengeni/contracts";
|
|
1
|
+
import { type WorkspaceInstructionPolicyListResponse, type WorkspaceInstructionPolicySnapshot, type WorkspaceStateResponse as WorkspaceStateResponseType } from "@opengeni/contracts";
|
|
2
2
|
import type { WorkspaceStateMemoryRecord } from "@opengeni/db";
|
|
3
3
|
import type { DocumentInventory } from "@opengeni/documents";
|
|
4
4
|
type KnowledgeProjectionInput = {
|
|
5
5
|
documents: DocumentInventory;
|
|
6
6
|
memories: WorkspaceStateMemoryRecord[];
|
|
7
7
|
};
|
|
8
|
+
type PreferenceGovernanceIdentity = {
|
|
9
|
+
id: string;
|
|
10
|
+
revisionId: string;
|
|
11
|
+
contentHash: string;
|
|
12
|
+
activeVersion: number;
|
|
13
|
+
scope: "organization" | "workspace" | "user";
|
|
14
|
+
};
|
|
15
|
+
type AttemptGovernanceProjectionInput = {
|
|
16
|
+
status: "unavailable";
|
|
17
|
+
} | {
|
|
18
|
+
status: "available";
|
|
19
|
+
attemptId: string;
|
|
20
|
+
executionGeneration: number;
|
|
21
|
+
acceptedAt: string;
|
|
22
|
+
policySnapshot: WorkspaceInstructionPolicySnapshot | null;
|
|
23
|
+
preferenceSnapshot: {
|
|
24
|
+
id: string;
|
|
25
|
+
descriptorHash: string;
|
|
26
|
+
descriptors: PreferenceGovernanceIdentity[];
|
|
27
|
+
truncated: boolean;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
} | null;
|
|
30
|
+
currentPreferences: {
|
|
31
|
+
descriptors: PreferenceGovernanceIdentity[];
|
|
32
|
+
truncated: boolean;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
8
35
|
export type WorkspaceStateProjectionInput = {
|
|
9
36
|
workspaceId: string;
|
|
10
37
|
generatedAt: string;
|
|
11
38
|
workspaceAgentInstructions: string | null;
|
|
12
39
|
policies: WorkspaceInstructionPolicyListResponse;
|
|
13
40
|
knowledge: KnowledgeProjectionInput | null;
|
|
41
|
+
attemptGovernance?: AttemptGovernanceProjectionInput | null;
|
|
14
42
|
};
|
|
15
43
|
export declare function projectWorkspaceState(input: WorkspaceStateProjectionInput): WorkspaceStateResponseType;
|
|
16
44
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/api-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -44,17 +44,17 @@
|
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
45
|
"@opengeni/agent-proto": "^0.3.0",
|
|
46
46
|
"@opengeni/codex": "^0.2.10",
|
|
47
|
-
"@opengeni/config": "^0.10.
|
|
48
|
-
"@opengeni/contracts": "^0.
|
|
49
|
-
"@opengeni/core": "^0.
|
|
50
|
-
"@opengeni/db": "^0.
|
|
51
|
-
"@opengeni/documents": "^0.
|
|
52
|
-
"@opengeni/events": "^0.3.
|
|
53
|
-
"@opengeni/github": "^0.4.
|
|
47
|
+
"@opengeni/config": "^0.10.7",
|
|
48
|
+
"@opengeni/contracts": "^0.36.0",
|
|
49
|
+
"@opengeni/core": "^0.20.3",
|
|
50
|
+
"@opengeni/db": "^0.27.0",
|
|
51
|
+
"@opengeni/documents": "^0.3.3",
|
|
52
|
+
"@opengeni/events": "^0.3.66",
|
|
53
|
+
"@opengeni/github": "^0.4.23",
|
|
54
54
|
"@opengeni/network": "^0.1.1",
|
|
55
|
-
"@opengeni/observability": "^0.4.
|
|
56
|
-
"@opengeni/runtime": "^0.18.
|
|
57
|
-
"@opengeni/storage": "^0.2.
|
|
55
|
+
"@opengeni/observability": "^0.4.10",
|
|
56
|
+
"@opengeni/runtime": "^0.18.4",
|
|
57
|
+
"@opengeni/storage": "^0.2.60",
|
|
58
58
|
"@temporalio/client": "^1.17.0",
|
|
59
59
|
"better-auth": "^1.6.14",
|
|
60
60
|
"hono": "^4.12.18",
|
package/src/app.ts
CHANGED
|
@@ -18,10 +18,11 @@ import {
|
|
|
18
18
|
} from "@opengeni/contracts";
|
|
19
19
|
import {
|
|
20
20
|
createDocumentServices,
|
|
21
|
+
getDocument,
|
|
21
22
|
indexDocumentNow,
|
|
22
23
|
type DocumentServices,
|
|
23
24
|
} from "@opengeni/documents";
|
|
24
|
-
import { dbSql, getWorkspace } from "@opengeni/db";
|
|
25
|
+
import { dbSql, getWorkspace, rlsContextForWorkspace } from "@opengeni/db";
|
|
25
26
|
import { createObservability } from "@opengeni/observability";
|
|
26
27
|
import { createObjectStorage } from "@opengeni/storage";
|
|
27
28
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
@@ -127,17 +128,38 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
127
128
|
accountId,
|
|
128
129
|
workspaceId,
|
|
129
130
|
documentId,
|
|
131
|
+
authorityKind,
|
|
132
|
+
authorityWorkspaceId,
|
|
133
|
+
authoritySubjectId,
|
|
130
134
|
}: {
|
|
131
135
|
accountId: string;
|
|
132
136
|
workspaceId: string;
|
|
133
137
|
documentId: string;
|
|
138
|
+
authorityKind: import("@opengeni/contracts").DocumentAuthorityKind;
|
|
139
|
+
authorityWorkspaceId: string | null;
|
|
140
|
+
authoritySubjectId: string | null;
|
|
134
141
|
}) => {
|
|
135
142
|
if (!objectStorage) {
|
|
136
143
|
throw new HTTPException(503, {
|
|
137
144
|
message: "object storage is not configured",
|
|
138
145
|
});
|
|
139
146
|
}
|
|
140
|
-
|
|
147
|
+
const context = await rlsContextForWorkspace(deps.db, workspaceId);
|
|
148
|
+
if (context.accountId !== accountId) {
|
|
149
|
+
throw new Error("document account/workspace authority mismatch");
|
|
150
|
+
}
|
|
151
|
+
const claimedDocument = await getDocument(deps.db, workspaceId, documentId, {
|
|
152
|
+
viewerSubjectId: authoritySubjectId,
|
|
153
|
+
});
|
|
154
|
+
if (
|
|
155
|
+
!claimedDocument ||
|
|
156
|
+
claimedDocument.authorityKind !== authorityKind ||
|
|
157
|
+
claimedDocument.authorityWorkspaceId !== authorityWorkspaceId ||
|
|
158
|
+
claimedDocument.authoritySubjectId !== authoritySubjectId
|
|
159
|
+
) {
|
|
160
|
+
throw new Error("document authority changed before indexing");
|
|
161
|
+
}
|
|
162
|
+
const document = await indexDocumentNow(
|
|
141
163
|
deps.db,
|
|
142
164
|
objectStorage,
|
|
143
165
|
workspaceId,
|
|
@@ -153,7 +175,16 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
153
175
|
});
|
|
154
176
|
},
|
|
155
177
|
},
|
|
178
|
+
{ viewerSubjectId: authoritySubjectId },
|
|
156
179
|
);
|
|
180
|
+
if (
|
|
181
|
+
document.authorityKind !== authorityKind ||
|
|
182
|
+
document.authorityWorkspaceId !== authorityWorkspaceId ||
|
|
183
|
+
document.authoritySubjectId !== authoritySubjectId
|
|
184
|
+
) {
|
|
185
|
+
throw new Error("document authority changed before indexing");
|
|
186
|
+
}
|
|
187
|
+
return document;
|
|
157
188
|
},
|
|
158
189
|
};
|
|
159
190
|
// The API process's own agent-loop-free sandbox client — the API-direct
|
package/src/codex-realtime.ts
CHANGED
|
@@ -103,6 +103,10 @@ The initial conversation items are authoritative context from the current sessio
|
|
|
103
103
|
|
|
104
104
|
Live context wrapped in <session_user_message> is an authoritative user message already routed to the current session. A status of queued_for_execution means it is waiting behind existing work; accepted_for_execution means it is next with no existing work ahead; accepted_for_steering means it was given priority as a change of direction, while any prior work may still be yielding. Incorporate it immediately as conversation context, but never delegate it again or treat the wrapper metadata as user-authored text.
|
|
105
105
|
|
|
106
|
+
Live context wrapped in <session_human_input_request> means current work is paused for the user's answer. Preserve the exact question meaning and options. Ask one question at a time when useful. The user may answer in the visible form or answer conversationally. If the user answers conversationally, create exactly one delegation containing the relevant question and the user's answer so the session agent can continue with complete context. If the user changes direction instead, delegate the new direction normally. Do not claim work resumed until session context confirms it.
|
|
107
|
+
|
|
108
|
+
Live context wrapped in <session_human_input_response> is the authoritative outcome of that pending question. An answered or skipped response came through the structured session UI and is already routed; incorporate it, never delegate it again, and acknowledge briefly only if useful. An expired or cancelled response means the question is no longer active.
|
|
109
|
+
|
|
106
110
|
Live session updates may describe work that started before this realtime conversation, work sent directly by the user, or work delegated during an earlier realtime connection. Treat those updates as part of this same session even when they have no current delegation identity.
|
|
107
111
|
|
|
108
112
|
## Backend use
|
package/src/index.ts
CHANGED
|
@@ -231,12 +231,13 @@ export async function createTemporalWorkflowClient(
|
|
|
231
231
|
},
|
|
232
232
|
};
|
|
233
233
|
const documentIndexer: DocumentIndexClient = {
|
|
234
|
-
indexDocument: async (
|
|
234
|
+
indexDocument: async (input) => {
|
|
235
|
+
const { documentId } = input;
|
|
235
236
|
const workflowId = `document-index-${documentId}-${crypto.randomUUID()}`;
|
|
236
237
|
await temporal.workflow.start("documentIndexWorkflow", {
|
|
237
238
|
taskQueue: settings.temporalTaskQueue,
|
|
238
239
|
workflowId,
|
|
239
|
-
args: [
|
|
240
|
+
args: [input],
|
|
240
241
|
});
|
|
241
242
|
},
|
|
242
243
|
};
|