@opengeni/core 0.20.16 → 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/dependencies.d.ts +3 -1
- 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 +453 -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/dist/transcription.d.ts +35 -0
- package/package.json +10 -10
- package/src/access/index.ts +43 -3
- package/src/application/session-commands.ts +29 -9
- package/src/dependencies.ts +3 -1
- 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/src/transcription.ts +40 -0
package/dist/index.js
CHANGED
|
@@ -3,7 +3,28 @@ 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
|
|
27
|
+
var TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS = 10 * 60 * 1e3;
|
|
7
28
|
var TranscriptionServiceError = class extends Error {
|
|
8
29
|
code;
|
|
9
30
|
status;
|
|
@@ -78,7 +99,7 @@ function filenameForMimeType(mimeType) {
|
|
|
78
99
|
|
|
79
100
|
// src/sandbox/fleet.ts
|
|
80
101
|
import {
|
|
81
|
-
getEnrollment,
|
|
102
|
+
getEnrollment as getEnrollment2,
|
|
82
103
|
getSandbox as getSandbox2,
|
|
83
104
|
listSandboxes,
|
|
84
105
|
readActiveSandbox as readActiveSandbox2,
|
|
@@ -88,6 +109,7 @@ import {
|
|
|
88
109
|
} from "@opengeni/db";
|
|
89
110
|
import {
|
|
90
111
|
NatsControlRpc as NatsControlRpc2,
|
|
112
|
+
NatsOpStreamTransport as NatsOpStreamTransport2,
|
|
91
113
|
selfhostedLiveness,
|
|
92
114
|
SelfhostedSession,
|
|
93
115
|
swapTargetEstablishability
|
|
@@ -99,6 +121,7 @@ import { sandboxArchiveCaptureTimeoutMs } from "@opengeni/config";
|
|
|
99
121
|
import {
|
|
100
122
|
advanceWorkspaceGenerationForDirectRequest,
|
|
101
123
|
advanceWorkspaceGenerationForRetainedProcess,
|
|
124
|
+
getEnrollment,
|
|
102
125
|
getRetainedProcess,
|
|
103
126
|
getSandbox,
|
|
104
127
|
markWarmLeaseInstanceLost,
|
|
@@ -114,6 +137,7 @@ import {
|
|
|
114
137
|
isProviderSandboxGoneDuringRoutedOperation,
|
|
115
138
|
makeActiveBackendResolver,
|
|
116
139
|
NatsControlRpc,
|
|
140
|
+
NatsOpStreamTransport,
|
|
117
141
|
RoutingSandboxSession,
|
|
118
142
|
resolveModalCheckpointProviderBindingForSession
|
|
119
143
|
} from "@opengeni/runtime/sandbox";
|
|
@@ -153,6 +177,18 @@ function controlRpcFactory(bus) {
|
|
|
153
177
|
return bus.getRequestConnection();
|
|
154
178
|
});
|
|
155
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
|
+
}
|
|
156
192
|
function routingEnabled(settings) {
|
|
157
193
|
return settings.sandboxSelfhostedEnabled === true;
|
|
158
194
|
}
|
|
@@ -316,6 +352,7 @@ function wrapChannelABoxWithRouting(services, ids, established) {
|
|
|
316
352
|
} : null;
|
|
317
353
|
},
|
|
318
354
|
controlRpcFactory: controlRpcFactory(bus),
|
|
355
|
+
resolveSelfhostedOpStream: (sandbox) => resolveSelfhostedOpStream(services, ids.workspaceId, sandbox),
|
|
319
356
|
relay: relayConfigFromSettings(settings),
|
|
320
357
|
selfhostedTimeoutMs: settings.sandboxSelfhostedControlTimeoutMs,
|
|
321
358
|
selfhostedExecTimeoutMs: settings.sandboxSelfhostedExecTimeoutMs,
|
|
@@ -462,6 +499,10 @@ async function listFleet(services, ctx) {
|
|
|
462
499
|
const groupRecovering = Boolean(
|
|
463
500
|
groupLease && (groupLease.liveness === "warming" || groupLease.recovery.restore.status === "pending" || groupLease.recovery.restore.status === "restoring" || groupLease.recovery.restore.status === "verifying")
|
|
464
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";
|
|
465
506
|
entries.push({
|
|
466
507
|
id: ctx.sessionGroupId,
|
|
467
508
|
kind: ctx.sessionBackend === "selfhosted" ? "selfhosted" : "modal",
|
|
@@ -471,6 +512,7 @@ async function listFleet(services, ctx) {
|
|
|
471
512
|
isSessionGroup: true,
|
|
472
513
|
enrollmentId: null,
|
|
473
514
|
attachable: groupOnline,
|
|
515
|
+
operationAvailability: groupOperationAvailability,
|
|
474
516
|
providerStatus: groupLease?.recovery.provider.status ?? "not_created",
|
|
475
517
|
leaseLiveness: groupLease?.liveness ?? null,
|
|
476
518
|
routeStatus: groupActive ? "attached" : "detached",
|
|
@@ -488,7 +530,10 @@ async function listFleet(services, ctx) {
|
|
|
488
530
|
if (sandbox.kind !== "selfhosted" || !sandbox.enrollmentId) {
|
|
489
531
|
continue;
|
|
490
532
|
}
|
|
491
|
-
const enrollment = await
|
|
533
|
+
const enrollment = await getEnrollment2(db, ctx.workspaceId, sandbox.enrollmentId);
|
|
534
|
+
if (!enrollment || enrollment.status !== "active") {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
492
537
|
const probe = enrollment ? await probeEnrollment(services, ctx.workspaceId, enrollment) : { liveness: "offline", consented: false, hasDisplay: false };
|
|
493
538
|
entries.push({
|
|
494
539
|
id: sandbox.id,
|
|
@@ -499,6 +544,7 @@ async function listFleet(services, ctx) {
|
|
|
499
544
|
isSessionGroup: false,
|
|
500
545
|
enrollmentId: sandbox.enrollmentId,
|
|
501
546
|
attachable: probe.liveness === "online",
|
|
547
|
+
operationAvailability: probe.liveness === "online" ? "ready" : probe.liveness === "reconnecting" ? "recovering" : "unavailable",
|
|
502
548
|
consented: probe.consented,
|
|
503
549
|
hasDisplay: probe.hasDisplay,
|
|
504
550
|
lastSeenAt: enrollment?.lastSeenAt ?? null,
|
|
@@ -548,7 +594,7 @@ async function resolveTarget(services, ctx, target) {
|
|
|
548
594
|
code: "offline_enrollment"
|
|
549
595
|
};
|
|
550
596
|
}
|
|
551
|
-
const enrollment = await
|
|
597
|
+
const enrollment = await getEnrollment2(services.db, ctx.workspaceId, sandbox.enrollmentId);
|
|
552
598
|
if (!enrollment) {
|
|
553
599
|
return {
|
|
554
600
|
ok: false,
|
|
@@ -647,7 +693,8 @@ async function executeRunOnSelfhostedMachine(machine, target, op) {
|
|
|
647
693
|
controlRpc: machine.controlRpc,
|
|
648
694
|
relay: machine.relay,
|
|
649
695
|
timeoutMs: machine.controlTimeoutMs,
|
|
650
|
-
execTimeoutMs: machine.execTimeoutMs
|
|
696
|
+
execTimeoutMs: machine.execTimeoutMs,
|
|
697
|
+
...machine.opStream !== void 0 ? { opStream: machine.opStream } : {}
|
|
651
698
|
});
|
|
652
699
|
try {
|
|
653
700
|
if (op.kind === "exec") {
|
|
@@ -690,6 +737,8 @@ async function executeRunOnSelfhostedMachine(machine, target, op) {
|
|
|
690
737
|
// so leave `timedOut` absent while still reporting the enforced deadline.
|
|
691
738
|
...op.kind === "exec" ? { deadlineMs: session.effectiveExecDeadlineMs } : {}
|
|
692
739
|
};
|
|
740
|
+
} finally {
|
|
741
|
+
await session.finalizeOpStreamOps().catch(() => void 0);
|
|
693
742
|
}
|
|
694
743
|
}
|
|
695
744
|
async function runOnSandbox(services, ctx, target, op) {
|
|
@@ -711,7 +760,7 @@ async function runOnSandbox(services, ctx, target, op) {
|
|
|
711
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)`
|
|
712
761
|
};
|
|
713
762
|
}
|
|
714
|
-
const enrollment = await
|
|
763
|
+
const enrollment = await getEnrollment2(services.db, ctx.workspaceId, sandbox.enrollmentId);
|
|
715
764
|
if (!enrollment || enrollment.status !== "active") {
|
|
716
765
|
return {
|
|
717
766
|
target,
|
|
@@ -728,7 +777,14 @@ async function runOnSandbox(services, ctx, target, op) {
|
|
|
728
777
|
controlRpc: controlRpc(services.bus),
|
|
729
778
|
relay: relayConfigFromSettings(services.settings),
|
|
730
779
|
controlTimeoutMs: services.settings.sandboxSelfhostedControlTimeoutMs,
|
|
731
|
-
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
|
+
} : {}
|
|
732
788
|
},
|
|
733
789
|
target,
|
|
734
790
|
op
|
|
@@ -740,7 +796,7 @@ async function provisionSandbox(services, ctx, input) {
|
|
|
740
796
|
const base = (services.settings.publicBaseUrl ?? "https://get.opengeni.ai").replace(/\/+$/, "");
|
|
741
797
|
return {
|
|
742
798
|
kind: "selfhosted",
|
|
743
|
-
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.",
|
|
744
800
|
// Install from THIS control plane's origin (not a hardcoded public CDN): the
|
|
745
801
|
// served install script is rewritten to pull the per-SHA agent baked into
|
|
746
802
|
// this exact deployment (see apps/api/src/routes/install.ts), so a deployed
|
|
@@ -852,10 +908,43 @@ function requirePermission(grant, permission) {
|
|
|
852
908
|
});
|
|
853
909
|
}
|
|
854
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
|
+
}
|
|
855
921
|
function hasPermission(permissions, permission) {
|
|
922
|
+
if (permission === "secrets:read") {
|
|
923
|
+
return permissions.includes("secrets:read");
|
|
924
|
+
}
|
|
856
925
|
const aliases = {
|
|
857
926
|
"variable-sets:use": ["environments:use"],
|
|
858
|
-
"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"]
|
|
859
948
|
};
|
|
860
949
|
return permissions.includes(permission) || (aliases[permission]?.some((alias) => permissions.includes(alias)) ?? false) || permissions.includes("workspace:admin");
|
|
861
950
|
}
|
|
@@ -911,9 +1000,7 @@ async function resolveAccessContext(c, deps) {
|
|
|
911
1000
|
}
|
|
912
1001
|
}
|
|
913
1002
|
if (deps.managedAuth) {
|
|
914
|
-
const session = await deps.managedAuth
|
|
915
|
-
headers: c.req.raw.headers
|
|
916
|
-
});
|
|
1003
|
+
const session = await getManagedSession(c, deps.managedAuth);
|
|
917
1004
|
if (session?.user) {
|
|
918
1005
|
return await ensureManagedAccessForUser(deps.db, {
|
|
919
1006
|
userId: session.user.id,
|
|
@@ -1053,6 +1140,13 @@ var SessionAuthorizationUnavailableError = class extends Error {
|
|
|
1053
1140
|
this.name = "SessionAuthorizationUnavailableError";
|
|
1054
1141
|
}
|
|
1055
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
|
+
}
|
|
1056
1150
|
async function requireSessionAuthorization(deps, grant, input) {
|
|
1057
1151
|
const port = deps.sessionAuthorization;
|
|
1058
1152
|
const slackAccess = await getSlackInteractionSessionAccessForSession(deps.db, {
|
|
@@ -1748,7 +1842,8 @@ async function buildCapabilityCatalog(input) {
|
|
|
1748
1842
|
workspacePacks,
|
|
1749
1843
|
socialConnections,
|
|
1750
1844
|
bundledSkills,
|
|
1751
|
-
curatedLibrarySkills
|
|
1845
|
+
curatedLibrarySkills,
|
|
1846
|
+
codexAppsCredentialId
|
|
1752
1847
|
] = await Promise.all([
|
|
1753
1848
|
listCapabilityCatalogItems(input.db, input.workspaceId),
|
|
1754
1849
|
listCapabilityInstallations(input.db, input.workspaceId),
|
|
@@ -1756,7 +1851,8 @@ async function buildCapabilityCatalog(input) {
|
|
|
1756
1851
|
listWorkspaceCapabilityPacks(input.db, input.workspaceId),
|
|
1757
1852
|
listSocialConnections(input.db, input.workspaceId, 500, input.subjectId),
|
|
1758
1853
|
discoverBundledSkills(),
|
|
1759
|
-
discoverCuratedSkillLibraryItems()
|
|
1854
|
+
discoverCuratedSkillLibraryItems(),
|
|
1855
|
+
input.settings.codexConnectedAppsEnabled ? resolveCodexAppsCredentialIdForRun(input.db, input.workspaceId) : Promise.resolve(null)
|
|
1760
1856
|
]);
|
|
1761
1857
|
const capabilityInstallationById = new Map(
|
|
1762
1858
|
capabilityInstallations.map((installation) => [installation.capabilityId, installation])
|
|
@@ -1774,7 +1870,14 @@ async function buildCapabilityCatalog(input) {
|
|
|
1774
1870
|
...bundledSkills,
|
|
1775
1871
|
...curatedLibrarySkills
|
|
1776
1872
|
];
|
|
1777
|
-
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(
|
|
1778
1881
|
(item) => applyCapabilityEnablement(item, capabilityInstallationById.get(item.id), activePackIds)
|
|
1779
1882
|
).sort(compareCatalogItems);
|
|
1780
1883
|
return {
|
|
@@ -1794,7 +1897,7 @@ async function createCatalogItem(input) {
|
|
|
1794
1897
|
message: "skill ids are managed by the OpenGeni skill library or runtime adapters"
|
|
1795
1898
|
});
|
|
1796
1899
|
}
|
|
1797
|
-
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)) {
|
|
1798
1901
|
throw new HTTPException6(422, {
|
|
1799
1902
|
message: `${CODEX_APPS_MCP_SERVER_ID} is reserved for the canonical Codex Apps service`
|
|
1800
1903
|
});
|
|
@@ -2434,7 +2537,7 @@ function packCatalogItem(pack, source) {
|
|
|
2434
2537
|
});
|
|
2435
2538
|
}
|
|
2436
2539
|
function configuredMcpCatalogItems(settings) {
|
|
2437
|
-
return settings.mcpServers.map(
|
|
2540
|
+
return settings.mcpServers.filter((server) => server.id !== CODEX_APPS_MCP_SERVER_ID).map(
|
|
2438
2541
|
(server) => CapabilityCatalogItem.parse({
|
|
2439
2542
|
id: `mcp:${server.id}`,
|
|
2440
2543
|
kind: "mcp",
|
|
@@ -2459,6 +2562,38 @@ function configuredMcpCatalogItems(settings) {
|
|
|
2459
2562
|
})
|
|
2460
2563
|
);
|
|
2461
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
|
+
}
|
|
2462
2597
|
function platformApiCatalogItems(socialConnections) {
|
|
2463
2598
|
const xConnection = preferredSocialConnection(socialConnections, "x");
|
|
2464
2599
|
const xEnabled = xConnection?.status === "connected" || xConnection?.status === "needs_reauth";
|
|
@@ -2655,6 +2790,9 @@ function applyCapabilityEnablement(item, installation, activePackIds) {
|
|
|
2655
2790
|
if (item.surfaceType === "first_party_social") {
|
|
2656
2791
|
return { ...item, connectionRef: null };
|
|
2657
2792
|
}
|
|
2793
|
+
if (item.surfaceType === "codex_apps") {
|
|
2794
|
+
return { ...item, connectionRef: null };
|
|
2795
|
+
}
|
|
2658
2796
|
if (item.source === "built_in" || item.source === "configured") {
|
|
2659
2797
|
return {
|
|
2660
2798
|
...item,
|
|
@@ -3486,6 +3624,7 @@ import {
|
|
|
3486
3624
|
mergeResourceRefs as mergeContractResourceRefs,
|
|
3487
3625
|
mergeToolRefs,
|
|
3488
3626
|
normalizeRepositorySubpath,
|
|
3627
|
+
normalizeRepositoryTransportUri,
|
|
3489
3628
|
normalizeResourceMountPath,
|
|
3490
3629
|
resourceIdentityKey,
|
|
3491
3630
|
resourceMountPath,
|
|
@@ -3557,21 +3696,18 @@ function normalizeResources(resources) {
|
|
|
3557
3696
|
mountPath
|
|
3558
3697
|
};
|
|
3559
3698
|
} else {
|
|
3560
|
-
const
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
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
|
+
});
|
|
3568
3707
|
}
|
|
3569
|
-
const repo = parts.join("/");
|
|
3570
|
-
const normalizedUri = `https://${url.host.toLowerCase()}/${repo}.git`;
|
|
3571
3708
|
const mountPath = normalizeMountPath(
|
|
3572
|
-
resource.mountPath ?? defaultRepositoryMountPath(normalizedUri)
|
|
3709
|
+
resource.mountPath ?? defaultRepositoryMountPath(normalizedUri, credentialProvider)
|
|
3573
3710
|
);
|
|
3574
|
-
const credentialProvider = gitCredentialProviderForRepository(resource);
|
|
3575
3711
|
const credentialBindingId = gitCredentialBindingIdForRepository(resource, credentialProvider);
|
|
3576
3712
|
if ((resource.credentialBindingId || resource.connectionId || resource.access) && !credentialProvider) {
|
|
3577
3713
|
throw new HTTPException8(422, {
|
|
@@ -3731,13 +3867,6 @@ function normalizeMountPath(path) {
|
|
|
3731
3867
|
throw new HTTPException8(422, { message: `invalid resource mount path: ${path}` });
|
|
3732
3868
|
}
|
|
3733
3869
|
}
|
|
3734
|
-
function parseResourceUrl(uri) {
|
|
3735
|
-
try {
|
|
3736
|
-
return new URL(uri);
|
|
3737
|
-
} catch {
|
|
3738
|
-
throw new HTTPException8(422, { message: "repository resources must use valid URLs" });
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3741
3870
|
function positiveInteger(value) {
|
|
3742
3871
|
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
3743
3872
|
return value;
|
|
@@ -3881,6 +4010,7 @@ import {
|
|
|
3881
4010
|
getRig as getRig3,
|
|
3882
4011
|
getScheduledTask,
|
|
3883
4012
|
getScheduledTaskPersonalConnectionDelegations,
|
|
4013
|
+
getSession as getSession3,
|
|
3884
4014
|
requireWorkspace as requireWorkspace2,
|
|
3885
4015
|
updateScheduledTask
|
|
3886
4016
|
} from "@opengeni/db";
|
|
@@ -3915,7 +4045,7 @@ import {
|
|
|
3915
4045
|
createSessionWithIdempotencyKeyResult,
|
|
3916
4046
|
encryptVariableSetValue as encryptVariableSetValue2,
|
|
3917
4047
|
getAnySessionInGroup,
|
|
3918
|
-
getEnrollment as
|
|
4048
|
+
getEnrollment as getEnrollment3,
|
|
3919
4049
|
getRig as getRig2,
|
|
3920
4050
|
getWorkspaceDefaultRigId,
|
|
3921
4051
|
listDistinctVariableSetIdsInGroup,
|
|
@@ -4304,7 +4434,7 @@ function validateSessionMcpCredentialUpdates(input) {
|
|
|
4304
4434
|
});
|
|
4305
4435
|
return encryptedUpdates;
|
|
4306
4436
|
}
|
|
4307
|
-
async function
|
|
4437
|
+
async function createAndStartSessionWithOutcome(input) {
|
|
4308
4438
|
const sessionMetadata = {
|
|
4309
4439
|
...input.metadata,
|
|
4310
4440
|
model: input.model,
|
|
@@ -4350,12 +4480,24 @@ async function createAndStartSession(input) {
|
|
|
4350
4480
|
}
|
|
4351
4481
|
const { session: keyed, created } = keyedResult;
|
|
4352
4482
|
if (!created) {
|
|
4353
|
-
|
|
4483
|
+
const finished3 = await finishStartSession(
|
|
4354
4484
|
keyed.temporalWorkflowId ? { ...input, seedTargetSandbox: null } : input,
|
|
4355
4485
|
keyed
|
|
4356
4486
|
);
|
|
4487
|
+
return {
|
|
4488
|
+
session: finished3.session,
|
|
4489
|
+
outcome: finished3.changed ? "repaired" : "replayed",
|
|
4490
|
+
replay: !finished3.changed,
|
|
4491
|
+
changed: finished3.changed
|
|
4492
|
+
};
|
|
4357
4493
|
}
|
|
4358
|
-
|
|
4494
|
+
const finished2 = await finishStartSession(input, keyed);
|
|
4495
|
+
return {
|
|
4496
|
+
session: finished2.session,
|
|
4497
|
+
outcome: "created",
|
|
4498
|
+
replay: false,
|
|
4499
|
+
changed: true
|
|
4500
|
+
};
|
|
4359
4501
|
}
|
|
4360
4502
|
let session;
|
|
4361
4503
|
try {
|
|
@@ -4397,7 +4539,16 @@ async function createAndStartSession(input) {
|
|
|
4397
4539
|
}
|
|
4398
4540
|
throw error;
|
|
4399
4541
|
}
|
|
4400
|
-
|
|
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;
|
|
4401
4552
|
}
|
|
4402
4553
|
async function finishStartSession(input, session) {
|
|
4403
4554
|
if (input.seedTargetSandbox) {
|
|
@@ -4459,7 +4610,10 @@ async function finishStartSession(input, session) {
|
|
|
4459
4610
|
}
|
|
4460
4611
|
const persisted = await requireSession2(input.db, session.workspaceId, session.id);
|
|
4461
4612
|
const initialTurnId = started.turn?.id ?? (await listSessionTurns(input.db, session.workspaceId, session.id, 1))[0]?.id ?? null;
|
|
4462
|
-
return {
|
|
4613
|
+
return {
|
|
4614
|
+
session: { ...persisted, initialTurnId },
|
|
4615
|
+
changed: started.changed
|
|
4616
|
+
};
|
|
4463
4617
|
}
|
|
4464
4618
|
function workflowIdForSession(sessionId) {
|
|
4465
4619
|
return `session-${sessionId}`;
|
|
@@ -4644,15 +4798,16 @@ async function postUserMessageTurn(input) {
|
|
|
4644
4798
|
wakeRevision: result.wakeRevision,
|
|
4645
4799
|
...(input.delivery ?? "send") === "steer" || result.interruptionCount > 0 ? { interruptionRequested: true } : {}
|
|
4646
4800
|
});
|
|
4647
|
-
} catch
|
|
4648
|
-
console.warn(
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
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
|
+
});
|
|
4652
4807
|
}
|
|
4653
|
-
return { accepted, turn };
|
|
4808
|
+
return { accepted, turn, replay: result.replay };
|
|
4654
4809
|
}
|
|
4655
|
-
async function
|
|
4810
|
+
async function createSessionForRequestWithOutcome(deps, grant, workspaceId, rawPayload) {
|
|
4656
4811
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
4657
4812
|
const payload = CreateSessionRequest.parse(rawPayload);
|
|
4658
4813
|
if (hasReservedOpenGeniSlackBotSessionMetadata(payload.metadata)) {
|
|
@@ -4923,7 +5078,7 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
4923
5078
|
if (targetSandbox?.kind === "selfhosted") {
|
|
4924
5079
|
machineHomeBackend = "selfhosted";
|
|
4925
5080
|
if (targetSandbox.enrollmentId) {
|
|
4926
|
-
const enrollment = await
|
|
5081
|
+
const enrollment = await getEnrollment3(db, workspaceId, targetSandbox.enrollmentId);
|
|
4927
5082
|
if (enrollment && (enrollment.os === "macos" || enrollment.os === "windows" || enrollment.os === "linux")) {
|
|
4928
5083
|
machineHomeOs = enrollment.os;
|
|
4929
5084
|
}
|
|
@@ -4940,9 +5095,9 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
4940
5095
|
});
|
|
4941
5096
|
}
|
|
4942
5097
|
const creationInitiator = creationInitiatorForGrant(grant);
|
|
4943
|
-
let
|
|
5098
|
+
let createOutcome;
|
|
4944
5099
|
try {
|
|
4945
|
-
|
|
5100
|
+
createOutcome = await createAndStartSessionWithOutcome({
|
|
4946
5101
|
...payload.requestedSessionId ? { requestedSessionId: payload.requestedSessionId } : {},
|
|
4947
5102
|
db,
|
|
4948
5103
|
bus,
|
|
@@ -5017,26 +5172,45 @@ async function createSessionForRequest(deps, grant, workspaceId, rawPayload) {
|
|
|
5017
5172
|
}
|
|
5018
5173
|
throw error;
|
|
5019
5174
|
}
|
|
5175
|
+
let usageRecording = "recorded";
|
|
5020
5176
|
if (payload.startMode !== "realtime") {
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
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
|
+
}
|
|
5036
5197
|
}
|
|
5037
|
-
return
|
|
5198
|
+
return { ...createOutcome, usageRecording };
|
|
5038
5199
|
}
|
|
5039
|
-
|
|
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) {
|
|
5040
5214
|
const { settings, db, bus, workflowClient, objectStorage } = deps;
|
|
5041
5215
|
await requireSessionAuthorization(deps, grant, {
|
|
5042
5216
|
sessionId,
|
|
@@ -5104,7 +5278,7 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
|
|
|
5104
5278
|
source: personalConnectionDelegationSourceForGrant(grant)
|
|
5105
5279
|
});
|
|
5106
5280
|
const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
|
|
5107
|
-
const { accepted, turn } = await postUserMessageTurn({
|
|
5281
|
+
const { accepted, turn, replay } = await postUserMessageTurn({
|
|
5108
5282
|
db,
|
|
5109
5283
|
bus,
|
|
5110
5284
|
workflowClient,
|
|
@@ -5154,6 +5328,16 @@ async function acceptSessionUserMessage(deps, grant, workspaceId, sessionId, inp
|
|
|
5154
5328
|
origin: turn.source,
|
|
5155
5329
|
idempotencyKey: `agent_run.created:${workspaceId}:${turn.id}`
|
|
5156
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
|
+
);
|
|
5157
5341
|
return { accepted, turn };
|
|
5158
5342
|
}
|
|
5159
5343
|
async function updateSessionTitle(deps, grant, sessionId, title, source) {
|
|
@@ -5451,6 +5635,17 @@ async function createValidatedScheduledTask(input) {
|
|
|
5451
5635
|
});
|
|
5452
5636
|
const id = crypto.randomUUID();
|
|
5453
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
|
+
});
|
|
5454
5649
|
if (input.payload.variableSetId) {
|
|
5455
5650
|
await validateVariableSetAttachment(
|
|
5456
5651
|
{ settings: input.settings, db: input.db },
|
|
@@ -5491,11 +5686,97 @@ async function createValidatedScheduledTask(input) {
|
|
|
5491
5686
|
...creationInitiator.context ? { createdByContext: creationInitiator.context } : {},
|
|
5492
5687
|
createdByActor: creationInitiator.actor ?? null,
|
|
5493
5688
|
personalConnectionDelegations,
|
|
5689
|
+
targetSessionId: target?.id ?? null,
|
|
5494
5690
|
variableSetId: input.payload.variableSetId ?? null,
|
|
5495
5691
|
rigId: input.payload.rigId ?? null,
|
|
5496
5692
|
metadata: input.payload.metadata
|
|
5497
5693
|
});
|
|
5498
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
|
+
}
|
|
5499
5780
|
async function requireScheduledTaskRig(db, workspaceId, rigId) {
|
|
5500
5781
|
const rig = await getRig3(db, workspaceId, rigId);
|
|
5501
5782
|
if (!rig) {
|
|
@@ -5504,6 +5785,14 @@ async function requireScheduledTaskRig(db, workspaceId, rigId) {
|
|
|
5504
5785
|
}
|
|
5505
5786
|
async function validatedScheduledTaskUpdate(input) {
|
|
5506
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
|
+
}
|
|
5507
5796
|
if (input.payload.name !== void 0) {
|
|
5508
5797
|
update.name = trimmedScheduledTaskName(input.payload.name);
|
|
5509
5798
|
}
|
|
@@ -5597,6 +5886,33 @@ async function validatedScheduledTaskUpdate(input) {
|
|
|
5597
5886
|
}
|
|
5598
5887
|
update.personalConnectionDelegations = personalConnectionDelegations;
|
|
5599
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
|
+
}
|
|
5600
5916
|
return update;
|
|
5601
5917
|
}
|
|
5602
5918
|
async function requireScheduledTaskForApi(db, workspaceId, taskId) {
|
|
@@ -5626,28 +5942,44 @@ async function restoreScheduledTask(db, previous) {
|
|
|
5626
5942
|
overlapPolicy: task.overlapPolicy,
|
|
5627
5943
|
agentConfig: task.agentConfig,
|
|
5628
5944
|
personalConnectionDelegations: previous.personalConnectionDelegations,
|
|
5629
|
-
reusableSessionId: task.reusableSessionId,
|
|
5945
|
+
...task.runMode === "existing_session" ? { targetSessionId: task.targetSessionId } : { reusableSessionId: task.reusableSessionId },
|
|
5630
5946
|
variableSetId: task.variableSetId,
|
|
5631
5947
|
rigId: task.rigId,
|
|
5632
5948
|
metadata: task.metadata
|
|
5633
5949
|
});
|
|
5634
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
|
+
};
|
|
5635
5959
|
async function syncCreatedScheduledTask(input) {
|
|
5636
5960
|
try {
|
|
5637
5961
|
await input.workflowClient.syncScheduledTask({ task: input.task });
|
|
5638
5962
|
} catch (error) {
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
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);
|
|
5643
5970
|
}
|
|
5644
5971
|
}
|
|
5645
5972
|
async function syncUpdatedScheduledTask(input) {
|
|
5646
5973
|
try {
|
|
5647
5974
|
await input.workflowClient.syncScheduledTask({ task: input.task });
|
|
5648
5975
|
} catch (error) {
|
|
5649
|
-
|
|
5650
|
-
|
|
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);
|
|
5651
5983
|
}
|
|
5652
5984
|
}
|
|
5653
5985
|
function scheduledTaskTemporalScheduleId(taskId) {
|
|
@@ -6157,7 +6489,6 @@ async function getWorkspaceInsights(db, settings, input) {
|
|
|
6157
6489
|
// src/domain/memory-slack-publication.ts
|
|
6158
6490
|
import { createHash } from "crypto";
|
|
6159
6491
|
import {
|
|
6160
|
-
redactSensitiveText,
|
|
6161
6492
|
stableJson as stableJson3
|
|
6162
6493
|
} from "@opengeni/contracts";
|
|
6163
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}$/;
|
|
@@ -6226,9 +6557,8 @@ function evaluateMemorySlackPublication(input) {
|
|
|
6226
6557
|
const deliveryMode = effectiveDeliveryMode(policy, input.distribution);
|
|
6227
6558
|
if (!deliveryMode) return denied("below_noise_policy");
|
|
6228
6559
|
const collapsedSummary = collapseText(input.distribution.shareSummary);
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
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);
|
|
6232
6562
|
const namespace = normalizeNamespace(input.memory.namespace);
|
|
6233
6563
|
const labels = normalizeLabels(input.memory.labels);
|
|
6234
6564
|
if (!namespace || !labels) return denied("invalid_input");
|
|
@@ -6244,13 +6574,11 @@ function evaluateMemorySlackPublication(input) {
|
|
|
6244
6574
|
importance: input.distribution.importance,
|
|
6245
6575
|
deliveryMode,
|
|
6246
6576
|
summary: summary.value,
|
|
6247
|
-
summaryRedacted: redactedSummary !== collapsedSummary,
|
|
6248
6577
|
summaryTruncated: summary.truncated,
|
|
6249
6578
|
namespace,
|
|
6250
6579
|
labels: labels.values,
|
|
6251
6580
|
labelsTruncated: labels.truncated,
|
|
6252
6581
|
ownerLabel: owner.value,
|
|
6253
|
-
ownerLabelRedacted: owner.redacted,
|
|
6254
6582
|
ownerLabelTruncated: owner.truncated,
|
|
6255
6583
|
authoritativeRecord: {
|
|
6256
6584
|
workspaceId: input.context.workspaceId,
|
|
@@ -6324,10 +6652,8 @@ function effectiveDeliveryMode(policy, distribution) {
|
|
|
6324
6652
|
}
|
|
6325
6653
|
function normalizeNamespace(value) {
|
|
6326
6654
|
const trimmed = value.trim();
|
|
6327
|
-
if (redactSensitiveText(trimmed) !== trimmed) return null;
|
|
6328
6655
|
const namespace = trimmed.toLowerCase();
|
|
6329
6656
|
if (!namespace || utf8Bytes(namespace) > MEMORY_SLACK_NAMESPACE_MAX_UTF8_BYTES) return null;
|
|
6330
|
-
if (redactSensitiveText(namespace) !== namespace) return null;
|
|
6331
6657
|
const segments = namespace.split("/");
|
|
6332
6658
|
if (segments.some((segment) => !SELECTOR_SEGMENT_PATTERN.test(segment))) return null;
|
|
6333
6659
|
return segments.join("/");
|
|
@@ -6338,9 +6664,8 @@ function normalizeLabels(values) {
|
|
|
6338
6664
|
for (const value of values) {
|
|
6339
6665
|
if (typeof value !== "string") return null;
|
|
6340
6666
|
const trimmed = value.trim();
|
|
6341
|
-
if (redactSensitiveText(trimmed) !== trimmed) return null;
|
|
6342
6667
|
const label = trimmed.toLowerCase();
|
|
6343
|
-
if (
|
|
6668
|
+
if (!SELECTOR_SEGMENT_PATTERN.test(label) || utf8Bytes(label) > MEMORY_SLACK_LABEL_MAX_UTF8_BYTES) {
|
|
6344
6669
|
return null;
|
|
6345
6670
|
}
|
|
6346
6671
|
labels.add(label);
|
|
@@ -6353,12 +6678,10 @@ function normalizeLabels(values) {
|
|
|
6353
6678
|
}
|
|
6354
6679
|
function boundedOptionalText(value, maxBytes) {
|
|
6355
6680
|
const collapsed = collapseText(value ?? "");
|
|
6356
|
-
if (!collapsed) return { value: null,
|
|
6357
|
-
const
|
|
6358
|
-
const bounded = truncateUtf8(redacted, maxBytes);
|
|
6681
|
+
if (!collapsed) return { value: null, truncated: false };
|
|
6682
|
+
const bounded = truncateUtf8(collapsed, maxBytes);
|
|
6359
6683
|
return {
|
|
6360
6684
|
value: bounded.value || null,
|
|
6361
|
-
redacted: redacted !== collapsed,
|
|
6362
6685
|
truncated: bounded.truncated
|
|
6363
6686
|
};
|
|
6364
6687
|
}
|
|
@@ -6437,7 +6760,7 @@ import {
|
|
|
6437
6760
|
} from "@opengeni/contracts";
|
|
6438
6761
|
import {
|
|
6439
6762
|
getNewSessionDraftInTransaction,
|
|
6440
|
-
getEnrollment as
|
|
6763
|
+
getEnrollment as getEnrollment4,
|
|
6441
6764
|
getRig as getRig4,
|
|
6442
6765
|
getSandbox as getSandbox4,
|
|
6443
6766
|
getVariableSet as getVariableSet4,
|
|
@@ -6508,7 +6831,7 @@ async function hydrateNewSessionDraft(deps, grant, workspaceId, row) {
|
|
|
6508
6831
|
}
|
|
6509
6832
|
if (options.targetSandboxId) {
|
|
6510
6833
|
const sandbox = await getSandbox4(deps.db, workspaceId, options.targetSandboxId);
|
|
6511
|
-
const enrollment = sandbox?.enrollmentId ? await
|
|
6834
|
+
const enrollment = sandbox?.enrollmentId ? await getEnrollment4(deps.db, workspaceId, sandbox.enrollmentId) : null;
|
|
6512
6835
|
if (!sandbox || sandbox.kind !== "selfhosted" || !enrollment || enrollment.status !== "active") {
|
|
6513
6836
|
delete options.targetSandboxId;
|
|
6514
6837
|
delete options.workingDir;
|
|
@@ -6619,7 +6942,7 @@ import {
|
|
|
6619
6942
|
deleteSessionQueueItemInTransaction,
|
|
6620
6943
|
editQueuedTurnInTransaction,
|
|
6621
6944
|
getComposerDraftInTransaction,
|
|
6622
|
-
getSession as
|
|
6945
|
+
getSession as getSession4,
|
|
6623
6946
|
getSessionEvent as getSessionEvent2,
|
|
6624
6947
|
getWorkspaceControlEvent as getWorkspaceControlEvent2,
|
|
6625
6948
|
getSessionQueueSnapshot,
|
|
@@ -6711,10 +7034,14 @@ async function publishAndWakeAgentCommand(deps, input) {
|
|
|
6711
7034
|
wakeRevision: input.wakeRevision,
|
|
6712
7035
|
...input.controlRequested || input.interruptionCount > 0 ? { interruptionRequested: true } : {}
|
|
6713
7036
|
});
|
|
6714
|
-
} catch
|
|
7037
|
+
} catch {
|
|
6715
7038
|
console.warn(
|
|
6716
|
-
|
|
6717
|
-
|
|
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
|
+
}
|
|
6718
7045
|
);
|
|
6719
7046
|
}
|
|
6720
7047
|
}
|
|
@@ -6722,10 +7049,15 @@ async function requestControlWakeDispatch(deps, wakeCount) {
|
|
|
6722
7049
|
if (wakeCount === 0) return;
|
|
6723
7050
|
try {
|
|
6724
7051
|
await deps.workflowClient.requestSessionWorkflowWakeDispatch();
|
|
6725
|
-
} catch
|
|
7052
|
+
} catch {
|
|
6726
7053
|
console.warn(
|
|
6727
|
-
|
|
6728
|
-
|
|
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
|
+
}
|
|
6729
7061
|
);
|
|
6730
7062
|
}
|
|
6731
7063
|
}
|
|
@@ -6989,7 +7321,7 @@ async function steerHumanQueuePrompt(deps, context, turnId, input) {
|
|
|
6989
7321
|
await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
|
|
6990
7322
|
return response;
|
|
6991
7323
|
}
|
|
6992
|
-
async function
|
|
7324
|
+
async function controlHumanSessionWorkstreamWithOutcome(deps, context, input) {
|
|
6993
7325
|
const authorization = await authorizeHumanSessionCommand(deps, context, "session.control");
|
|
6994
7326
|
const result = await withWorkspaceRls(
|
|
6995
7327
|
deps.db,
|
|
@@ -7024,7 +7356,10 @@ async function controlHumanSessionWorkstream(deps, context, input) {
|
|
|
7024
7356
|
}
|
|
7025
7357
|
await publishWorkspaceControlEvent(deps, context.workspaceId, result.workspaceControlEventId);
|
|
7026
7358
|
await requestControlWakeDispatch(deps, result.wakeCount);
|
|
7027
|
-
return response;
|
|
7359
|
+
return { response, replay: result.replay };
|
|
7360
|
+
}
|
|
7361
|
+
async function controlHumanSessionWorkstream(deps, context, input) {
|
|
7362
|
+
return (await controlHumanSessionWorkstreamWithOutcome(deps, context, input)).response;
|
|
7028
7363
|
}
|
|
7029
7364
|
async function controlHumanWorkspace(deps, context, input) {
|
|
7030
7365
|
const result = await withWorkspaceRls(
|
|
@@ -7067,7 +7402,7 @@ async function getHumanComposerDraft(deps, context) {
|
|
|
7067
7402
|
);
|
|
7068
7403
|
const mapped = composerDraft(row);
|
|
7069
7404
|
if (mapped) return mapped;
|
|
7070
|
-
const session = await
|
|
7405
|
+
const session = await getSession4(deps.db, context.workspaceId, context.sessionId);
|
|
7071
7406
|
if (!session) throw new Error(`Session not found: ${context.sessionId}`);
|
|
7072
7407
|
return {
|
|
7073
7408
|
revision: 0,
|
|
@@ -7091,6 +7426,7 @@ async function saveHumanComposerDraft(deps, context, input) {
|
|
|
7091
7426
|
(tx) => saveComposerDraftInTransaction(tx, {
|
|
7092
7427
|
...context,
|
|
7093
7428
|
...input,
|
|
7429
|
+
resources: normalizeResources(input.resources),
|
|
7094
7430
|
subjectId: context.subjectId
|
|
7095
7431
|
})
|
|
7096
7432
|
)
|
|
@@ -7119,11 +7455,14 @@ export {
|
|
|
7119
7455
|
SESSION_WORKFLOW_WAKE_DISPATCHER_PERIOD_MS,
|
|
7120
7456
|
SESSION_WORKFLOW_WAKE_DISPATCHER_SCHEDULE_ID,
|
|
7121
7457
|
SESSION_WORKFLOW_WAKE_DISPATCHER_WORKFLOW_TYPE,
|
|
7458
|
+
ScheduledTaskSyncError,
|
|
7122
7459
|
SessionAuthorizationDeniedError,
|
|
7123
7460
|
SessionAuthorizationUnavailableError,
|
|
7124
7461
|
SessionSpawnDeniedError,
|
|
7462
|
+
TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS,
|
|
7125
7463
|
TranscriptionServiceError,
|
|
7126
7464
|
acceptSessionUserMessage,
|
|
7465
|
+
acceptSessionUserMessageWithOutcome,
|
|
7127
7466
|
accessGrantAuthorizationFromContext,
|
|
7128
7467
|
activateRigVersionForApi,
|
|
7129
7468
|
appendRigSetupCommand,
|
|
@@ -7146,14 +7485,18 @@ export {
|
|
|
7146
7485
|
captureScheduledTaskRestoreState,
|
|
7147
7486
|
checkLimit,
|
|
7148
7487
|
classifyRigVerificationOutcome,
|
|
7488
|
+
codexAppsCatalogItem,
|
|
7149
7489
|
controlAgentSessionWorkstream,
|
|
7150
7490
|
controlHumanSessionWorkstream,
|
|
7491
|
+
controlHumanSessionWorkstreamWithOutcome,
|
|
7151
7492
|
controlHumanWorkspace,
|
|
7152
7493
|
createAndStartSession,
|
|
7494
|
+
createAndStartSessionWithOutcome,
|
|
7153
7495
|
createCatalogItem,
|
|
7154
7496
|
createRigForApi,
|
|
7155
7497
|
createRigVersionForApi,
|
|
7156
7498
|
createSessionForRequest,
|
|
7499
|
+
createSessionForRequestWithOutcome,
|
|
7157
7500
|
createValidatedScheduledTask,
|
|
7158
7501
|
creationInitiatorForGrant,
|
|
7159
7502
|
defaultSessionMcpServerIds,
|
|
@@ -7172,7 +7515,9 @@ export {
|
|
|
7172
7515
|
getActorNewSessionDraft,
|
|
7173
7516
|
getCapabilityPack,
|
|
7174
7517
|
getHumanComposerDraft,
|
|
7518
|
+
getManagedSession,
|
|
7175
7519
|
getWorkspaceInsights,
|
|
7520
|
+
hasLiteralPermission,
|
|
7176
7521
|
hasPermission,
|
|
7177
7522
|
hasReservedOpenGeniSlackBotMetadata,
|
|
7178
7523
|
hasReservedOpenGeniSlackBotSessionMetadata,
|
|
@@ -7215,11 +7560,14 @@ export {
|
|
|
7215
7560
|
recordWorkspaceUsage,
|
|
7216
7561
|
relayConfigFromSettings,
|
|
7217
7562
|
relayDialBaseFromSettings,
|
|
7563
|
+
reportSessionUsageRecordingFailure,
|
|
7218
7564
|
requireAccessContext,
|
|
7219
7565
|
requireAccessGrant,
|
|
7220
7566
|
requireAccessGrantAuthorization,
|
|
7221
7567
|
requireEnvironmentEncryption,
|
|
7222
7568
|
requireLimit,
|
|
7569
|
+
requireLiteralPermission,
|
|
7570
|
+
requireLiveAgentAttemptAuthorization,
|
|
7223
7571
|
requireOpenGeniSlackBotConnection,
|
|
7224
7572
|
requirePermission,
|
|
7225
7573
|
requireQueuedTurnForApi,
|
|
@@ -7242,6 +7590,8 @@ export {
|
|
|
7242
7590
|
saveActorNewSessionDraft,
|
|
7243
7591
|
saveHumanComposerDraft,
|
|
7244
7592
|
scheduledSlackBotConnectionId,
|
|
7593
|
+
scheduledTaskForGrant,
|
|
7594
|
+
scheduledTaskRunForGrant,
|
|
7245
7595
|
scheduledTaskTemporalScheduleId,
|
|
7246
7596
|
scheduledTaskToolsProvided,
|
|
7247
7597
|
scheduledTaskTriggerToken,
|
|
@@ -7270,6 +7620,7 @@ export {
|
|
|
7270
7620
|
validateGitHubRepositorySelectionShapes,
|
|
7271
7621
|
validateMcpCapabilityConnection,
|
|
7272
7622
|
validateOpenGeniSlackBotConnectionSelection,
|
|
7623
|
+
validateScheduledTaskTarget,
|
|
7273
7624
|
validateToolRefs,
|
|
7274
7625
|
validateToolRefsForSessionPolicy,
|
|
7275
7626
|
validateVariableSetAttachment,
|