@opengeni/core 0.21.2 → 0.21.10
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/access/index.d.ts +8 -0
- package/dist/application/session-commands.d.ts +7 -2
- package/dist/domain/capabilities.d.ts +7 -0
- package/dist/domain/memory-slack-publication.d.ts +0 -2
- package/dist/domain/scheduled-tasks.d.ts +25 -1
- package/dist/domain/sessions.d.ts +30 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +451 -102
- package/dist/index.js.map +1 -1
- package/dist/managed-session.d.ts +53 -0
- package/dist/sandbox/fleet.d.ts +9 -1
- package/dist/session-authorization.d.ts +9 -0
- package/package.json +10 -10
- package/src/access/index.ts +43 -3
- package/src/application/session-commands.ts +29 -9
- package/src/domain/capabilities.ts +99 -29
- package/src/domain/memory-slack-publication.ts +5 -17
- package/src/domain/resources.ts +10 -20
- package/src/domain/scheduled-tasks.ts +221 -7
- package/src/domain/sessions.ts +127 -37
- package/src/index.ts +1 -0
- package/src/managed-auth-type.ts +4 -3
- package/src/managed-session.ts +36 -0
- package/src/sandbox/fleet.ts +56 -1
- package/src/sandbox/routing.ts +26 -0
- package/src/session-authorization.ts +17 -0
package/dist/index.js
CHANGED
|
@@ -3,6 +3,26 @@ var SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID = "opengeni-session-workflow-wa
|
|
|
3
3
|
var SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE = "sessionWorkflowWakeDispatcherWorkflow";
|
|
4
4
|
var SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS = 1e4;
|
|
5
5
|
|
|
6
|
+
// src/managed-session.ts
|
|
7
|
+
async function getManagedSession(c, auth) {
|
|
8
|
+
const result = await auth.api.getSession({
|
|
9
|
+
headers: c.req.raw.headers,
|
|
10
|
+
returnHeaders: true
|
|
11
|
+
});
|
|
12
|
+
for (const cookie of setCookieHeaders(result.headers)) {
|
|
13
|
+
c.header("set-cookie", cookie, { append: true });
|
|
14
|
+
}
|
|
15
|
+
return result.response;
|
|
16
|
+
}
|
|
17
|
+
function setCookieHeaders(headers) {
|
|
18
|
+
const getSetCookie = headers.getSetCookie;
|
|
19
|
+
if (getSetCookie) {
|
|
20
|
+
return getSetCookie.call(headers);
|
|
21
|
+
}
|
|
22
|
+
const cookie = headers.get("set-cookie");
|
|
23
|
+
return cookie ? [cookie] : [];
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
// src/transcription.ts
|
|
7
27
|
var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
|
|
8
28
|
var TranscriptionServiceError = class extends Error {
|
|
@@ -79,7 +99,7 @@ function filenameForMimeType(mimeType) {
|
|
|
79
99
|
|
|
80
100
|
// src/sandbox/fleet.ts
|
|
81
101
|
import {
|
|
82
|
-
getEnrollment,
|
|
102
|
+
getEnrollment as getEnrollment2,
|
|
83
103
|
getSandbox as getSandbox2,
|
|
84
104
|
listSandboxes,
|
|
85
105
|
readActiveSandbox as readActiveSandbox2,
|
|
@@ -89,6 +109,7 @@ import {
|
|
|
89
109
|
} from "@opengeni/db";
|
|
90
110
|
import {
|
|
91
111
|
NatsControlRpc as NatsControlRpc2,
|
|
112
|
+
NatsOpStreamTransport as NatsOpStreamTransport2,
|
|
92
113
|
selfhostedLiveness,
|
|
93
114
|
SelfhostedSession,
|
|
94
115
|
swapTargetEstablishability
|
|
@@ -100,6 +121,7 @@ import { sandboxArchiveCaptureTimeoutMs } from "@opengeni/config";
|
|
|
100
121
|
import {
|
|
101
122
|
advanceWorkspaceGenerationForDirectRequest,
|
|
102
123
|
advanceWorkspaceGenerationForRetainedProcess,
|
|
124
|
+
getEnrollment,
|
|
103
125
|
getRetainedProcess,
|
|
104
126
|
getSandbox,
|
|
105
127
|
markWarmLeaseInstanceLost,
|
|
@@ -115,6 +137,7 @@ import {
|
|
|
115
137
|
isProviderSandboxGoneDuringRoutedOperation,
|
|
116
138
|
makeActiveBackendResolver,
|
|
117
139
|
NatsControlRpc,
|
|
140
|
+
NatsOpStreamTransport,
|
|
118
141
|
RoutingSandboxSession,
|
|
119
142
|
resolveModalCheckpointProviderBindingForSession
|
|
120
143
|
} from "@opengeni/runtime/sandbox";
|
|
@@ -154,6 +177,18 @@ function controlRpcFactory(bus) {
|
|
|
154
177
|
return bus.getRequestConnection();
|
|
155
178
|
});
|
|
156
179
|
}
|
|
180
|
+
async function resolveSelfhostedOpStream(services, workspaceId, sandbox) {
|
|
181
|
+
if (services.settings.agentOpStreamEnabled !== true || !services.bus?.getOpStreamConnection || !sandbox.enrollmentId) {
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
const enrollment = await getEnrollment(services.db, workspaceId, sandbox.enrollmentId);
|
|
185
|
+
if (enrollment?.opStream !== true) return void 0;
|
|
186
|
+
return {
|
|
187
|
+
transport: new NatsOpStreamTransport(
|
|
188
|
+
async () => services.bus?.getOpStreamConnection?.() ?? null
|
|
189
|
+
)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
157
192
|
function routingEnabled(settings) {
|
|
158
193
|
return settings.sandboxSelfhostedEnabled === true;
|
|
159
194
|
}
|
|
@@ -317,6 +352,7 @@ function wrapChannelABoxWithRouting(services, ids, established) {
|
|
|
317
352
|
} : null;
|
|
318
353
|
},
|
|
319
354
|
controlRpcFactory: controlRpcFactory(bus),
|
|
355
|
+
resolveSelfhostedOpStream: (sandbox) => resolveSelfhostedOpStream(services, ids.workspaceId, sandbox),
|
|
320
356
|
relay: relayConfigFromSettings(settings),
|
|
321
357
|
selfhostedTimeoutMs: settings.sandboxSelfhostedControlTimeoutMs,
|
|
322
358
|
selfhostedExecTimeoutMs: settings.sandboxSelfhostedExecTimeoutMs,
|
|
@@ -463,6 +499,10 @@ async function listFleet(services, ctx) {
|
|
|
463
499
|
const groupRecovering = Boolean(
|
|
464
500
|
groupLease && (groupLease.liveness === "warming" || groupLease.recovery.restore.status === "pending" || groupLease.recovery.restore.status === "restoring" || groupLease.recovery.restore.status === "verifying")
|
|
465
501
|
);
|
|
502
|
+
const groupRecoveryUnavailable = Boolean(
|
|
503
|
+
groupLease && (groupLease.recovery.restore.status === "degraded" || groupLease.recovery.restore.status === "unrecoverable" || groupLease.recovery.workspace.status === "degraded" || groupLease.recovery.workspace.status === "unrecoverable")
|
|
504
|
+
);
|
|
505
|
+
const groupOperationAvailability = groupOnline ? "ready" : groupRecoveryUnavailable ? "unavailable" : groupRecovering ? "recovering" : ctx.sessionBackend === "selfhosted" ? "unavailable" : "wakeable";
|
|
466
506
|
entries.push({
|
|
467
507
|
id: ctx.sessionGroupId,
|
|
468
508
|
kind: ctx.sessionBackend === "selfhosted" ? "selfhosted" : "modal",
|
|
@@ -472,6 +512,7 @@ async function listFleet(services, ctx) {
|
|
|
472
512
|
isSessionGroup: true,
|
|
473
513
|
enrollmentId: null,
|
|
474
514
|
attachable: groupOnline,
|
|
515
|
+
operationAvailability: groupOperationAvailability,
|
|
475
516
|
providerStatus: groupLease?.recovery.provider.status ?? "not_created",
|
|
476
517
|
leaseLiveness: groupLease?.liveness ?? null,
|
|
477
518
|
routeStatus: groupActive ? "attached" : "detached",
|
|
@@ -489,7 +530,10 @@ async function listFleet(services, ctx) {
|
|
|
489
530
|
if (sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
|
|
490
531
|
continue;
|
|
491
532
|
}
|
|
492
|
-
const enrollment = await
|
|
533
|
+
const enrollment = await getEnrollment2(db, ctx.workspaceId, sandbox.enrollmentId);
|
|
534
|
+
if (!enrollment || enrollment.status !== "active") {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
493
537
|
const probe = enrollment ? await probeEnrollment(services, ctx.workspaceId, enrollment) : { liveness: "offline", consented: false, hasDisplay: false };
|
|
494
538
|
entries.push({
|
|
495
539
|
id: sandbox.id,
|
|
@@ -500,6 +544,7 @@ async function listFleet(services, ctx) {
|
|
|
500
544
|
isSessionGroup: false,
|
|
501
545
|
enrollmentId: sandbox.enrollmentId,
|
|
502
546
|
attachable: probe.liveness === "online",
|
|
547
|
+
operationAvailability: probe.liveness === "online" ? "ready" : probe.liveness === "reconnecting" ? "recovering" : "unavailable",
|
|
503
548
|
consented: probe.consented,
|
|
504
549
|
hasDisplay: probe.hasDisplay,
|
|
505
550
|
lastSeenAt: enrollment?.lastSeenAt ?? null,
|
|
@@ -549,7 +594,7 @@ async function resolveTarget(services, ctx, target) {
|
|
|
549
594
|
code: "offline_enrollment"
|
|
550
595
|
};
|
|
551
596
|
}
|
|
552
|
-
const enrollment = await
|
|
597
|
+
const enrollment = await getEnrollment2(services.db, ctx.workspaceId, sandbox.enrollmentId);
|
|
553
598
|
if (!enrollment) {
|
|
554
599
|
return {
|
|
555
600
|
ok: false,
|
|
@@ -648,7 +693,8 @@ async function executeRunOnSelfhostedMachine(machine, target, op) {
|
|
|
648
693
|
controlRpc: machine.controlRpc,
|
|
649
694
|
relay: machine.relay,
|
|
650
695
|
timeoutMs: machine.controlTimeoutMs,
|
|
651
|
-
execTimeoutMs: machine.execTimeoutMs
|
|
696
|
+
execTimeoutMs: machine.execTimeoutMs,
|
|
697
|
+
...machine.opStream !== void 0 ? { opStream: machine.opStream } : {}
|
|
652
698
|
});
|
|
653
699
|
try {
|
|
654
700
|
if (op.kind === "exec") {
|
|
@@ -691,6 +737,8 @@ async function executeRunOnSelfhostedMachine(machine, target, op) {
|
|
|
691
737
|
// so leave `timedOut` absent while still reporting the enforced deadline.
|
|
692
738
|
...op.kind === "exec" ? { deadlineMs: session.effectiveExecDeadlineMs } : {}
|
|
693
739
|
};
|
|
740
|
+
} finally {
|
|
741
|
+
await session.finalizeOpStreamOps().catch(() => void 0);
|
|
694
742
|
}
|
|
695
743
|
}
|
|
696
744
|
async function runOnSandbox(services, ctx, target, op) {
|
|
@@ -712,7 +760,7 @@ async function runOnSandbox(services, ctx, target, op) {
|
|
|
712
760
|
reason: `run_on routes one-off ops to enrolled selfhosted machines; ${sandbox.kind} targets are reached via the active sandbox (swap to it first)`
|
|
713
761
|
};
|
|
714
762
|
}
|
|
715
|
-
const enrollment = await
|
|
763
|
+
const enrollment = await getEnrollment2(services.db, ctx.workspaceId, sandbox.enrollmentId);
|
|
716
764
|
if (!enrollment || enrollment.status !== "active") {
|
|
717
765
|
return {
|
|
718
766
|
target,
|
|
@@ -729,7 +777,14 @@ async function runOnSandbox(services, ctx, target, op) {
|
|
|
729
777
|
controlRpc: controlRpc(services.bus),
|
|
730
778
|
relay: relayConfigFromSettings(services.settings),
|
|
731
779
|
controlTimeoutMs: services.settings.sandboxSelfhostedControlTimeoutMs,
|
|
732
|
-
execTimeoutMs: services.settings.sandboxSelfhostedExecTimeoutMs
|
|
780
|
+
execTimeoutMs: services.settings.sandboxSelfhostedExecTimeoutMs,
|
|
781
|
+
...services.settings.agentOpStreamEnabled === true && enrollment.opStream === true && services.bus?.getOpStreamConnection ? {
|
|
782
|
+
opStream: {
|
|
783
|
+
transport: new NatsOpStreamTransport2(
|
|
784
|
+
async () => services.bus?.getOpStreamConnection?.() ?? null
|
|
785
|
+
)
|
|
786
|
+
}
|
|
787
|
+
} : {}
|
|
733
788
|
},
|
|
734
789
|
target,
|
|
735
790
|
op
|
|
@@ -741,7 +796,7 @@ async function provisionSandbox(services, ctx, input) {
|
|
|
741
796
|
const base = (services.settings.publicBaseUrl ?? "https://get.opengeni.ai").replace(/\/+$/, "");
|
|
742
797
|
return {
|
|
743
798
|
kind: "selfhosted",
|
|
744
|
-
instructions: "Share these instructions with a human operator. They install the OpenGeni agent on the machine, run `opengeni-agent
|
|
799
|
+
instructions: "Share these instructions with a human operator. They install the OpenGeni agent on the machine, run `opengeni-agent connect`, complete the device-flow at the verification URL (the loud whole-machine + screen-control consent), and the machine then appears here as an attachable selfhosted sandbox. Existing connections to other OpenGeni workspaces or deployments are preserved.",
|
|
745
800
|
// Install from THIS control plane's origin (not a hardcoded public CDN): the
|
|
746
801
|
// served install script is rewritten to pull the per-SHA agent baked into
|
|
747
802
|
// this exact deployment (see apps/api/src/routes/install.ts), so a deployed
|
|
@@ -853,10 +908,43 @@ function requirePermission(grant, permission) {
|
|
|
853
908
|
});
|
|
854
909
|
}
|
|
855
910
|
}
|
|
911
|
+
function requireLiteralPermission(grant, permission) {
|
|
912
|
+
if (!hasLiteralPermission(grant.permissions, permission)) {
|
|
913
|
+
throw new HTTPException2(403, {
|
|
914
|
+
message: `missing literal permission: ${permission}`
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
function hasLiteralPermission(permissions, permission) {
|
|
919
|
+
return permissions.includes(permission);
|
|
920
|
+
}
|
|
856
921
|
function hasPermission(permissions, permission) {
|
|
922
|
+
if (permission === "secrets:read") {
|
|
923
|
+
return permissions.includes("secrets:read");
|
|
924
|
+
}
|
|
857
925
|
const aliases = {
|
|
858
926
|
"variable-sets:use": ["environments:use"],
|
|
859
|
-
"variable-sets:manage": ["environments:manage"]
|
|
927
|
+
"variable-sets:manage": ["environments:manage"],
|
|
928
|
+
"variable-sets:list": [
|
|
929
|
+
"variable-sets:use",
|
|
930
|
+
"variable-sets:manage",
|
|
931
|
+
"environments:use",
|
|
932
|
+
"environments:manage"
|
|
933
|
+
],
|
|
934
|
+
"variable-sets:read": [
|
|
935
|
+
"variable-sets:use",
|
|
936
|
+
"variable-sets:manage",
|
|
937
|
+
"environments:use",
|
|
938
|
+
"environments:manage"
|
|
939
|
+
],
|
|
940
|
+
"variable-sets:write": ["variable-sets:manage", "environments:manage"],
|
|
941
|
+
"secrets:list": [
|
|
942
|
+
"variable-sets:use",
|
|
943
|
+
"variable-sets:manage",
|
|
944
|
+
"environments:use",
|
|
945
|
+
"environments:manage"
|
|
946
|
+
],
|
|
947
|
+
"secrets:write": ["variable-sets:manage", "environments:manage"]
|
|
860
948
|
};
|
|
861
949
|
return permissions.includes(permission) || (aliases[permission]?.some((alias) => permissions.includes(alias)) ?? false) || permissions.includes("workspace:admin");
|
|
862
950
|
}
|
|
@@ -912,9 +1000,7 @@ async function resolveAccessContext(c, deps) {
|
|
|
912
1000
|
}
|
|
913
1001
|
}
|
|
914
1002
|
if (deps.managedAuth) {
|
|
915
|
-
const session = await deps.managedAuth
|
|
916
|
-
headers: c.req.raw.headers
|
|
917
|
-
});
|
|
1003
|
+
const session = await getManagedSession(c, deps.managedAuth);
|
|
918
1004
|
if (session?.user) {
|
|
919
1005
|
return await ensureManagedAccessForUser(deps.db, {
|
|
920
1006
|
userId: session.user.id,
|
|
@@ -1054,6 +1140,13 @@ var SessionAuthorizationUnavailableError = class extends Error {
|
|
|
1054
1140
|
this.name = "SessionAuthorizationUnavailableError";
|
|
1055
1141
|
}
|
|
1056
1142
|
};
|
|
1143
|
+
async function requireLiveAgentAttemptAuthorization(db, grant, callerSessionId) {
|
|
1144
|
+
const actor = await resolveSessionAuthorizationActor(db, grant);
|
|
1145
|
+
if (actor.kind !== "agent_attempt" || actor.callerSessionId !== callerSessionId) {
|
|
1146
|
+
throw new SessionAuthorizationDeniedError("caller_stale");
|
|
1147
|
+
}
|
|
1148
|
+
return actor;
|
|
1149
|
+
}
|
|
1057
1150
|
async function requireSessionAuthorization(deps, grant, input) {
|
|
1058
1151
|
const port = deps.sessionAuthorization;
|
|
1059
1152
|
const slackAccess = await getSlackInteractionSessionAccessForSession(deps.db, {
|
|
@@ -1749,7 +1842,8 @@ async function buildCapabilityCatalog(input) {
|
|
|
1749
1842
|
workspacePacks,
|
|
1750
1843
|
socialConnections,
|
|
1751
1844
|
bundledSkills,
|
|
1752
|
-
curatedLibrarySkills
|
|
1845
|
+
curatedLibrarySkills,
|
|
1846
|
+
codexAppsCredentialId
|
|
1753
1847
|
] = await Promise.all([
|
|
1754
1848
|
listCapabilityCatalogItems(input.db, input.workspaceId),
|
|
1755
1849
|
listCapabilityInstallations(input.db, input.workspaceId),
|
|
@@ -1757,7 +1851,8 @@ async function buildCapabilityCatalog(input) {
|
|
|
1757
1851
|
listWorkspaceCapabilityPacks(input.db, input.workspaceId),
|
|
1758
1852
|
listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
|
|
1759
1853
|
discoverBundledSkills(),
|
|
1760
|
-
discoverCuratedSkillLibraryItems()
|
|
1854
|
+
discoverCuratedSkillLibraryItems(),
|
|
1855
|
+
input.settings.codexConnectedAppsEnabled ? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId) : Promise.resolve(null)
|
|
1761
1856
|
]);
|
|
1762
1857
|
const capabilityInstallationById = new Map(
|
|
1763
1858
|
capabilityInstallations.map((installation) => [installation.capabilityId, installation])
|
|
@@ -1775,7 +1870,14 @@ async function buildCapabilityCatalog(input) {
|
|
|
1775
1870
|
...bundledSkills,
|
|
1776
1871
|
...curatedLibrarySkills
|
|
1777
1872
|
];
|
|
1778
|
-
const
|
|
1873
|
+
const codexApps = input.settings.codexConnectedAppsEnabled ? codexAppsCatalogItem(codexAppsCredentialId !== null) : null;
|
|
1874
|
+
const items = dedupeCatalogItems([
|
|
1875
|
+
...builtIns,
|
|
1876
|
+
...persistedItems.filter((item) => !isReservedCodexAppsCatalogItem(item)),
|
|
1877
|
+
// Keep the reserved, server-derived item authoritative over any stale
|
|
1878
|
+
// legacy catalog row with the same id.
|
|
1879
|
+
...codexApps ? [codexApps] : []
|
|
1880
|
+
]).map(
|
|
1779
1881
|
(item) => applyCapabilityEnablement(item, capabilityInstallationById.get(item.id), activePackIds)
|
|
1780
1882
|
).sort(compareCatalogItems);
|
|
1781
1883
|
return {
|
|
@@ -1795,7 +1897,7 @@ async function createCatalogItem(input) {
|
|
|
1795
1897
|
message: "skill ids are managed by the OpenGeni skill library or runtime adapters"
|
|
1796
1898
|
});
|
|
1797
1899
|
}
|
|
1798
|
-
if (input.payload.kind === "mcp" && typeof input.payload.metadata.mcpServerId === "string" && input.payload.metadata.mcpServerId.trim() === CODEX_APPS_MCP_SERVER_ID) {
|
|
1900
|
+
if (input.payload.kind === "mcp" && (id === `mcp:${CODEX_APPS_MCP_SERVER_ID}` || typeof input.payload.metadata.mcpServerId === "string" && input.payload.metadata.mcpServerId.trim() === CODEX_APPS_MCP_SERVER_ID)) {
|
|
1799
1901
|
throw new HTTPException6(422, {
|
|
1800
1902
|
message: `${CODEX_APPS_MCP_SERVER_ID} is reserved for the canonical Codex Apps service`
|
|
1801
1903
|
});
|
|
@@ -2435,7 +2537,7 @@ function packCatalogItem(pack, source) {
|
|
|
2435
2537
|
});
|
|
2436
2538
|
}
|
|
2437
2539
|
function configuredMcpCatalogItems(settings) {
|
|
2438
|
-
return settings.mcpServers.map(
|
|
2540
|
+
return settings.mcpServers.filter((server) => server.id !== CODEX_APPS_MCP_SERVER_ID).map(
|
|
2439
2541
|
(server) => CapabilityCatalogItem.parse({
|
|
2440
2542
|
id: `mcp:${server.id}`,
|
|
2441
2543
|
kind: "mcp",
|
|
@@ -2460,6 +2562,38 @@ function configuredMcpCatalogItems(settings) {
|
|
|
2460
2562
|
})
|
|
2461
2563
|
);
|
|
2462
2564
|
}
|
|
2565
|
+
function isReservedCodexAppsCatalogItem(item) {
|
|
2566
|
+
return item.id === `mcp:${CODEX_APPS_MCP_SERVER_ID}` || item.kind === "mcp" && (item.runtime.mcpServerId === CODEX_APPS_MCP_SERVER_ID || item.metadata.mcpServerId === CODEX_APPS_MCP_SERVER_ID);
|
|
2567
|
+
}
|
|
2568
|
+
function codexAppsCatalogItem(available) {
|
|
2569
|
+
return CapabilityCatalogItem.parse({
|
|
2570
|
+
id: `mcp:${CODEX_APPS_MCP_SERVER_ID}`,
|
|
2571
|
+
kind: "mcp",
|
|
2572
|
+
source: "built_in",
|
|
2573
|
+
name: "Codex Apps",
|
|
2574
|
+
description: "Use the ChatGPT Apps designated for this workspace. Sessions include this surface by default when it is authorized; explicit policies can opt out.",
|
|
2575
|
+
category: "productivity",
|
|
2576
|
+
tags: ["mcp", "codex", "connected-apps"],
|
|
2577
|
+
providerDomain: "chatgpt.com",
|
|
2578
|
+
surfaceType: "codex_apps",
|
|
2579
|
+
transport: "streamable-http",
|
|
2580
|
+
mcpUrl: CODEX_APPS_MCP_URL,
|
|
2581
|
+
authKind: "none",
|
|
2582
|
+
tools: [{ kind: "mcp", id: CODEX_APPS_MCP_SERVER_ID }],
|
|
2583
|
+
runtime: {
|
|
2584
|
+
available,
|
|
2585
|
+
...available ? { mcpServerId: CODEX_APPS_MCP_SERVER_ID } : {},
|
|
2586
|
+
transport: "streamable-http",
|
|
2587
|
+
notes: available ? "Available through the active workspace Apps designation." : "Unavailable until an active Codex Apps credential is designated for this workspace."
|
|
2588
|
+
},
|
|
2589
|
+
enabled: available,
|
|
2590
|
+
enabledReason: available ? "designated Apps credential" : "no active Apps designation",
|
|
2591
|
+
metadata: {
|
|
2592
|
+
mcpServerId: CODEX_APPS_MCP_SERVER_ID,
|
|
2593
|
+
authorization: "workspace_designation"
|
|
2594
|
+
}
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2463
2597
|
function platformApiCatalogItems(socialConnections) {
|
|
2464
2598
|
const xConnection = preferredSocialConnection(socialConnections, "x");
|
|
2465
2599
|
const xEnabled = xConnection?.status === "connected" || xConnection?.status === "needs_reauth";
|
|
@@ -2656,6 +2790,9 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
|
|
|
2656
2790
|
if (item.surfaceType === "first_party_social") {
|
|
2657
2791
|
return { ...item, connectionRef: null };
|
|
2658
2792
|
}
|
|
2793
|
+
if (item.surfaceType === "codex_apps") {
|
|
2794
|
+
return { ...item, connectionRef: null };
|
|
2795
|
+
}
|
|
2659
2796
|
if (item.source === "built_in" || item.source === "configured") {
|
|
2660
2797
|
return {
|
|
2661
2798
|
...item,
|
|
@@ -3487,6 +3624,7 @@ import {
|
|
|
3487
3624
|
mergeResourceRefs as mergeContractResourceRefs,
|
|
3488
3625
|
mergeToolRefs,
|
|
3489
3626
|
normalizeRepositorySubpath,
|
|
3627
|
+
normalizeRepositoryTransportUri,
|
|
3490
3628
|
normalizeResourceMountPath,
|
|
3491
3629
|
resourceIdentityKey,
|
|
3492
3630
|
resourceMountPath,
|
|
@@ -3558,21 +3696,18 @@ function normalizeResources(resources) {
|
|
|
3558
3696
|
mountPath
|
|
3559
3697
|
};
|
|
3560
3698
|
} else {
|
|
3561
|
-
const
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3699
|
+
const credentialProvider = gitCredentialProviderForRepository(resource);
|
|
3700
|
+
let normalizedUri;
|
|
3701
|
+
try {
|
|
3702
|
+
normalizedUri = normalizeRepositoryTransportUri(resource.uri);
|
|
3703
|
+
} catch (error) {
|
|
3704
|
+
throw new HTTPException8(422, {
|
|
3705
|
+
message: error instanceof Error ? error.message : "invalid repository URI"
|
|
3706
|
+
});
|
|
3569
3707
|
}
|
|
3570
|
-
const repo = parts.join("/");
|
|
3571
|
-
const normalizedUri = `https://${url.host.toLowerCase()}/${repo}.git`;
|
|
3572
3708
|
const mountPath = normalizeMountPath(
|
|
3573
|
-
resource.mountPath ?? defaultRepositoryMountPath(normalizedUri)
|
|
3709
|
+
resource.mountPath ?? defaultRepositoryMountPath(normalizedUri, credentialProvider)
|
|
3574
3710
|
);
|
|
3575
|
-
const credentialProvider = gitCredentialProviderForRepository(resource);
|
|
3576
3711
|
const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
|
|
3577
3712
|
if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
|
|
3578
3713
|
throw new HTTPException8(422, {
|
|
@@ -3732,13 +3867,6 @@ function normalizeMountPath(path) {
|
|
|
3732
3867
|
throw new HTTPException8(422, { message: `invalid resource mount path: ${path}` });
|
|
3733
3868
|
}
|
|
3734
3869
|
}
|
|
3735
|
-
function parseResourceUrl(uri) {
|
|
3736
|
-
try {
|
|
3737
|
-
return new URL(uri);
|
|
3738
|
-
} catch {
|
|
3739
|
-
throw new HTTPException8(422, { message: "repository resources must use valid URLs" });
|
|
3740
|
-
}
|
|
3741
|
-
}
|
|
3742
3870
|
function positiveInteger(value) {
|
|
3743
3871
|
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
3744
3872
|
return value;
|
|
@@ -3882,6 +4010,7 @@ import {
|
|
|
3882
4010
|
getRig as getRig3,
|
|
3883
4011
|
getScheduledTask,
|
|
3884
4012
|
getScheduledTaskPersonalConnectionDelegations,
|
|
4013
|
+
getSession as getSession3,
|
|
3885
4014
|
requireWorkspace as requireWorkspace2,
|
|
3886
4015
|
updateScheduledTask
|
|
3887
4016
|
} from "@opengeni/db";
|
|
@@ -3916,7 +4045,7 @@ import {
|
|
|
3916
4045
|
createSessionWithIdempotencyKeyResult,
|
|
3917
4046
|
encryptVariableSetValue as encryptVariableSetValue2,
|
|
3918
4047
|
getAnySessionInGroup,
|
|
3919
|
-
getEnrollment as
|
|
4048
|
+
getEnrollment as getEnrollment3,
|
|
3920
4049
|
getRig as getRig2,
|
|
3921
4050
|
getWorkspaceDefaultRigId,
|
|
3922
4051
|
listDistinctVariableSetIdsInGroup,
|
|
@@ -4305,7 +4434,7 @@ function validateSessionMcpCredentialUpdates(input) {
|
|
|
4305
4434
|
});
|
|
4306
4435
|
return encryptedUpdates;
|
|
4307
4436
|
}
|
|
4308
|
-
async function
|
|
4437
|
+
async function createAndStartSessionWithOutcome(input) {
|
|
4309
4438
|
const sessionMetadata = {
|
|
4310
4439
|
...input.metadata,
|
|
4311
4440
|
model: input.model,
|
|
@@ -4351,12 +4480,24 @@ async function createAndStartSession(input) {
|
|
|
4351
4480
|
}
|
|
4352
4481
|
const { session: keyed, created } = keyedResult;
|
|
4353
4482
|
if (!created) {
|
|
4354
|
-
|
|
4483
|
+
const finished3 = await finishStartSession(
|
|
4355
4484
|
keyed.temporalWorkflowId ? { ...input, seedTargetSandbox: null } : input,
|
|
4356
4485
|
keyed
|
|
4357
4486
|
);
|
|
4487
|
+
return {
|
|
4488
|
+
session: finished3.session,
|
|
4489
|
+
outcome: finished3.changed ? "repaired" : "replayed",
|
|
4490
|
+
replay: !finished3.changed,
|
|
4491
|
+
changed: finished3.changed
|
|
4492
|
+
};
|
|
4358
4493
|
}
|
|
4359
|
-
|
|
4494
|
+
const finished2 = await finishStartSession(input, keyed);
|
|
4495
|
+
return {
|
|
4496
|
+
session: finished2.session,
|
|
4497
|
+
outcome: "created",
|
|
4498
|
+
replay: false,
|
|
4499
|
+
changed: true
|
|
4500
|
+
};
|
|
4360
4501
|
}
|
|
4361
4502
|
let session;
|
|
4362
4503
|
try {
|
|
@@ -4398,7 +4539,16 @@ async function createAndStartSession(input) {
|
|
|
4398
4539
|
}
|
|
4399
4540
|
throw error;
|
|
4400
4541
|
}
|
|
4401
|
-
|
|
4542
|
+
const finished = await finishStartSession(input, session);
|
|
4543
|
+
return {
|
|
4544
|
+
session: finished.session,
|
|
4545
|
+
outcome: "created",
|
|
4546
|
+
replay: false,
|
|
4547
|
+
changed: true
|
|
4548
|
+
};
|
|
4549
|
+
}
|
|
4550
|
+
async function createAndStartSession(input) {
|
|
4551
|
+
return (await createAndStartSessionWithOutcome(input)).session;
|
|
4402
4552
|
}
|
|
4403
4553
|
async function finishStartSession(input, session) {
|
|
4404
4554
|
if (input.seedTargetSandbox) {
|
|
@@ -4460,7 +4610,10 @@ async function finishStartSession(input, session) {
|
|
|
4460
4610
|
}
|
|
4461
4611
|
const persisted = await requireSession2(input.db, session.workspaceId, session.id);
|
|
4462
4612
|
const initialTurnId = started.turn?.id ?? (await listSessionTurns(input.db, session.workspaceId, session.id, 1))[0]?.id ?? null;
|
|
4463
|
-
return {
|
|
4613
|
+
return {
|
|
4614
|
+
session: { ...persisted, initialTurnId },
|
|
4615
|
+
changed: started.changed
|
|
4616
|
+
};
|
|
4464
4617
|
}
|
|
4465
4618
|
function workflowIdForSession(sessionId) {
|
|
4466
4619
|
return `session-${sessionId}`;
|
|
@@ -4645,15 +4798,16 @@ async function postUserMessageTurn(input) {
|
|
|
4645
4798
|
wakeRevision: result.wakeRevision,
|
|
4646
4799
|
...(input.delivery ?? "send") === "steer" || result.interruptionCount > 0 ? { interruptionRequested: true } : {}
|
|
4647
4800
|
});
|
|
4648
|
-
} catch
|
|
4649
|
-
console.warn(
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4801
|
+
} catch {
|
|
4802
|
+
console.warn("[sessions] workflow wake failed; durable outbox will retry", {
|
|
4803
|
+
errorClass: "WorkflowWakeOperationError",
|
|
4804
|
+
errorCode: "session_workflow_wake_failed",
|
|
4805
|
+
origin: "core"
|
|
4806
|
+
});
|
|
4653
4807
|
}
|
|
4654
|
-
return { accepted, turn };
|
|
4808
|
+
return { accepted, turn, replay: result.replay };
|
|
4655
4809
|
}
|
|
4656
|
-
async function
|
|
4810
|
+
async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload) {
|
|
4657
4811
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
4658
4812
|
const payload = CreateSessionRequest.parse(rawPayload);
|
|
4659
4813
|
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
@@ -4924,7 +5078,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
4924
5078
|
if (targetSandbox?.kind === "selfhosted") {
|
|
4925
5079
|
machineHomeBackend = "selfhosted";
|
|
4926
5080
|
if (targetSandbox.enrollmentId) {
|
|
4927
|
-
const enrollment = await
|
|
5081
|
+
const enrollment = await getEnrollment3(db, workspaceId, targetSandbox.enrollmentId);
|
|
4928
5082
|
if (enrollment && (enrollment.os === "macos" || enrollment.os === "windows" || enrollment.os === "linux")) {
|
|
4929
5083
|
machineHomeOs = enrollment.os;
|
|
4930
5084
|
}
|
|
@@ -4941,9 +5095,9 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
4941
5095
|
});
|
|
4942
5096
|
}
|
|
4943
5097
|
const creationInitiator = creationInitiatorForGrant(grant);
|
|
4944
|
-
let
|
|
5098
|
+
let createOutcome;
|
|
4945
5099
|
try {
|
|
4946
|
-
|
|
5100
|
+
createOutcome = await createAndStartSessionWithOutcome({
|
|
4947
5101
|
...payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {},
|
|
4948
5102
|
db,
|
|
4949
5103
|
bus,
|
|
@@ -5018,26 +5172,45 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
5018
5172
|
}
|
|
5019
5173
|
throw error;
|
|
5020
5174
|
}
|
|
5175
|
+
let usageRecording = "recorded";
|
|
5021
5176
|
if (payload.startMode !== "realtime") {
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5177
|
+
try {
|
|
5178
|
+
await recordWorkspaceUsage(deps, {
|
|
5179
|
+
accountId: grant.accountId,
|
|
5180
|
+
workspaceId,
|
|
5181
|
+
subjectId: grant.subjectId,
|
|
5182
|
+
eventType: "agent_run.created",
|
|
5183
|
+
quantity: 1,
|
|
5184
|
+
unit: "run",
|
|
5185
|
+
sourceResourceType: "session",
|
|
5186
|
+
sourceResourceId: createOutcome.session.id,
|
|
5187
|
+
sessionId: createOutcome.session.id,
|
|
5188
|
+
initiator: createOutcome.session.createdBy,
|
|
5189
|
+
initiatorContext: createOutcome.session.createdByContext,
|
|
5190
|
+
origin: creationInitiator.actor ? "system" : "user",
|
|
5191
|
+
idempotencyKey: `agent_run.created:${workspaceId}:${createOutcome.session.id}`
|
|
5192
|
+
});
|
|
5193
|
+
} catch (error) {
|
|
5194
|
+
usageRecording = "failed";
|
|
5195
|
+
reportSessionUsageRecordingFailure(error);
|
|
5196
|
+
}
|
|
5037
5197
|
}
|
|
5038
|
-
return
|
|
5198
|
+
return { ...createOutcome, usageRecording };
|
|
5039
5199
|
}
|
|
5040
|
-
|
|
5200
|
+
function reportSessionUsageRecordingFailure(_error) {
|
|
5201
|
+
console.warn(
|
|
5202
|
+
"[sessions] usage recording failed after committed session create; returning committed outcome",
|
|
5203
|
+
{
|
|
5204
|
+
errorClass: "UsageRecordingError",
|
|
5205
|
+
errorCode: "session_create_usage_recording_failed",
|
|
5206
|
+
origin: "core"
|
|
5207
|
+
}
|
|
5208
|
+
);
|
|
5209
|
+
}
|
|
5210
|
+
async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
5211
|
+
return (await createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload)).session;
|
|
5212
|
+
}
|
|
5213
|
+
async function acceptSessionUserMessageWithOutcome(deps, grant, workspaceId, sessionId, input) {
|
|
5041
5214
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
5042
5215
|
await requireSessionAuthorization(deps, grant, {
|
|
5043
5216
|
sessionId,
|
|
@@ -5105,7 +5278,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
|
|
|
5105
5278
|
source: personalConnectionDelegationSourceForGrant(grant)
|
|
5106
5279
|
});
|
|
5107
5280
|
const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
|
|
5108
|
-
const { accepted, turn } = await postUserMessageTurn({
|
|
5281
|
+
const { accepted, turn, replay } = await postUserMessageTurn({
|
|
5109
5282
|
db,
|
|
5110
5283
|
bus,
|
|
5111
5284
|
workflowClient,
|
|
@@ -5155,6 +5328,16 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
|
|
|
5155
5328
|
origin: turn.source,
|
|
5156
5329
|
idempotencyKey: `agent_run.created:${workspaceId}:${turn.id}`
|
|
5157
5330
|
});
|
|
5331
|
+
return { accepted, turn, replay };
|
|
5332
|
+
}
|
|
5333
|
+
async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, input) {
|
|
5334
|
+
const { accepted, turn } = await acceptSessionUserMessageWithOutcome(
|
|
5335
|
+
deps,
|
|
5336
|
+
grant,
|
|
5337
|
+
workspaceId,
|
|
5338
|
+
sessionId,
|
|
5339
|
+
input
|
|
5340
|
+
);
|
|
5158
5341
|
return { accepted, turn };
|
|
5159
5342
|
}
|
|
5160
5343
|
async function updateSessionTitle(deps, grant, sessionId, title, source) {
|
|
@@ -5452,6 +5635,17 @@ async function createValidatedScheduledTask(input) {
|
|
|
5452
5635
|
});
|
|
5453
5636
|
const id = crypto.randomUUID();
|
|
5454
5637
|
validateScheduledTaskSchedule(input.payload.schedule);
|
|
5638
|
+
const target = await validateScheduledTaskTarget({
|
|
5639
|
+
db: input.db,
|
|
5640
|
+
sessionAuthorization: input.sessionAuthorization,
|
|
5641
|
+
authorizationSurface: input.authorizationSurface,
|
|
5642
|
+
grant: input.grant,
|
|
5643
|
+
targetSessionId: input.payload.targetSessionId,
|
|
5644
|
+
runMode: input.payload.runMode,
|
|
5645
|
+
variableSetId: input.payload.variableSetId,
|
|
5646
|
+
rigId: input.payload.rigId,
|
|
5647
|
+
agentConfig
|
|
5648
|
+
});
|
|
5455
5649
|
if (input.payload.variableSetId) {
|
|
5456
5650
|
await validateVariableSetAttachment(
|
|
5457
5651
|
{ settings: input.settings, db: input.db },
|
|
@@ -5492,11 +5686,97 @@ async function createValidatedScheduledTask(input) {
|
|
|
5492
5686
|
...creationInitiator.context ? { createdByContext: creationInitiator.context } : {},
|
|
5493
5687
|
createdByActor: creationInitiator.actor ?? null,
|
|
5494
5688
|
personalConnectionDelegations,
|
|
5689
|
+
targetSessionId: target?.id ?? null,
|
|
5495
5690
|
variableSetId: input.payload.variableSetId ?? null,
|
|
5496
5691
|
rigId: input.payload.rigId ?? null,
|
|
5497
5692
|
metadata: input.payload.metadata
|
|
5498
5693
|
});
|
|
5499
5694
|
}
|
|
5695
|
+
async function validateScheduledTaskTarget(input) {
|
|
5696
|
+
if (input.runMode !== "existing_session") {
|
|
5697
|
+
if (input.targetSessionId) {
|
|
5698
|
+
throw new HTTPException11(422, {
|
|
5699
|
+
message: "targetSessionId requires runMode=existing_session"
|
|
5700
|
+
});
|
|
5701
|
+
}
|
|
5702
|
+
return null;
|
|
5703
|
+
}
|
|
5704
|
+
if (!input.targetSessionId) {
|
|
5705
|
+
throw new HTTPException11(input.missingTargetStatus ?? 422, {
|
|
5706
|
+
message: input.missingTargetStatus === 404 ? "target session not found" : "targetSessionId is required when runMode=existing_session"
|
|
5707
|
+
});
|
|
5708
|
+
}
|
|
5709
|
+
requirePermission(input.grant, "sessions:control");
|
|
5710
|
+
if (input.agentConfig.goal) {
|
|
5711
|
+
throw new HTTPException11(422, {
|
|
5712
|
+
message: "agentConfig.goal cannot be used with an existing-session target"
|
|
5713
|
+
});
|
|
5714
|
+
}
|
|
5715
|
+
try {
|
|
5716
|
+
await requireSessionAuthorization(
|
|
5717
|
+
{
|
|
5718
|
+
db: input.db,
|
|
5719
|
+
...input.sessionAuthorization !== void 0 ? { sessionAuthorization: input.sessionAuthorization } : {}
|
|
5720
|
+
},
|
|
5721
|
+
input.grant,
|
|
5722
|
+
{
|
|
5723
|
+
sessionId: input.targetSessionId,
|
|
5724
|
+
operation: "session.control",
|
|
5725
|
+
surface: input.authorizationSurface ?? "http"
|
|
5726
|
+
}
|
|
5727
|
+
);
|
|
5728
|
+
} catch (error) {
|
|
5729
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
5730
|
+
throw new HTTPException11(404, { message: "target session not found" });
|
|
5731
|
+
}
|
|
5732
|
+
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
5733
|
+
throw new HTTPException11(503, { message: "session authorization is unavailable" });
|
|
5734
|
+
}
|
|
5735
|
+
throw error;
|
|
5736
|
+
}
|
|
5737
|
+
const session = await getSession3(input.db, input.grant.workspaceId, input.targetSessionId);
|
|
5738
|
+
if (!session || session.accountId !== input.grant.accountId) {
|
|
5739
|
+
throw new HTTPException11(404, { message: "target session not found" });
|
|
5740
|
+
}
|
|
5741
|
+
if (session.status === "cancelled") {
|
|
5742
|
+
throw new HTTPException11(409, {
|
|
5743
|
+
message: "target session is cancelled; choose a revivable session"
|
|
5744
|
+
});
|
|
5745
|
+
}
|
|
5746
|
+
if ((session.variableSetId ?? null) !== (input.variableSetId ?? null)) {
|
|
5747
|
+
throw new HTTPException11(422, {
|
|
5748
|
+
message: "target session variableSet attachment does not match the scheduled task"
|
|
5749
|
+
});
|
|
5750
|
+
}
|
|
5751
|
+
if (input.rigId && input.rigId !== session.rigId) {
|
|
5752
|
+
throw new HTTPException11(422, {
|
|
5753
|
+
message: "target session rig does not match the scheduled task"
|
|
5754
|
+
});
|
|
5755
|
+
}
|
|
5756
|
+
if (input.agentConfig.sandboxBackend !== void 0 && input.agentConfig.sandboxBackend !== session.sandboxBackend) {
|
|
5757
|
+
throw new HTTPException11(422, {
|
|
5758
|
+
message: "target session sandbox backend does not match the scheduled task"
|
|
5759
|
+
});
|
|
5760
|
+
}
|
|
5761
|
+
if (scheduledSlackBotConnectionId(session.metadata) !== (input.agentConfig.slackBotConnectionId ?? null)) {
|
|
5762
|
+
throw new HTTPException11(422, {
|
|
5763
|
+
message: "target session OpenGeni Slack bot binding does not match the scheduled task"
|
|
5764
|
+
});
|
|
5765
|
+
}
|
|
5766
|
+
return session;
|
|
5767
|
+
}
|
|
5768
|
+
function scheduledTaskForGrant(task, grant) {
|
|
5769
|
+
if (hasPermission(grant.permissions, "sessions:control") || task.targetSessionId === null) {
|
|
5770
|
+
return task;
|
|
5771
|
+
}
|
|
5772
|
+
return { ...task, targetSessionId: null };
|
|
5773
|
+
}
|
|
5774
|
+
function scheduledTaskRunForGrant(run, grant) {
|
|
5775
|
+
if (hasPermission(grant.permissions, "sessions:control") || run.sessionId === null) {
|
|
5776
|
+
return run;
|
|
5777
|
+
}
|
|
5778
|
+
return { ...run, sessionId: null };
|
|
5779
|
+
}
|
|
5500
5780
|
async function requireScheduledTaskRig(db, workspaceId, rigId) {
|
|
5501
5781
|
const rig = await getRig3(db, workspaceId, rigId);
|
|
5502
5782
|
if (!rig) {
|
|
@@ -5505,6 +5785,14 @@ async function requireScheduledTaskRig(db, workspaceId, rigId) {
|
|
|
5505
5785
|
}
|
|
5506
5786
|
async function validatedScheduledTaskUpdate(input) {
|
|
5507
5787
|
const update = {};
|
|
5788
|
+
const existingTarget = input.existing.targetSessionId;
|
|
5789
|
+
const nextRunMode = input.payload.runMode ?? input.existing.runMode;
|
|
5790
|
+
const nextTargetSessionId = input.payload.targetSessionId !== void 0 ? input.payload.targetSessionId : nextRunMode === "existing_session" ? existingTarget : null;
|
|
5791
|
+
if (input.existing.runMode === "reusable_session" && input.existing.reusableSessionId && nextRunMode === "existing_session") {
|
|
5792
|
+
throw new HTTPException11(409, {
|
|
5793
|
+
message: "cannot target an existing session after this task created its reusable session; create a new task"
|
|
5794
|
+
});
|
|
5795
|
+
}
|
|
5508
5796
|
if (input.payload.name !== void 0) {
|
|
5509
5797
|
update.name = trimmedScheduledTaskName(input.payload.name);
|
|
5510
5798
|
}
|
|
@@ -5598,6 +5886,33 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
5598
5886
|
}
|
|
5599
5887
|
update.personalConnectionDelegations = personalConnectionDelegations;
|
|
5600
5888
|
}
|
|
5889
|
+
if (existingTarget && (nextRunMode !== "existing_session" || nextTargetSessionId !== existingTarget)) {
|
|
5890
|
+
await validateScheduledTaskTarget({
|
|
5891
|
+
db: input.db,
|
|
5892
|
+
sessionAuthorization: input.sessionAuthorization,
|
|
5893
|
+
authorizationSurface: input.authorizationSurface,
|
|
5894
|
+
grant: input.grant,
|
|
5895
|
+
targetSessionId: existingTarget,
|
|
5896
|
+
runMode: "existing_session",
|
|
5897
|
+
variableSetId: input.existing.variableSetId,
|
|
5898
|
+
rigId: input.existing.rigId,
|
|
5899
|
+
agentConfig: input.existing.agentConfig
|
|
5900
|
+
});
|
|
5901
|
+
}
|
|
5902
|
+
await validateScheduledTaskTarget({
|
|
5903
|
+
db: input.db,
|
|
5904
|
+
sessionAuthorization: input.sessionAuthorization,
|
|
5905
|
+
authorizationSurface: input.authorizationSurface,
|
|
5906
|
+
grant: input.grant,
|
|
5907
|
+
targetSessionId: nextTargetSessionId,
|
|
5908
|
+
runMode: nextRunMode,
|
|
5909
|
+
variableSetId: input.payload.variableSetId !== void 0 ? input.payload.variableSetId : input.existing.variableSetId,
|
|
5910
|
+
rigId: input.payload.rigId !== void 0 ? input.payload.rigId : input.existing.rigId,
|
|
5911
|
+
agentConfig: update.agentConfig ?? input.existing.agentConfig
|
|
5912
|
+
});
|
|
5913
|
+
if (input.payload.targetSessionId !== void 0 || input.existing.runMode === "existing_session" || nextRunMode === "existing_session") {
|
|
5914
|
+
update.targetSessionId = nextTargetSessionId;
|
|
5915
|
+
}
|
|
5601
5916
|
return update;
|
|
5602
5917
|
}
|
|
5603
5918
|
async function requireScheduledTaskForApi(db, workspaceId, taskId) {
|
|
@@ -5627,28 +5942,44 @@ async function restoreScheduledTask(db, previous) {
|
|
|
5627
5942
|
overlapPolicy: task.overlapPolicy,
|
|
5628
5943
|
agentConfig: task.agentConfig,
|
|
5629
5944
|
personalConnectionDelegations: previous.personalConnectionDelegations,
|
|
5630
|
-
reusableSessionId: task.reusableSessionId,
|
|
5945
|
+
...task.runMode === "existing_session" ? { targetSessionId: task.targetSessionId } : { reusableSessionId: task.reusableSessionId },
|
|
5631
5946
|
variableSetId: task.variableSetId,
|
|
5632
5947
|
rigId: task.rigId,
|
|
5633
5948
|
metadata: task.metadata
|
|
5634
5949
|
});
|
|
5635
5950
|
}
|
|
5951
|
+
var ScheduledTaskSyncError = class extends Error {
|
|
5952
|
+
persistenceRestored;
|
|
5953
|
+
constructor(cause, persistenceRestored) {
|
|
5954
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
5955
|
+
this.name = "ScheduledTaskSyncError";
|
|
5956
|
+
this.persistenceRestored = persistenceRestored;
|
|
5957
|
+
}
|
|
5958
|
+
};
|
|
5636
5959
|
async function syncCreatedScheduledTask(input) {
|
|
5637
5960
|
try {
|
|
5638
5961
|
await input.workflowClient.syncScheduledTask({ task: input.task });
|
|
5639
5962
|
} catch (error) {
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5963
|
+
let persistenceRestored = true;
|
|
5964
|
+
try {
|
|
5965
|
+
await deleteScheduledTask(input.db, input.task.workspaceId, input.task.id);
|
|
5966
|
+
} catch {
|
|
5967
|
+
persistenceRestored = false;
|
|
5968
|
+
}
|
|
5969
|
+
throw new ScheduledTaskSyncError(error, persistenceRestored);
|
|
5644
5970
|
}
|
|
5645
5971
|
}
|
|
5646
5972
|
async function syncUpdatedScheduledTask(input) {
|
|
5647
5973
|
try {
|
|
5648
5974
|
await input.workflowClient.syncScheduledTask({ task: input.task });
|
|
5649
5975
|
} catch (error) {
|
|
5650
|
-
|
|
5651
|
-
|
|
5976
|
+
let persistenceRestored = true;
|
|
5977
|
+
try {
|
|
5978
|
+
await restoreScheduledTask(input.db, input.previous);
|
|
5979
|
+
} catch {
|
|
5980
|
+
persistenceRestored = false;
|
|
5981
|
+
}
|
|
5982
|
+
throw new ScheduledTaskSyncError(error, persistenceRestored);
|
|
5652
5983
|
}
|
|
5653
5984
|
}
|
|
5654
5985
|
function scheduledTaskTemporalScheduleId(taskId) {
|
|
@@ -6158,7 +6489,6 @@ async function getWorkspaceInsights(db, settings, input) {
|
|
|
6158
6489
|
// src/domain/memory-slack-publication.ts
|
|
6159
6490
|
import { createHash } from "crypto";
|
|
6160
6491
|
import {
|
|
6161
|
-
redactSensitiveText,
|
|
6162
6492
|
stableJson as stableJson3
|
|
6163
6493
|
} from "@opengeni/contracts";
|
|
6164
6494
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
@@ -6227,9 +6557,8 @@ function evaluateMemorySlackPublication(input) {
|
|
|
6227
6557
|
const deliveryMode = effectiveDeliveryMode(policy, input.distribution);
|
|
6228
6558
|
if (!deliveryMode) return denied("below_noise_policy");
|
|
6229
6559
|
const collapsedSummary = collapseText(input.distribution.shareSummary);
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
const summary = truncateUtf8(redactedSummary, MEMORY_SLACK_SUMMARY_MAX_UTF8_BYTES);
|
|
6560
|
+
if (!collapsedSummary) return denied("missing_summary");
|
|
6561
|
+
const summary = truncateUtf8(collapsedSummary, MEMORY_SLACK_SUMMARY_MAX_UTF8_BYTES);
|
|
6233
6562
|
const namespace = normalizeNamespace(input.memory.namespace);
|
|
6234
6563
|
const labels = normalizeLabels(input.memory.labels);
|
|
6235
6564
|
if (!namespace || !labels) return denied("invalid_input");
|
|
@@ -6245,13 +6574,11 @@ function evaluateMemorySlackPublication(input) {
|
|
|
6245
6574
|
importance: input.distribution.importance,
|
|
6246
6575
|
deliveryMode,
|
|
6247
6576
|
summary: summary.value,
|
|
6248
|
-
summaryRedacted: redactedSummary !== collapsedSummary,
|
|
6249
6577
|
summaryTruncated: summary.truncated,
|
|
6250
6578
|
namespace,
|
|
6251
6579
|
labels: labels.values,
|
|
6252
6580
|
labelsTruncated: labels.truncated,
|
|
6253
6581
|
ownerLabel: owner.value,
|
|
6254
|
-
ownerLabelRedacted: owner.redacted,
|
|
6255
6582
|
ownerLabelTruncated: owner.truncated,
|
|
6256
6583
|
authoritativeRecord: {
|
|
6257
6584
|
workspaceId: input.context.workspaceId,
|
|
@@ -6325,10 +6652,8 @@ function effectiveDeliveryMode(policy, distribution) {
|
|
|
6325
6652
|
}
|
|
6326
6653
|
function normalizeNamespace(value) {
|
|
6327
6654
|
const trimmed = value.trim();
|
|
6328
|
-
if (redactSensitiveText(trimmed) !== trimmed) return null;
|
|
6329
6655
|
const namespace = trimmed.toLowerCase();
|
|
6330
6656
|
if (!namespace || utf8Bytes(namespace) > MEMORY_SLACK_NAMESPACE_MAX_UTF8_BYTES) return null;
|
|
6331
|
-
if (redactSensitiveText(namespace) !== namespace) return null;
|
|
6332
6657
|
const segments = namespace.split("/");
|
|
6333
6658
|
if (segments.some((segment) => !SELECTOR_SEGMENT_PATTERN.test(segment))) return null;
|
|
6334
6659
|
return segments.join("/");
|
|
@@ -6339,9 +6664,8 @@ function normalizeLabels(values) {
|
|
|
6339
6664
|
for (const value of values) {
|
|
6340
6665
|
if (typeof value !== "string") return null;
|
|
6341
6666
|
const trimmed = value.trim();
|
|
6342
|
-
if (redactSensitiveText(trimmed) !== trimmed) return null;
|
|
6343
6667
|
const label = trimmed.toLowerCase();
|
|
6344
|
-
if (
|
|
6668
|
+
if (!SELECTOR_SEGMENT_PATTERN.test(label) || utf8Bytes(label) > MEMORY_SLACK_LABEL_MAX_UTF8_BYTES) {
|
|
6345
6669
|
return null;
|
|
6346
6670
|
}
|
|
6347
6671
|
labels.add(label);
|
|
@@ -6354,12 +6678,10 @@ function normalizeLabels(values) {
|
|
|
6354
6678
|
}
|
|
6355
6679
|
function boundedOptionalText(value, maxBytes) {
|
|
6356
6680
|
const collapsed = collapseText(value ?? "");
|
|
6357
|
-
if (!collapsed) return { value: null,
|
|
6358
|
-
const
|
|
6359
|
-
const bounded = truncateUtf8(redacted, maxBytes);
|
|
6681
|
+
if (!collapsed) return { value: null, truncated: false };
|
|
6682
|
+
const bounded = truncateUtf8(collapsed, maxBytes);
|
|
6360
6683
|
return {
|
|
6361
6684
|
value: bounded.value || null,
|
|
6362
|
-
redacted: redacted !== collapsed,
|
|
6363
6685
|
truncated: bounded.truncated
|
|
6364
6686
|
};
|
|
6365
6687
|
}
|
|
@@ -6438,7 +6760,7 @@ import {
|
|
|
6438
6760
|
} from "@opengeni/contracts";
|
|
6439
6761
|
import {
|
|
6440
6762
|
getNewSessionDraftInTransaction,
|
|
6441
|
-
getEnrollment as
|
|
6763
|
+
getEnrollment as getEnrollment4,
|
|
6442
6764
|
getRig as getRig4,
|
|
6443
6765
|
getSandbox as getSandbox4,
|
|
6444
6766
|
getVariableSet as getVariableSet4,
|
|
@@ -6509,7 +6831,7 @@ async function hydrateNewSessionDraft(deps, grant, workspaceId, row) {
|
|
|
6509
6831
|
}
|
|
6510
6832
|
if (options.targetSandboxId) {
|
|
6511
6833
|
const sandbox = await getSandbox4(deps.db, workspaceId, options.targetSandboxId);
|
|
6512
|
-
const enrollment = sandbox?.enrollmentId ? await
|
|
6834
|
+
const enrollment = sandbox?.enrollmentId ? await getEnrollment4(deps.db, workspaceId, sandbox.enrollmentId) : null;
|
|
6513
6835
|
if (!sandbox || sandbox.kind !== "selfhosted" || !enrollment || enrollment.status !== "active") {
|
|
6514
6836
|
delete options.targetSandboxId;
|
|
6515
6837
|
delete options.workingDir;
|
|
@@ -6620,7 +6942,7 @@ import {
|
|
|
6620
6942
|
deleteSessionQueueItemInTransaction,
|
|
6621
6943
|
editQueuedTurnInTransaction,
|
|
6622
6944
|
getComposerDraftInTransaction,
|
|
6623
|
-
getSession as
|
|
6945
|
+
getSession as getSession4,
|
|
6624
6946
|
getSessionEvent as getSessionEvent2,
|
|
6625
6947
|
getWorkspaceControlEvent as getWorkspaceControlEvent2,
|
|
6626
6948
|
getSessionQueueSnapshot,
|
|
@@ -6712,10 +7034,14 @@ async function publishAndWakeAgentCommand(deps, input) {
|
|
|
6712
7034
|
wakeRevision: input.wakeRevision,
|
|
6713
7035
|
...input.controlRequested || input.interruptionCount > 0 ? { interruptionRequested: true } : {}
|
|
6714
7036
|
});
|
|
6715
|
-
} catch
|
|
7037
|
+
} catch {
|
|
6716
7038
|
console.warn(
|
|
6717
|
-
|
|
6718
|
-
|
|
7039
|
+
"[session-commands] immediate Agent command wake failed; durable outbox will retry",
|
|
7040
|
+
{
|
|
7041
|
+
errorClass: "WorkflowWakeOperationError",
|
|
7042
|
+
errorCode: "agent_command_wake_failed",
|
|
7043
|
+
origin: "core"
|
|
7044
|
+
}
|
|
6719
7045
|
);
|
|
6720
7046
|
}
|
|
6721
7047
|
}
|
|
@@ -6723,10 +7049,15 @@ async function requestControlWakeDispatch(deps, wakeCount) {
|
|
|
6723
7049
|
if (wakeCount === 0) return;
|
|
6724
7050
|
try {
|
|
6725
7051
|
await deps.workflowClient.requestSessionWorkflowWakeDispatch();
|
|
6726
|
-
} catch
|
|
7052
|
+
} catch {
|
|
6727
7053
|
console.warn(
|
|
6728
|
-
|
|
6729
|
-
|
|
7054
|
+
"[session-commands] immediate control wake dispatch failed; durable outbox will retry",
|
|
7055
|
+
{
|
|
7056
|
+
errorClass: "WorkflowWakeOperationError",
|
|
7057
|
+
errorCode: "control_wake_dispatch_failed",
|
|
7058
|
+
origin: "core",
|
|
7059
|
+
wakeCount
|
|
7060
|
+
}
|
|
6730
7061
|
);
|
|
6731
7062
|
}
|
|
6732
7063
|
}
|
|
@@ -6990,7 +7321,7 @@ async function steerHumanQueuePrompt(deps, context, turnId, input) {
|
|
|
6990
7321
|
await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
|
|
6991
7322
|
return response;
|
|
6992
7323
|
}
|
|
6993
|
-
async function
|
|
7324
|
+
async function controlHumanSessionWorkstreamWithOutcome(deps, context, input) {
|
|
6994
7325
|
const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
|
|
6995
7326
|
const result = await withWorkspaceRls(
|
|
6996
7327
|
deps.db,
|
|
@@ -7025,7 +7356,10 @@ async function controlHumanSessionWorkstream(deps, context, input) {
|
|
|
7025
7356
|
}
|
|
7026
7357
|
await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
|
|
7027
7358
|
await requestControlWakeDispatch(deps, result.wakeCount);
|
|
7028
|
-
return response;
|
|
7359
|
+
return { response, replay: result.replay };
|
|
7360
|
+
}
|
|
7361
|
+
async function controlHumanSessionWorkstream(deps, context, input) {
|
|
7362
|
+
return (await controlHumanSessionWorkstreamWithOutcome(deps, context, input)).response;
|
|
7029
7363
|
}
|
|
7030
7364
|
async function controlHumanWorkspace(deps, context, input) {
|
|
7031
7365
|
const result = await withWorkspaceRls(
|
|
@@ -7068,7 +7402,7 @@ async function getHumanComposerDraft(deps, context) {
|
|
|
7068
7402
|
);
|
|
7069
7403
|
const mapped = composerDraft(row);
|
|
7070
7404
|
if (mapped) return mapped;
|
|
7071
|
-
const session = await
|
|
7405
|
+
const session = await getSession4(deps.db, context.workspaceId, context.sessionId);
|
|
7072
7406
|
if (!session) throw new Error(`Session not found: ${context.sessionId}`);
|
|
7073
7407
|
return {
|
|
7074
7408
|
revision: 0,
|
|
@@ -7092,6 +7426,7 @@ async function saveHumanComposerDraft(deps, context, input) {
|
|
|
7092
7426
|
(tx) => saveComposerDraftInTransaction(tx, {
|
|
7093
7427
|
...context,
|
|
7094
7428
|
...input,
|
|
7429
|
+
resources: normalizeResources(input.resources),
|
|
7095
7430
|
subjectId: context.subjectId
|
|
7096
7431
|
})
|
|
7097
7432
|
)
|
|
@@ -7120,12 +7455,14 @@ export {
|
|
|
7120
7455
|
SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS,
|
|
7121
7456
|
SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID,
|
|
7122
7457
|
SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE,
|
|
7458
|
+
ScheduledTaskSyncError,
|
|
7123
7459
|
SessionAuthorizationDeniedError,
|
|
7124
7460
|
SessionAuthorizationUnavailableError,
|
|
7125
7461
|
SessionSpawnDeniedError,
|
|
7126
7462
|
TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS,
|
|
7127
7463
|
TranscriptionServiceError,
|
|
7128
7464
|
acceptSessionUserMessage,
|
|
7465
|
+
acceptSessionUserMessageWithOutcome,
|
|
7129
7466
|
accessGrantAuthorizationFromContext,
|
|
7130
7467
|
activateRigVersionForApi,
|
|
7131
7468
|
appendRigSetupCommand,
|
|
@@ -7148,14 +7485,18 @@ export {
|
|
|
7148
7485
|
captureScheduledTaskRestoreState,
|
|
7149
7486
|
checkLimit,
|
|
7150
7487
|
classifyRigVerificationOutcome,
|
|
7488
|
+
codexAppsCatalogItem,
|
|
7151
7489
|
controlAgentSessionWorkstream,
|
|
7152
7490
|
controlHumanSessionWorkstream,
|
|
7491
|
+
controlHumanSessionWorkstreamWithOutcome,
|
|
7153
7492
|
controlHumanWorkspace,
|
|
7154
7493
|
createAndStartSession,
|
|
7494
|
+
createAndStartSessionWithOutcome,
|
|
7155
7495
|
createCatalogItem,
|
|
7156
7496
|
createRigForApi,
|
|
7157
7497
|
createRigVersionForApi,
|
|
7158
7498
|
createSessionForRequest,
|
|
7499
|
+
createSessionForRequestWithOutcome,
|
|
7159
7500
|
createValidatedScheduledTask,
|
|
7160
7501
|
creationInitiatorForGrant,
|
|
7161
7502
|
defaultSessionMcpServerIds,
|
|
@@ -7174,7 +7515,9 @@ export {
|
|
|
7174
7515
|
getActorNewSessionDraft,
|
|
7175
7516
|
getCapabilityPack,
|
|
7176
7517
|
getHumanComposerDraft,
|
|
7518
|
+
getManagedSession,
|
|
7177
7519
|
getWorkspaceInsights,
|
|
7520
|
+
hasLiteralPermission,
|
|
7178
7521
|
hasPermission,
|
|
7179
7522
|
hasReservedOpenGeniSlackBotMetadata,
|
|
7180
7523
|
hasReservedOpenGeniSlackBotSessionMetadata,
|
|
@@ -7217,11 +7560,14 @@ export {
|
|
|
7217
7560
|
recordWorkspaceUsage,
|
|
7218
7561
|
relayConfigFromSettings,
|
|
7219
7562
|
relayDialBaseFromSettings,
|
|
7563
|
+
reportSessionUsageRecordingFailure,
|
|
7220
7564
|
requireAccessContext,
|
|
7221
7565
|
requireAccessGrant,
|
|
7222
7566
|
requireAccessGrantAuthorization,
|
|
7223
7567
|
requireEnvironmentEncryption,
|
|
7224
7568
|
requireLimit,
|
|
7569
|
+
requireLiteralPermission,
|
|
7570
|
+
requireLiveAgentAttemptAuthorization,
|
|
7225
7571
|
requireOpenGeniSlackBotConnection,
|
|
7226
7572
|
requirePermission,
|
|
7227
7573
|
requireQueuedTurnForApi,
|
|
@@ -7244,6 +7590,8 @@ export {
|
|
|
7244
7590
|
saveActorNewSessionDraft,
|
|
7245
7591
|
saveHumanComposerDraft,
|
|
7246
7592
|
scheduledSlackBotConnectionId,
|
|
7593
|
+
scheduledTaskForGrant,
|
|
7594
|
+
scheduledTaskRunForGrant,
|
|
7247
7595
|
scheduledTaskTemporalScheduleId,
|
|
7248
7596
|
scheduledTaskToolsProvided,
|
|
7249
7597
|
scheduledTaskTriggerToken,
|
|
@@ -7272,6 +7620,7 @@ export {
|
|
|
7272
7620
|
validateGitHubRepositorySelectionShapes,
|
|
7273
7621
|
validateMcpCapabilityConnection,
|
|
7274
7622
|
validateOpenGeniSlackBotConnectionSelection,
|
|
7623
|
+
validateScheduledTaskTarget,
|
|
7275
7624
|
validateToolRefs,
|
|
7276
7625
|
validateToolRefsForSessionPolicy,
|
|
7277
7626
|
validateVariableSetAttachment,
|