@opengeni/contracts 0.7.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/index.d.ts +3516 -339
- package/dist/index.js +1225 -147
- package/dist/index.js.map +1 -1
- package/package.json +10 -13
- package/src/index.ts +1639 -221
package/src/index.ts
CHANGED
|
@@ -5,6 +5,9 @@ export const SessionStatus = z.enum([
|
|
|
5
5
|
"running",
|
|
6
6
|
"idle",
|
|
7
7
|
"requires_action",
|
|
8
|
+
"recovering",
|
|
9
|
+
"waiting_capacity",
|
|
10
|
+
"paused",
|
|
8
11
|
"failed",
|
|
9
12
|
"cancelled",
|
|
10
13
|
]);
|
|
@@ -470,12 +473,22 @@ export const Permission = z.enum([
|
|
|
470
473
|
"github:manage",
|
|
471
474
|
"github:use",
|
|
472
475
|
"api_keys:manage",
|
|
476
|
+
"connections:read",
|
|
477
|
+
"connections:write",
|
|
478
|
+
/** @deprecated alias of variable-sets:manage */
|
|
473
479
|
"environments:manage",
|
|
480
|
+
/** @deprecated alias of variable-sets:use */
|
|
474
481
|
"environments:use",
|
|
482
|
+
"variable-sets:manage",
|
|
483
|
+
"variable-sets:use",
|
|
475
484
|
// Attach or rotate per-session third-party MCP server credentials. Deliberately
|
|
476
485
|
// not part of the worker's default first-party MCP permission set: a sandboxed
|
|
477
486
|
// agent must not be able to hand itself new bearer credentials.
|
|
478
487
|
"mcp_servers:attach",
|
|
488
|
+
// Programmatic sandbox -> tool access through the first-party MCP gate. This is
|
|
489
|
+
// intentionally narrow and is never part of first-party MCP defaults; callers
|
|
490
|
+
// must receive it through an explicit delegated `ogd_` mint carrying sessionId.
|
|
491
|
+
"toolspace:call",
|
|
479
492
|
"goals:manage",
|
|
480
493
|
// Bring-your-own-compute (M5). enrollments:read lists a workspace's machines;
|
|
481
494
|
// enrollments:manage approves a device-flow enrollment (the LOUD whole-machine
|
|
@@ -484,9 +497,20 @@ export const Permission = z.enum([
|
|
|
484
497
|
// admin-shaped action. workspace:admin is the super-wildcard over both.
|
|
485
498
|
"enrollments:read",
|
|
486
499
|
"enrollments:manage",
|
|
500
|
+
// Rigs (workspace-scoped, versioned sandbox machine definitions). rigs:use is
|
|
501
|
+
// read + propose-change (the agent-native, additive path a sandboxed session
|
|
502
|
+
// is trusted with); rigs:manage is create/edit/activate/promote/delete (the
|
|
503
|
+
// admin-shaped path that mints or rolls versions). workspace:admin is the
|
|
504
|
+
// super-wildcard over both.
|
|
505
|
+
"rigs:use",
|
|
506
|
+
"rigs:manage",
|
|
487
507
|
]);
|
|
488
508
|
export type Permission = z.infer<typeof Permission>;
|
|
489
509
|
|
|
510
|
+
export function prefixedMcpToolName(registryId: string, toolName: string): string {
|
|
511
|
+
return `${registryId}__${toolName}`;
|
|
512
|
+
}
|
|
513
|
+
|
|
490
514
|
export const ProductAccessMode = z.enum(["local", "configured", "managed"]);
|
|
491
515
|
export type ProductAccessMode = z.infer<typeof ProductAccessMode>;
|
|
492
516
|
|
|
@@ -522,14 +546,66 @@ export const Workspace = z.object({
|
|
|
522
546
|
// Per-workspace agent persona template (white-label override). null means
|
|
523
547
|
// the deployment default (OPENGENI_AGENT_INSTRUCTIONS_TEMPLATE /
|
|
524
548
|
// DEFAULT_AGENT_INSTRUCTIONS) is used. The runtime always injects the
|
|
525
|
-
// non-bypassable CORE (goal-loop ownership +
|
|
549
|
+
// non-bypassable CORE (goal-loop ownership + variableSet block), so an
|
|
526
550
|
// override restyles the persona without dropping that contract.
|
|
527
551
|
agentInstructions: z.string().nullable(),
|
|
552
|
+
// Growth-ready per-workspace settings bag (migration 0045). Known keys are
|
|
553
|
+
// validated by WorkspaceSettingsSchema; unknown keys are preserved across
|
|
554
|
+
// PATCH merges so newer settings survive an older server.
|
|
555
|
+
settings: z.record(z.string(), z.unknown()),
|
|
556
|
+
inferenceState: z.enum(["active", "paused"]),
|
|
557
|
+
inferenceGeneration: z.number().int().nonnegative(),
|
|
558
|
+
inferenceReason: z.string().nullable(),
|
|
559
|
+
inferenceChangedBy: z.string().nullable(),
|
|
560
|
+
inferenceChangedAt: z.string().nullable(),
|
|
561
|
+
// Workspace default rig used by session/scheduled-task create fallback.
|
|
562
|
+
defaultRigId: z.string().uuid().nullable(),
|
|
528
563
|
createdAt: z.string(),
|
|
529
564
|
updatedAt: z.string(),
|
|
530
565
|
});
|
|
531
566
|
export type Workspace = z.infer<typeof Workspace>;
|
|
532
567
|
|
|
568
|
+
// Validates the KNOWN keys of workspaces.settings; passthrough keeps unknown
|
|
569
|
+
// (future) keys rather than stripping them. memoryEnabled gates Workspace Memory
|
|
570
|
+
// V1 agent surfaces (turn injection + first-party memory tools); default false.
|
|
571
|
+
export const WorkspaceSettingsSchema = z
|
|
572
|
+
.object({
|
|
573
|
+
memoryEnabled: z.boolean().optional(),
|
|
574
|
+
})
|
|
575
|
+
.passthrough();
|
|
576
|
+
export type WorkspaceSettings = z.infer<typeof WorkspaceSettingsSchema>;
|
|
577
|
+
|
|
578
|
+
// Resolve the effective memoryEnabled flag from a raw settings bag (default off).
|
|
579
|
+
export function resolveWorkspaceMemoryEnabled(settings: unknown): boolean {
|
|
580
|
+
const parsed = WorkspaceSettingsSchema.safeParse(settings ?? {});
|
|
581
|
+
return parsed.success ? parsed.data.memoryEnabled === true : false;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// PATCH body for workspace settings: a partial patch that deep-merges into the
|
|
585
|
+
// stored bag. memoryEnabled is the only typed key today; passthrough carries
|
|
586
|
+
// forward-compatible unknown keys through validation.
|
|
587
|
+
export const UpdateWorkspaceSettingsRequest = z
|
|
588
|
+
.object({
|
|
589
|
+
memoryEnabled: z.boolean().optional(),
|
|
590
|
+
})
|
|
591
|
+
.passthrough();
|
|
592
|
+
export type UpdateWorkspaceSettingsRequest = z.infer<typeof UpdateWorkspaceSettingsRequest>;
|
|
593
|
+
|
|
594
|
+
export const SetWorkspaceDefaultRigRequest = z.object({
|
|
595
|
+
rigId: z.string().uuid().nullable(),
|
|
596
|
+
});
|
|
597
|
+
export type SetWorkspaceDefaultRigRequest = z.infer<typeof SetWorkspaceDefaultRigRequest>;
|
|
598
|
+
|
|
599
|
+
// PUT body for the workspace model policy (full replace, not a merge). null (or
|
|
600
|
+
// omitted) = unrestricted for that dimension; an EMPTY array is a valid,
|
|
601
|
+
// explicit total block. Entries are provider ids / exact model ids as the
|
|
602
|
+
// router resolves them (see evaluateWorkspaceModelPolicy).
|
|
603
|
+
export const UpdateWorkspaceModelPolicyRequest = z.object({
|
|
604
|
+
allowedProviders: z.array(z.string().min(1).max(128)).max(64).nullable().optional(),
|
|
605
|
+
allowedModels: z.array(z.string().min(1).max(256)).max(256).nullable().optional(),
|
|
606
|
+
});
|
|
607
|
+
export type UpdateWorkspaceModelPolicyRequest = z.infer<typeof UpdateWorkspaceModelPolicyRequest>;
|
|
608
|
+
|
|
533
609
|
export const AccountGrant = z.object({
|
|
534
610
|
accountId: z.string().uuid(),
|
|
535
611
|
subjectId: z.string().min(1),
|
|
@@ -570,17 +646,32 @@ export const DelegatedAccessTokenPayload = z.object({
|
|
|
570
646
|
// Worker-asserted session scope for first-party MCP calls (HMAC-signed, not
|
|
571
647
|
// agent-controlled); enables session-scoped tools such as goal management.
|
|
572
648
|
sessionId: z.string().uuid().optional(),
|
|
649
|
+
// The turn making the call (the caller's identity), HMAC-signed by the worker
|
|
650
|
+
// at turn setup. Lets a tool classify WHO is calling from the token itself,
|
|
651
|
+
// instead of racily re-reading the session's live active_turn_id — e.g. the
|
|
652
|
+
// sacred-pause guard must know if the CALLER is a machine child-notification
|
|
653
|
+
// turn, and the active pointer can flip to another turn mid-check.
|
|
654
|
+
turnId: z.string().uuid().optional(),
|
|
573
655
|
exp: z.number().int().positive(),
|
|
574
656
|
});
|
|
575
657
|
export type DelegatedAccessTokenPayload = z.infer<typeof DelegatedAccessTokenPayload>;
|
|
576
658
|
|
|
577
|
-
export async function signDelegatedAccessToken(
|
|
578
|
-
|
|
659
|
+
export async function signDelegatedAccessToken(
|
|
660
|
+
secret: string,
|
|
661
|
+
payload: DelegatedAccessTokenPayload,
|
|
662
|
+
): Promise<string> {
|
|
663
|
+
const encodedPayload = base64UrlEncode(
|
|
664
|
+
JSON.stringify(DelegatedAccessTokenPayload.parse(payload)),
|
|
665
|
+
);
|
|
579
666
|
const signature = await hmacSha256Base64Url(secret, encodedPayload);
|
|
580
667
|
return `ogd_${encodedPayload}.${signature}`;
|
|
581
668
|
}
|
|
582
669
|
|
|
583
|
-
export async function verifyDelegatedAccessToken(
|
|
670
|
+
export async function verifyDelegatedAccessToken(
|
|
671
|
+
secret: string,
|
|
672
|
+
token: string,
|
|
673
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
674
|
+
): Promise<DelegatedAccessTokenPayload | null> {
|
|
584
675
|
if (!token.startsWith("ogd_")) {
|
|
585
676
|
return null;
|
|
586
677
|
}
|
|
@@ -595,7 +686,9 @@ export async function verifyDelegatedAccessToken(secret: string, token: string,
|
|
|
595
686
|
if (!constantTimeEqual(signature, expected)) {
|
|
596
687
|
return null;
|
|
597
688
|
}
|
|
598
|
-
const payload = DelegatedAccessTokenPayload.safeParse(
|
|
689
|
+
const payload = DelegatedAccessTokenPayload.safeParse(
|
|
690
|
+
JSON.parse(base64UrlDecode(encodedPayload)),
|
|
691
|
+
);
|
|
599
692
|
if (!payload.success || payload.data.exp < nowSeconds) {
|
|
600
693
|
return null;
|
|
601
694
|
}
|
|
@@ -623,13 +716,20 @@ export const EnrollmentBearerPayload = z.object({
|
|
|
623
716
|
});
|
|
624
717
|
export type EnrollmentBearerPayload = z.infer<typeof EnrollmentBearerPayload>;
|
|
625
718
|
|
|
626
|
-
export async function signEnrollmentBearer(
|
|
719
|
+
export async function signEnrollmentBearer(
|
|
720
|
+
secret: string,
|
|
721
|
+
payload: EnrollmentBearerPayload,
|
|
722
|
+
): Promise<string> {
|
|
627
723
|
const encodedPayload = base64UrlEncode(JSON.stringify(EnrollmentBearerPayload.parse(payload)));
|
|
628
724
|
const signature = await hmacSha256Base64Url(secret, encodedPayload);
|
|
629
725
|
return `oge_${encodedPayload}.${signature}`;
|
|
630
726
|
}
|
|
631
727
|
|
|
632
|
-
export async function verifyEnrollmentBearer(
|
|
728
|
+
export async function verifyEnrollmentBearer(
|
|
729
|
+
secret: string,
|
|
730
|
+
token: string,
|
|
731
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
732
|
+
): Promise<EnrollmentBearerPayload | null> {
|
|
633
733
|
if (!token.startsWith("oge_")) {
|
|
634
734
|
return null;
|
|
635
735
|
}
|
|
@@ -679,7 +779,10 @@ export const EnrollTokenPayload = z.object({
|
|
|
679
779
|
});
|
|
680
780
|
export type EnrollTokenPayload = z.infer<typeof EnrollTokenPayload>;
|
|
681
781
|
|
|
682
|
-
export async function signEnrollToken(
|
|
782
|
+
export async function signEnrollToken(
|
|
783
|
+
secret: string,
|
|
784
|
+
payload: EnrollTokenPayload,
|
|
785
|
+
): Promise<string> {
|
|
683
786
|
const encodedPayload = base64UrlEncode(JSON.stringify(EnrollTokenPayload.parse(payload)));
|
|
684
787
|
const signature = await hmacSha256Base64Url(secret, encodedPayload);
|
|
685
788
|
return `oget_${encodedPayload}.${signature}`;
|
|
@@ -693,7 +796,11 @@ export async function signEnrollToken(secret: string, payload: EnrollTokenPayloa
|
|
|
693
796
|
* bearer fails the prefix gate; a same-secret token that lacks the typ claim fails
|
|
694
797
|
* the schema gate — both halves of the domain separation are enforced here.
|
|
695
798
|
*/
|
|
696
|
-
export async function verifyEnrollToken(
|
|
799
|
+
export async function verifyEnrollToken(
|
|
800
|
+
secret: string,
|
|
801
|
+
token: string,
|
|
802
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
803
|
+
): Promise<EnrollTokenPayload | null> {
|
|
697
804
|
if (!token.startsWith("oget_")) {
|
|
698
805
|
return null;
|
|
699
806
|
}
|
|
@@ -755,7 +862,10 @@ export const StreamTokenPayload = z.object({
|
|
|
755
862
|
});
|
|
756
863
|
export type StreamTokenPayload = z.infer<typeof StreamTokenPayload>;
|
|
757
864
|
|
|
758
|
-
export async function signStreamToken(
|
|
865
|
+
export async function signStreamToken(
|
|
866
|
+
secret: string,
|
|
867
|
+
payload: StreamTokenPayload,
|
|
868
|
+
): Promise<string> {
|
|
759
869
|
const encodedPayload = base64UrlEncode(JSON.stringify(StreamTokenPayload.parse(payload)));
|
|
760
870
|
const signature = await hmacSha256Base64Url(secret, encodedPayload);
|
|
761
871
|
return `ogs_${encodedPayload}.${signature}`;
|
|
@@ -771,7 +881,11 @@ export async function signStreamToken(secret: string, payload: StreamTokenPayloa
|
|
|
771
881
|
* lease + route params — verify proves the token is authentic + unexpired, the
|
|
772
882
|
* caller proves it is for THIS box's current epoch and THIS workspace+session.
|
|
773
883
|
*/
|
|
774
|
-
export async function verifyStreamToken(
|
|
884
|
+
export async function verifyStreamToken(
|
|
885
|
+
secret: string,
|
|
886
|
+
token: string,
|
|
887
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
888
|
+
): Promise<StreamTokenPayload | null> {
|
|
775
889
|
if (!token.startsWith("ogs_")) {
|
|
776
890
|
return null;
|
|
777
891
|
}
|
|
@@ -848,7 +962,11 @@ export async function signRelayToken(secret: string, payload: RelayTokenPayload)
|
|
|
848
962
|
* The channel-key scope (claim.workspaceId/agentId vs the StreamOpen channel key)
|
|
849
963
|
* is enforced by the relay at USE — verify proves authenticity + freshness only.
|
|
850
964
|
*/
|
|
851
|
-
export async function verifyRelayToken(
|
|
965
|
+
export async function verifyRelayToken(
|
|
966
|
+
secret: string,
|
|
967
|
+
token: string,
|
|
968
|
+
nowSeconds = Math.floor(Date.now() / 1000),
|
|
969
|
+
): Promise<RelayTokenPayload | null> {
|
|
852
970
|
if (!token.startsWith("ogr_")) {
|
|
853
971
|
return null;
|
|
854
972
|
}
|
|
@@ -1054,7 +1172,12 @@ export type LimitDecision = z.infer<typeof LimitDecision>;
|
|
|
1054
1172
|
// the admitted quantity so a PULL host can grant a partial allowance.
|
|
1055
1173
|
export const EntitlementDecision = z.discriminatedUnion("allowed", [
|
|
1056
1174
|
z.object({ allowed: z.literal(true), quantity: z.number().optional() }),
|
|
1057
|
-
z.object({
|
|
1175
|
+
z.object({
|
|
1176
|
+
allowed: z.literal(false),
|
|
1177
|
+
reason: z.string(),
|
|
1178
|
+
code: z.string().optional(),
|
|
1179
|
+
quantity: z.number().optional(),
|
|
1180
|
+
}),
|
|
1058
1181
|
]);
|
|
1059
1182
|
export type EntitlementDecision = z.infer<typeof EntitlementDecision>;
|
|
1060
1183
|
|
|
@@ -1069,16 +1192,32 @@ export type EntitlementsPort = {
|
|
|
1069
1192
|
admitRun(input: AdmitRunInput): Promise<EntitlementDecision>;
|
|
1070
1193
|
};
|
|
1071
1194
|
|
|
1195
|
+
export const GitCredentialProvider = z.enum(["github", "gitlab", "azure_devops"]);
|
|
1196
|
+
export type GitCredentialProvider = z.infer<typeof GitCredentialProvider>;
|
|
1197
|
+
|
|
1198
|
+
const GitProviderRepositoryId = z.union([z.number().int().positive(), z.string().min(1)]);
|
|
1199
|
+
|
|
1200
|
+
export const GitCredentialRepositoryRef = z.object({
|
|
1201
|
+
provider: GitCredentialProvider.optional(),
|
|
1202
|
+
uri: z.string().min(1),
|
|
1203
|
+
ref: z.string().min(1),
|
|
1204
|
+
repositoryId: GitProviderRepositoryId.optional(),
|
|
1205
|
+
installationId: GitProviderRepositoryId.optional(),
|
|
1206
|
+
projectId: GitProviderRepositoryId.optional(),
|
|
1207
|
+
connectionId: z.string().min(1).optional(),
|
|
1208
|
+
});
|
|
1209
|
+
export type GitCredentialRepositoryRef = z.infer<typeof GitCredentialRepositoryRef>;
|
|
1210
|
+
|
|
1072
1211
|
// ============ P4a — Connection-credential provider (§7.6) ============
|
|
1073
1212
|
//
|
|
1074
1213
|
// The host-providable per-run credential-mint seam over OpenGeni's TWO
|
|
1075
1214
|
// run-scoped credential sites in the worker:
|
|
1076
|
-
// - GIT credentials:
|
|
1077
|
-
// `sandboxEnvironmentForRun` (
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
// - SANDBOX secrets: the decrypted
|
|
1081
|
-
// `
|
|
1215
|
+
// - GIT credentials: run-scoped provider tokens minted in
|
|
1216
|
+
// `sandboxEnvironmentForRun` (standalone self-mints GitHub App tokens from
|
|
1217
|
+
// `settings`; embedded hosts can broker GitHub, GitLab, and Azure DevOps)
|
|
1218
|
+
// and seeded off-manifest into sandbox token files for git + provider CLIs.
|
|
1219
|
+
// - SANDBOX secrets: the decrypted variable set values loaded in
|
|
1220
|
+
// `loadVariableSetForRun` (today decrypted with
|
|
1082
1221
|
// `environmentsEncryptionKeyBytes(settings)`).
|
|
1083
1222
|
//
|
|
1084
1223
|
// In embedded/separate topologies the HOST owns these external connections
|
|
@@ -1090,28 +1229,41 @@ export type EntitlementsPort = {
|
|
|
1090
1229
|
// FORK-7 CROSS-CHECK (the host-mapping safety guardrail): a credential
|
|
1091
1230
|
// provider returns the `workspaceId` it scoped the credential to, and the
|
|
1092
1231
|
// activity ASSERTS it agrees with the run's workspace BEFORE injecting
|
|
1093
|
-
//
|
|
1232
|
+
// any git provider token seed (or applying decrypted environment values). A host mapping bug that
|
|
1094
1233
|
// returns tenant B's creds while the run is tenant A is thereby caught at the
|
|
1095
1234
|
// seam, never silently injected into tenant A's sandbox.
|
|
1096
1235
|
|
|
1097
1236
|
export type GitCredentialsRequest = {
|
|
1098
1237
|
accountId: string;
|
|
1099
1238
|
workspaceId: string;
|
|
1100
|
-
//
|
|
1101
|
-
//
|
|
1102
|
-
//
|
|
1239
|
+
// Provider defaults to "github" for the legacy request shape. GitHub-only
|
|
1240
|
+
// hosts can keep reading installationId/repositoryIds exactly as before;
|
|
1241
|
+
// provider-aware hosts should branch on this and repositoryRefs.
|
|
1242
|
+
provider?: GitCredentialProvider;
|
|
1243
|
+
// Token requests are the existing behavior. Identity requests let lazy
|
|
1244
|
+
// sandbox provisioning resolve stable git author/committer identity before
|
|
1245
|
+
// the box exists while deferring the rotating token value to first provision.
|
|
1246
|
+
purpose?: "token" | "identity";
|
|
1247
|
+
// Provider-neutral repository refs for hosts that broker non-GitHub tokens.
|
|
1248
|
+
// For GitHub requests these are additive to the legacy fields below.
|
|
1249
|
+
repositoryRefs?: GitCredentialRepositoryRef[];
|
|
1250
|
+
// Legacy GitHub App installation shape retained for 0.x compatibility.
|
|
1103
1251
|
installationId: number;
|
|
1104
1252
|
repositoryIds: number[];
|
|
1105
1253
|
};
|
|
1106
1254
|
|
|
1107
1255
|
export type GitCredentials = {
|
|
1108
|
-
// The minted
|
|
1109
|
-
//
|
|
1110
|
-
//
|
|
1111
|
-
token
|
|
1256
|
+
// The minted provider token. Required for purpose="token"; optional for
|
|
1257
|
+
// purpose="identity" so hosts can return only stable git identity before lazy
|
|
1258
|
+
// sandbox provision. The value never enters the manifest.
|
|
1259
|
+
token?: string;
|
|
1112
1260
|
// FORK-7 echo: the workspace the provider scoped this token to. The activity
|
|
1113
1261
|
// asserts `workspaceId === request.workspaceId` before injecting.
|
|
1114
1262
|
workspaceId: string;
|
|
1263
|
+
// Optional provider expiry for host-managed proactive renewal. ISO-8601;
|
|
1264
|
+
// null/omitted means the host does not expose a deadline and OpenGeni uses
|
|
1265
|
+
// its conservative bounded refresh cadence instead.
|
|
1266
|
+
expiresAt?: string | null;
|
|
1115
1267
|
// Optional git identity override. When omitted the activity falls back to
|
|
1116
1268
|
// today's `githubAppBotIdentity(settings)`.
|
|
1117
1269
|
identity?: { name: string; email: string } | null;
|
|
@@ -1120,20 +1272,20 @@ export type GitCredentials = {
|
|
|
1120
1272
|
export type SandboxSecretsRequest = {
|
|
1121
1273
|
accountId: string;
|
|
1122
1274
|
workspaceId: string;
|
|
1123
|
-
// The
|
|
1275
|
+
// The variable set the run's session declares (null = unattached;
|
|
1124
1276
|
// the provider, like the self-mint path, returns null values for it).
|
|
1125
|
-
|
|
1277
|
+
variableSetId: string;
|
|
1126
1278
|
};
|
|
1127
1279
|
|
|
1128
1280
|
export type SandboxSecrets = {
|
|
1129
|
-
// The decrypted
|
|
1281
|
+
// The decrypted variableSet values the run injects, replacing the local
|
|
1130
1282
|
// `environmentsEncryptionKeyBytes` decrypt. Same shape the self-mint path
|
|
1131
1283
|
// produces (plaintext name→value).
|
|
1132
1284
|
values: Record<string, string>;
|
|
1133
1285
|
// FORK-7 echo: the workspace the provider scoped these secrets to.
|
|
1134
1286
|
workspaceId: string;
|
|
1135
|
-
// Optional
|
|
1136
|
-
//
|
|
1287
|
+
// Optional variableSet metadata; when omitted the activity uses the
|
|
1288
|
+
// variableSetId as both id and name (the local decrypt carries the row's
|
|
1137
1289
|
// id/name/description, but only `id` is load-bearing downstream).
|
|
1138
1290
|
id?: string;
|
|
1139
1291
|
name?: string;
|
|
@@ -1144,8 +1296,8 @@ export type ConnectionCredentialsPort = {
|
|
|
1144
1296
|
// Both legs are optional: a host may drive ONLY git creds (BYO-GitHub-App)
|
|
1145
1297
|
// and leave sandbox secrets to OpenGeni's local decrypt, or vice-versa. An
|
|
1146
1298
|
// unset leg falls through to today's self-mint for THAT leg only.
|
|
1147
|
-
gitCredentials
|
|
1148
|
-
sandboxSecrets
|
|
1299
|
+
gitCredentials?(input: GitCredentialsRequest): Promise<GitCredentials>;
|
|
1300
|
+
sandboxSecrets?(input: SandboxSecretsRequest): Promise<SandboxSecrets>;
|
|
1149
1301
|
};
|
|
1150
1302
|
|
|
1151
1303
|
// ============ P4a — GitHub App API port (BYO-App, §7.6 / SPIKE-2 remainder) ===
|
|
@@ -1177,9 +1329,7 @@ export type GitHubAppApiPort = {
|
|
|
1177
1329
|
code: string;
|
|
1178
1330
|
installationId: number;
|
|
1179
1331
|
}) => Promise<GitHubInstallationSummary>;
|
|
1180
|
-
listRepositories?: (input: {
|
|
1181
|
-
installationIds?: number[];
|
|
1182
|
-
}) => Promise<GitHubRepository[]>;
|
|
1332
|
+
listRepositories?: (input: { installationIds?: number[] }) => Promise<GitHubRepository[]>;
|
|
1183
1333
|
};
|
|
1184
1334
|
|
|
1185
1335
|
export const BillingBalance = z.object({
|
|
@@ -1192,10 +1342,14 @@ export type BillingBalance = z.infer<typeof BillingBalance>;
|
|
|
1192
1342
|
|
|
1193
1343
|
export const CreateCheckoutRequest = z.object({
|
|
1194
1344
|
accountId: z.string().uuid().optional(),
|
|
1195
|
-
amountUsd: z
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1345
|
+
amountUsd: z
|
|
1346
|
+
.number()
|
|
1347
|
+
.min(5)
|
|
1348
|
+
.max(10_000)
|
|
1349
|
+
.refine(
|
|
1350
|
+
(value) => Number.isFinite(value) && Math.abs(value - Math.round(value * 100) / 100) < 1e-9,
|
|
1351
|
+
{ message: "amountUsd must use cent precision" },
|
|
1352
|
+
),
|
|
1199
1353
|
successUrl: z.string().url().optional(),
|
|
1200
1354
|
cancelUrl: z.string().url().optional(),
|
|
1201
1355
|
});
|
|
@@ -1213,6 +1367,11 @@ export const RepositoryResourceRef = z.object({
|
|
|
1213
1367
|
ref: z.string().min(1),
|
|
1214
1368
|
mountPath: z.string().min(1).optional(),
|
|
1215
1369
|
subpath: z.string().min(1).optional(),
|
|
1370
|
+
provider: GitCredentialProvider.optional(),
|
|
1371
|
+
repositoryId: GitProviderRepositoryId.optional(),
|
|
1372
|
+
installationId: GitProviderRepositoryId.optional(),
|
|
1373
|
+
projectId: GitProviderRepositoryId.optional(),
|
|
1374
|
+
connectionId: z.string().min(1).optional(),
|
|
1216
1375
|
githubInstallationId: z.number().int().positive().optional(),
|
|
1217
1376
|
githubRepositoryId: z.number().int().positive().optional(),
|
|
1218
1377
|
});
|
|
@@ -1231,7 +1390,13 @@ export type ResourceRef = z.infer<typeof ResourceRef>;
|
|
|
1231
1390
|
export const FileStatus = z.enum(["pending_upload", "ready", "failed", "expired", "deleted"]);
|
|
1232
1391
|
export type FileStatus = z.infer<typeof FileStatus>;
|
|
1233
1392
|
|
|
1234
|
-
export const FileUploadStatus = z.enum([
|
|
1393
|
+
export const FileUploadStatus = z.enum([
|
|
1394
|
+
"pending",
|
|
1395
|
+
"cleanup_pending",
|
|
1396
|
+
"completed",
|
|
1397
|
+
"expired",
|
|
1398
|
+
"failed",
|
|
1399
|
+
]);
|
|
1235
1400
|
export type FileUploadStatus = z.infer<typeof FileUploadStatus>;
|
|
1236
1401
|
|
|
1237
1402
|
export const FileAsset = z.object({
|
|
@@ -1282,6 +1447,21 @@ export type FileDownloadUrlResponse = z.infer<typeof FileDownloadUrlResponse>;
|
|
|
1282
1447
|
export const DocumentStatus = z.enum(["queued", "indexing", "ready", "failed"]);
|
|
1283
1448
|
export type DocumentStatus = z.infer<typeof DocumentStatus>;
|
|
1284
1449
|
|
|
1450
|
+
export const KnowledgeSourceKind = z.enum([
|
|
1451
|
+
"manual_upload",
|
|
1452
|
+
"meeting_transcript",
|
|
1453
|
+
"repository",
|
|
1454
|
+
"email",
|
|
1455
|
+
"chat",
|
|
1456
|
+
"document",
|
|
1457
|
+
"web",
|
|
1458
|
+
"other",
|
|
1459
|
+
]);
|
|
1460
|
+
export type KnowledgeSourceKind = z.infer<typeof KnowledgeSourceKind>;
|
|
1461
|
+
|
|
1462
|
+
export const DocumentSearchMode = z.enum(["hybrid", "vector", "keyword"]);
|
|
1463
|
+
export type DocumentSearchMode = z.infer<typeof DocumentSearchMode>;
|
|
1464
|
+
|
|
1285
1465
|
export const DocumentBase = z.object({
|
|
1286
1466
|
id: z.string().uuid(),
|
|
1287
1467
|
workspaceId: z.string().uuid(),
|
|
@@ -1302,6 +1482,15 @@ export const Document = z.object({
|
|
|
1302
1482
|
parser: z.string(),
|
|
1303
1483
|
chunkCount: z.number().int().nonnegative(),
|
|
1304
1484
|
error: z.string().nullable(),
|
|
1485
|
+
sourceKind: KnowledgeSourceKind,
|
|
1486
|
+
sourceUri: z.string().nullable(),
|
|
1487
|
+
sourceExternalId: z.string().nullable(),
|
|
1488
|
+
sourceTitle: z.string().nullable(),
|
|
1489
|
+
sourceAuthor: z.string().nullable(),
|
|
1490
|
+
sourceCreatedAt: z.string().nullable(),
|
|
1491
|
+
sourceUpdatedAt: z.string().nullable(),
|
|
1492
|
+
sourceVersion: z.string().nullable(),
|
|
1493
|
+
aclTags: z.array(z.string()),
|
|
1305
1494
|
createdAt: z.string(),
|
|
1306
1495
|
updatedAt: z.string(),
|
|
1307
1496
|
});
|
|
@@ -1316,8 +1505,20 @@ export const DocumentSearchResult = z.object({
|
|
|
1316
1505
|
title: z.string(),
|
|
1317
1506
|
text: z.string(),
|
|
1318
1507
|
score: z.number(),
|
|
1508
|
+
matchType: DocumentSearchMode,
|
|
1509
|
+
vectorScore: z.number().nullable(),
|
|
1510
|
+
keywordScore: z.number().nullable(),
|
|
1319
1511
|
chunkIndex: z.number().int().nonnegative(),
|
|
1320
1512
|
metadata: z.record(z.string(), z.unknown()),
|
|
1513
|
+
sourceKind: KnowledgeSourceKind,
|
|
1514
|
+
sourceUri: z.string().nullable(),
|
|
1515
|
+
sourceExternalId: z.string().nullable(),
|
|
1516
|
+
sourceTitle: z.string().nullable(),
|
|
1517
|
+
sourceAuthor: z.string().nullable(),
|
|
1518
|
+
sourceCreatedAt: z.string().nullable(),
|
|
1519
|
+
sourceUpdatedAt: z.string().nullable(),
|
|
1520
|
+
sourceVersion: z.string().nullable(),
|
|
1521
|
+
aclTags: z.array(z.string()),
|
|
1321
1522
|
});
|
|
1322
1523
|
export type DocumentSearchResult = z.infer<typeof DocumentSearchResult>;
|
|
1323
1524
|
|
|
@@ -1329,15 +1530,160 @@ export type CreateDocumentBaseRequest = z.infer<typeof CreateDocumentBaseRequest
|
|
|
1329
1530
|
|
|
1330
1531
|
export const AddDocumentRequest = z.object({
|
|
1331
1532
|
fileId: z.string().uuid(),
|
|
1533
|
+
title: z.string().min(1).optional(),
|
|
1534
|
+
sourceKind: KnowledgeSourceKind.optional(),
|
|
1535
|
+
sourceUri: z.string().min(1).optional(),
|
|
1536
|
+
sourceExternalId: z.string().min(1).optional(),
|
|
1537
|
+
sourceTitle: z.string().min(1).optional(),
|
|
1538
|
+
sourceAuthor: z.string().min(1).optional(),
|
|
1539
|
+
sourceCreatedAt: z.string().datetime({ offset: true }).optional(),
|
|
1540
|
+
sourceUpdatedAt: z.string().datetime({ offset: true }).optional(),
|
|
1541
|
+
sourceVersion: z.string().min(1).optional(),
|
|
1542
|
+
aclTags: z.array(z.string().min(1)).optional(),
|
|
1332
1543
|
});
|
|
1333
1544
|
export type AddDocumentRequest = z.infer<typeof AddDocumentRequest>;
|
|
1334
1545
|
|
|
1335
1546
|
export const DocumentSearchRequest = z.object({
|
|
1336
1547
|
query: z.string().min(1),
|
|
1337
|
-
|
|
1548
|
+
baseIds: z.array(z.string().uuid()).optional(),
|
|
1549
|
+
mode: DocumentSearchMode.optional(),
|
|
1550
|
+
sourceKinds: z.array(KnowledgeSourceKind).optional(),
|
|
1551
|
+
aclTags: z.array(z.string().min(1)).optional(),
|
|
1552
|
+
limit: z.number().int().positive().max(50).default(5),
|
|
1338
1553
|
});
|
|
1339
1554
|
export type DocumentSearchRequest = z.infer<typeof DocumentSearchRequest>;
|
|
1340
1555
|
|
|
1556
|
+
// proposed/approved/rejected are the legacy curated-knowledge review states
|
|
1557
|
+
// (docs-MCP memory_propose lane). active/superseded/archived are Workspace
|
|
1558
|
+
// Memory V1: agent-written memories land `active` (usable immediately — human is
|
|
1559
|
+
// auditor, not gatekeeper), get `superseded` when replaced, `archived` when
|
|
1560
|
+
// retired. Agent-visible set = active ∪ approved.
|
|
1561
|
+
export const KnowledgeMemoryStatus = z.enum([
|
|
1562
|
+
"proposed",
|
|
1563
|
+
"approved",
|
|
1564
|
+
"rejected",
|
|
1565
|
+
"active",
|
|
1566
|
+
"superseded",
|
|
1567
|
+
"archived",
|
|
1568
|
+
]);
|
|
1569
|
+
export type KnowledgeMemoryStatus = z.infer<typeof KnowledgeMemoryStatus>;
|
|
1570
|
+
|
|
1571
|
+
export const KnowledgeMemoryKind = z.enum([
|
|
1572
|
+
"semantic",
|
|
1573
|
+
"episodic",
|
|
1574
|
+
"procedural",
|
|
1575
|
+
"decision",
|
|
1576
|
+
"preference",
|
|
1577
|
+
]);
|
|
1578
|
+
export type KnowledgeMemoryKind = z.infer<typeof KnowledgeMemoryKind>;
|
|
1579
|
+
|
|
1580
|
+
export const KnowledgeSourceRef = z.object({
|
|
1581
|
+
kind: z.enum(["document_chunk", "document", "session_event", "memory", "external"]),
|
|
1582
|
+
id: z.string().min(1),
|
|
1583
|
+
uri: z.string().min(1).optional(),
|
|
1584
|
+
title: z.string().min(1).optional(),
|
|
1585
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
1586
|
+
});
|
|
1587
|
+
export type KnowledgeSourceRef = z.infer<typeof KnowledgeSourceRef>;
|
|
1588
|
+
|
|
1589
|
+
export const KnowledgeMemory = z.object({
|
|
1590
|
+
id: z.string().uuid(),
|
|
1591
|
+
workspaceId: z.string().uuid(),
|
|
1592
|
+
status: KnowledgeMemoryStatus,
|
|
1593
|
+
kind: KnowledgeMemoryKind,
|
|
1594
|
+
scope: z.string(),
|
|
1595
|
+
text: z.string(),
|
|
1596
|
+
sourceRefs: z.array(KnowledgeSourceRef),
|
|
1597
|
+
confidence: z.number().min(0).max(1),
|
|
1598
|
+
metadata: z.record(z.string(), z.unknown()),
|
|
1599
|
+
createdBySessionId: z.string().uuid().nullable(),
|
|
1600
|
+
reviewedBy: z.string().nullable(),
|
|
1601
|
+
reviewedAt: z.string().nullable(),
|
|
1602
|
+
// Workspace Memory V1 fields. usageCount/lastUsedAt feed end-state ranking and
|
|
1603
|
+
// decay; supersedesId/supersededById link correction chains; validFrom/validUntil
|
|
1604
|
+
// are the point-in-time window. embedding/embeddingModel/textHash are internal
|
|
1605
|
+
// and never exposed on the wire.
|
|
1606
|
+
pinned: z.boolean(),
|
|
1607
|
+
usageCount: z.number().int(),
|
|
1608
|
+
lastUsedAt: z.string().nullable(),
|
|
1609
|
+
supersedesId: z.string().uuid().nullable(),
|
|
1610
|
+
supersededById: z.string().uuid().nullable(),
|
|
1611
|
+
validFrom: z.string(),
|
|
1612
|
+
validUntil: z.string().nullable(),
|
|
1613
|
+
createdAt: z.string(),
|
|
1614
|
+
updatedAt: z.string(),
|
|
1615
|
+
});
|
|
1616
|
+
export type KnowledgeMemory = z.infer<typeof KnowledgeMemory>;
|
|
1617
|
+
|
|
1618
|
+
// Default status is `active`: a create through this request lands an
|
|
1619
|
+
// agent-visible memory via the one write gate (saveWorkspaceMemory). Passing an
|
|
1620
|
+
// explicit `proposed`/`approved`/`rejected` status routes to the legacy curated
|
|
1621
|
+
// create instead (the docs-MCP memory_propose lane). pinned/replacesId apply to
|
|
1622
|
+
// the active (memory) path.
|
|
1623
|
+
export const CreateKnowledgeMemoryRequest = z.object({
|
|
1624
|
+
status: KnowledgeMemoryStatus.default("active"),
|
|
1625
|
+
kind: KnowledgeMemoryKind.default("semantic"),
|
|
1626
|
+
scope: z.string().min(1).default("workspace"),
|
|
1627
|
+
text: z.string().min(1),
|
|
1628
|
+
sourceRefs: z.array(KnowledgeSourceRef).default([]),
|
|
1629
|
+
confidence: z.number().min(0).max(1).default(0.5),
|
|
1630
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
1631
|
+
createdBySessionId: z.string().uuid().optional(),
|
|
1632
|
+
pinned: z.boolean().optional(),
|
|
1633
|
+
replacesId: z.string().min(1).optional(),
|
|
1634
|
+
});
|
|
1635
|
+
export type CreateKnowledgeMemoryRequest = z.infer<typeof CreateKnowledgeMemoryRequest>;
|
|
1636
|
+
|
|
1637
|
+
export const UpdateKnowledgeMemoryRequest = z.object({
|
|
1638
|
+
status: KnowledgeMemoryStatus.optional(),
|
|
1639
|
+
kind: KnowledgeMemoryKind.optional(),
|
|
1640
|
+
scope: z.string().min(1).optional(),
|
|
1641
|
+
text: z.string().min(1).optional(),
|
|
1642
|
+
sourceRefs: z.array(KnowledgeSourceRef).optional(),
|
|
1643
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
1644
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
1645
|
+
reviewedBy: z.string().min(1).optional(),
|
|
1646
|
+
// Human audit action: pin (never decays) / unpin.
|
|
1647
|
+
pinned: z.boolean().optional(),
|
|
1648
|
+
});
|
|
1649
|
+
export type UpdateKnowledgeMemoryRequest = z.infer<typeof UpdateKnowledgeMemoryRequest>;
|
|
1650
|
+
|
|
1651
|
+
// GET list/filter over knowledge memories (curated + memory).
|
|
1652
|
+
export const KnowledgeMemorySearchRequest = z.object({
|
|
1653
|
+
query: z.string().min(1).optional(),
|
|
1654
|
+
status: KnowledgeMemoryStatus.optional(),
|
|
1655
|
+
kind: KnowledgeMemoryKind.optional(),
|
|
1656
|
+
scope: z.string().min(1).optional(),
|
|
1657
|
+
limit: z.number().int().positive().max(100).default(20),
|
|
1658
|
+
});
|
|
1659
|
+
export type KnowledgeMemorySearchRequest = z.infer<typeof KnowledgeMemorySearchRequest>;
|
|
1660
|
+
|
|
1661
|
+
export const WorkspaceMemorySearchMode = z.enum(["hybrid", "vector", "keyword"]);
|
|
1662
|
+
export type WorkspaceMemorySearchMode = z.infer<typeof WorkspaceMemorySearchMode>;
|
|
1663
|
+
|
|
1664
|
+
// POST hybrid search over the workspace's agent-visible memory (active ∪ approved).
|
|
1665
|
+
export const WorkspaceMemorySearchRequest = z.object({
|
|
1666
|
+
query: z.string().min(1),
|
|
1667
|
+
kind: KnowledgeMemoryKind.optional(),
|
|
1668
|
+
limit: z.number().int().positive().max(20).optional(),
|
|
1669
|
+
mode: WorkspaceMemorySearchMode.optional(),
|
|
1670
|
+
});
|
|
1671
|
+
export type WorkspaceMemorySearchRequest = z.infer<typeof WorkspaceMemorySearchRequest>;
|
|
1672
|
+
|
|
1673
|
+
export const WorkspaceMemorySearchResult = z.object({
|
|
1674
|
+
memory: KnowledgeMemory,
|
|
1675
|
+
score: z.number(),
|
|
1676
|
+
matchType: WorkspaceMemorySearchMode,
|
|
1677
|
+
vectorScore: z.number().nullable(),
|
|
1678
|
+
keywordScore: z.number().nullable(),
|
|
1679
|
+
});
|
|
1680
|
+
export type WorkspaceMemorySearchResult = z.infer<typeof WorkspaceMemorySearchResult>;
|
|
1681
|
+
|
|
1682
|
+
export const WorkspaceMemorySearchResponse = z.object({
|
|
1683
|
+
results: z.array(WorkspaceMemorySearchResult),
|
|
1684
|
+
});
|
|
1685
|
+
export type WorkspaceMemorySearchResponse = z.infer<typeof WorkspaceMemorySearchResponse>;
|
|
1686
|
+
|
|
1341
1687
|
export const ToolRef = z.object({
|
|
1342
1688
|
kind: z.literal("mcp"),
|
|
1343
1689
|
id: z.string().min(1),
|
|
@@ -1352,13 +1698,19 @@ export const ToolRef = z.object({
|
|
|
1352
1698
|
export type ToolRef = z.infer<typeof ToolRef>;
|
|
1353
1699
|
|
|
1354
1700
|
const registryId = /^[A-Za-z0-9_-]+$/;
|
|
1355
|
-
const httpsUrl = z
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1701
|
+
const httpsUrl = z
|
|
1702
|
+
.string()
|
|
1703
|
+
.url()
|
|
1704
|
+
.refine(
|
|
1705
|
+
(value) => {
|
|
1706
|
+
try {
|
|
1707
|
+
return new URL(value).protocol === "https:";
|
|
1708
|
+
} catch {
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
},
|
|
1712
|
+
{ message: "URL must use https" },
|
|
1713
|
+
);
|
|
1362
1714
|
|
|
1363
1715
|
export const SessionMcpServerInput = z.object({
|
|
1364
1716
|
id: z.string().min(1).regex(registryId),
|
|
@@ -1367,6 +1719,12 @@ export const SessionMcpServerInput = z.object({
|
|
|
1367
1719
|
allowedTools: z.array(z.string().min(1)).optional(),
|
|
1368
1720
|
timeoutMs: z.number().int().positive().optional(),
|
|
1369
1721
|
cacheToolsList: z.boolean().optional(),
|
|
1722
|
+
// Human-approval policy for this server's tools. `true` = every tool of this
|
|
1723
|
+
// server requires approval before it runs (a `session.requiresAction` pause
|
|
1724
|
+
// the caller resolves with `user.approvalDecision`); a string[] = ONLY the
|
|
1725
|
+
// listed UNPREFIXED tool names require approval (e.g. reads auto-run, writes
|
|
1726
|
+
// ask); absent / `false` = auto-run everything (the historical default).
|
|
1727
|
+
requireApproval: z.union([z.boolean(), z.array(z.string().min(1))]).optional(),
|
|
1370
1728
|
// Write-only credential headers. Values are encrypted at rest and never
|
|
1371
1729
|
// returned in session responses or events; response metadata exposes names.
|
|
1372
1730
|
headers: z.record(z.string(), z.string()).optional(),
|
|
@@ -1379,13 +1737,15 @@ export const SessionMcpCredentialUpdateInput = z.object({
|
|
|
1379
1737
|
});
|
|
1380
1738
|
export type SessionMcpCredentialUpdateInput = z.infer<typeof SessionMcpCredentialUpdateInput>;
|
|
1381
1739
|
|
|
1382
|
-
export const SessionMcpServerMetadata = z
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1740
|
+
export const SessionMcpServerMetadata = z
|
|
1741
|
+
.object({
|
|
1742
|
+
id: z.string().min(1).regex(registryId),
|
|
1743
|
+
name: z.string().min(1).nullable(),
|
|
1744
|
+
url: httpsUrl,
|
|
1745
|
+
headerNames: z.array(z.string()).default([]),
|
|
1746
|
+
credentialVersion: z.number().int().positive(),
|
|
1747
|
+
})
|
|
1748
|
+
.strict();
|
|
1389
1749
|
export type SessionMcpServerMetadata = z.infer<typeof SessionMcpServerMetadata>;
|
|
1390
1750
|
|
|
1391
1751
|
export class ResourceRefConflictError extends Error {
|
|
@@ -1424,8 +1784,14 @@ export function mergeResourceRefs(
|
|
|
1424
1784
|
options: { rejectConflicts?: boolean } = {},
|
|
1425
1785
|
): ResourceRef[] {
|
|
1426
1786
|
const out = [...existing];
|
|
1427
|
-
const mountPaths = new Map(
|
|
1428
|
-
|
|
1787
|
+
const mountPaths = new Map(
|
|
1788
|
+
existing.flatMap((resource) =>
|
|
1789
|
+
resource.mountPath ? [[resource.mountPath, stableJson(resource)] as const] : [],
|
|
1790
|
+
),
|
|
1791
|
+
);
|
|
1792
|
+
const identities = new Map(
|
|
1793
|
+
existing.map((resource) => [resourceIdentityKey(resource), stableJson(resource)] as const),
|
|
1794
|
+
);
|
|
1429
1795
|
const exact = new Set(existing.map(stableJson));
|
|
1430
1796
|
|
|
1431
1797
|
for (const resource of additions) {
|
|
@@ -1436,12 +1802,16 @@ export function mergeResourceRefs(
|
|
|
1436
1802
|
if (options.rejectConflicts) {
|
|
1437
1803
|
const existingAtMount = resource.mountPath ? mountPaths.get(resource.mountPath) : undefined;
|
|
1438
1804
|
if (existingAtMount && existingAtMount !== serialized) {
|
|
1439
|
-
throw new ResourceRefConflictError(
|
|
1805
|
+
throw new ResourceRefConflictError(
|
|
1806
|
+
`resource mount path is already attached: ${resource.mountPath}`,
|
|
1807
|
+
);
|
|
1440
1808
|
}
|
|
1441
1809
|
const identity = resourceIdentityKey(resource);
|
|
1442
1810
|
const existingIdentity = identities.get(identity);
|
|
1443
1811
|
if (existingIdentity && existingIdentity !== serialized) {
|
|
1444
|
-
throw new ResourceRefConflictError(
|
|
1812
|
+
throw new ResourceRefConflictError(
|
|
1813
|
+
`resource is already attached with different settings: ${identity}`,
|
|
1814
|
+
);
|
|
1445
1815
|
}
|
|
1446
1816
|
}
|
|
1447
1817
|
out.push(resource);
|
|
@@ -1454,9 +1824,17 @@ export function mergeResourceRefs(
|
|
|
1454
1824
|
return out;
|
|
1455
1825
|
}
|
|
1456
1826
|
|
|
1457
|
-
export function reasoningEffortForMetadata(
|
|
1827
|
+
export function reasoningEffortForMetadata(
|
|
1828
|
+
metadata: Record<string, unknown>,
|
|
1829
|
+
fallback: ReasoningEffort,
|
|
1830
|
+
): ReasoningEffort {
|
|
1458
1831
|
const value = metadata.reasoningEffort;
|
|
1459
|
-
return value === "none" ||
|
|
1832
|
+
return value === "none" ||
|
|
1833
|
+
value === "minimal" ||
|
|
1834
|
+
value === "low" ||
|
|
1835
|
+
value === "medium" ||
|
|
1836
|
+
value === "high" ||
|
|
1837
|
+
value === "xhigh"
|
|
1460
1838
|
? value
|
|
1461
1839
|
: fallback;
|
|
1462
1840
|
}
|
|
@@ -1477,17 +1855,44 @@ function sortJson(value: unknown): unknown {
|
|
|
1477
1855
|
return value.map(sortJson);
|
|
1478
1856
|
}
|
|
1479
1857
|
if (value && typeof value === "object") {
|
|
1480
|
-
return Object.fromEntries(
|
|
1858
|
+
return Object.fromEntries(
|
|
1859
|
+
Object.entries(value)
|
|
1860
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
1861
|
+
.map(([key, nested]) => [key, sortJson(nested)]),
|
|
1862
|
+
);
|
|
1481
1863
|
}
|
|
1482
1864
|
return value;
|
|
1483
1865
|
}
|
|
1484
1866
|
|
|
1485
|
-
export const SessionTurnStatus = z.enum([
|
|
1867
|
+
export const SessionTurnStatus = z.enum([
|
|
1868
|
+
"queued",
|
|
1869
|
+
"running",
|
|
1870
|
+
"requires_action",
|
|
1871
|
+
"recovering",
|
|
1872
|
+
"waiting_capacity",
|
|
1873
|
+
"completed",
|
|
1874
|
+
"failed",
|
|
1875
|
+
"cancelled",
|
|
1876
|
+
"superseded",
|
|
1877
|
+
]);
|
|
1486
1878
|
export type SessionTurnStatus = z.infer<typeof SessionTurnStatus>;
|
|
1487
1879
|
|
|
1488
|
-
export const SessionTurnSource = z.enum([
|
|
1880
|
+
export const SessionTurnSource = z.enum([
|
|
1881
|
+
"user",
|
|
1882
|
+
"scheduled_task",
|
|
1883
|
+
"api",
|
|
1884
|
+
"goal",
|
|
1885
|
+
"system",
|
|
1886
|
+
"compaction",
|
|
1887
|
+
]);
|
|
1489
1888
|
export type SessionTurnSource = z.infer<typeof SessionTurnSource>;
|
|
1490
1889
|
|
|
1890
|
+
export const SessionControlState = z.enum(["active", "paused"]);
|
|
1891
|
+
export type SessionControlState = z.infer<typeof SessionControlState>;
|
|
1892
|
+
|
|
1893
|
+
export const WorkspaceInferenceState = z.enum(["active", "paused"]);
|
|
1894
|
+
export type WorkspaceInferenceState = z.infer<typeof WorkspaceInferenceState>;
|
|
1895
|
+
|
|
1491
1896
|
export const SessionGoalStatus = z.enum(["active", "paused", "completed"]);
|
|
1492
1897
|
export type SessionGoalStatus = z.infer<typeof SessionGoalStatus>;
|
|
1493
1898
|
|
|
@@ -1496,7 +1901,7 @@ export type SessionGoalCreatedBy = z.infer<typeof SessionGoalCreatedBy>;
|
|
|
1496
1901
|
|
|
1497
1902
|
export const SessionGoalPausedReason = z.enum([
|
|
1498
1903
|
"agent",
|
|
1499
|
-
"
|
|
1904
|
+
"user_pause",
|
|
1500
1905
|
"api",
|
|
1501
1906
|
"no_progress",
|
|
1502
1907
|
"max_auto_continuations",
|
|
@@ -1544,6 +1949,18 @@ export const UpdateSessionRequest = z.object({
|
|
|
1544
1949
|
});
|
|
1545
1950
|
export type UpdateSessionRequest = z.infer<typeof UpdateSessionRequest>;
|
|
1546
1951
|
|
|
1952
|
+
/**
|
|
1953
|
+
* A member's personal pin preference for a session. `expectedVersion` is
|
|
1954
|
+
* optional: ordinary pin/unpin actions are idempotent last-write-wins, while a
|
|
1955
|
+
* client that has a known version can fail closed rather than overwrite a newer
|
|
1956
|
+
* action from another browser.
|
|
1957
|
+
*/
|
|
1958
|
+
export const UpdateSessionPinRequest = z.object({
|
|
1959
|
+
pinned: z.boolean(),
|
|
1960
|
+
expectedVersion: z.number().int().nonnegative().optional(),
|
|
1961
|
+
});
|
|
1962
|
+
export type UpdateSessionPinRequest = z.infer<typeof UpdateSessionPinRequest>;
|
|
1963
|
+
|
|
1547
1964
|
// Operator context controls (slash-command palette: /clear, /compact). These
|
|
1548
1965
|
// are session/operator actions, NOT a structured way to talk to the agent —
|
|
1549
1966
|
// the human↔agent channel stays plain chat. Both require `sessions:control`.
|
|
@@ -1586,9 +2003,11 @@ export function isClearedRunStateBlob(serialized: string | null | undefined): bo
|
|
|
1586
2003
|
}
|
|
1587
2004
|
try {
|
|
1588
2005
|
const parsed = JSON.parse(serialized) as unknown;
|
|
1589
|
-
return
|
|
1590
|
-
|
|
1591
|
-
|
|
2006
|
+
return (
|
|
2007
|
+
typeof parsed === "object" &&
|
|
2008
|
+
parsed !== null &&
|
|
2009
|
+
(parsed as Record<string, unknown>)[CLEARED_RUN_STATE_MARKER] === true
|
|
2010
|
+
);
|
|
1592
2011
|
} catch {
|
|
1593
2012
|
return false;
|
|
1594
2013
|
}
|
|
@@ -1600,9 +2019,10 @@ export type CompactSessionContextRequest = z.infer<typeof CompactSessionContextR
|
|
|
1600
2019
|
|
|
1601
2020
|
/** Outcome of a manual /compact trigger. */
|
|
1602
2021
|
export const CompactSessionContextResult = z.object({
|
|
1603
|
-
//
|
|
1604
|
-
//
|
|
1605
|
-
|
|
2022
|
+
// pending: an active/paused session will compact at its next safe boundary.
|
|
2023
|
+
// completed: an idle compaction-only activity completed synchronously.
|
|
2024
|
+
// noop: there is no active history to compact.
|
|
2025
|
+
status: z.enum(["pending", "completed", "noop"]),
|
|
1606
2026
|
message: z.string(),
|
|
1607
2027
|
});
|
|
1608
2028
|
export type CompactSessionContextResult = z.infer<typeof CompactSessionContextResult>;
|
|
@@ -1615,7 +2035,7 @@ export const SessionTurn = z.object({
|
|
|
1615
2035
|
temporalWorkflowId: z.string(),
|
|
1616
2036
|
status: SessionTurnStatus,
|
|
1617
2037
|
source: SessionTurnSource,
|
|
1618
|
-
position: z.number().int()
|
|
2038
|
+
position: z.number().int(),
|
|
1619
2039
|
prompt: z.string().min(1),
|
|
1620
2040
|
resources: z.array(ResourceRef),
|
|
1621
2041
|
tools: z.array(ToolRef),
|
|
@@ -1625,6 +2045,12 @@ export const SessionTurn = z.object({
|
|
|
1625
2045
|
// Per-turn OS override. NULL = inherit the session's sandboxOs.
|
|
1626
2046
|
sandboxOs: SandboxOs.nullable(),
|
|
1627
2047
|
metadata: z.record(z.string(), z.unknown()),
|
|
2048
|
+
version: z.number().int().positive(),
|
|
2049
|
+
executionGeneration: z.number().int().nonnegative(),
|
|
2050
|
+
activeAttemptId: z.string().uuid().nullable(),
|
|
2051
|
+
lineage: z.record(z.string(), z.unknown()),
|
|
2052
|
+
cancelledBy: z.string().nullable(),
|
|
2053
|
+
cancelReason: z.string().nullable(),
|
|
1628
2054
|
startedAt: z.string().nullable(),
|
|
1629
2055
|
finishedAt: z.string().nullable(),
|
|
1630
2056
|
createdAt: z.string(),
|
|
@@ -1632,68 +2058,316 @@ export const SessionTurn = z.object({
|
|
|
1632
2058
|
});
|
|
1633
2059
|
export type SessionTurn = z.infer<typeof SessionTurn>;
|
|
1634
2060
|
|
|
1635
|
-
export const
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
2061
|
+
export const SessionQueueSnapshot = z.object({
|
|
2062
|
+
version: z.number().int().nonnegative(),
|
|
2063
|
+
controlState: SessionControlState,
|
|
2064
|
+
controlGeneration: z.number().int().nonnegative(),
|
|
2065
|
+
workspaceInferenceState: WorkspaceInferenceState,
|
|
2066
|
+
workspaceInferenceGeneration: z.number().int().nonnegative(),
|
|
2067
|
+
workspaceRunExceptionGeneration: z.number().int().nonnegative().nullable(),
|
|
2068
|
+
items: z.array(SessionTurn),
|
|
1643
2069
|
});
|
|
1644
|
-
export type
|
|
2070
|
+
export type SessionQueueSnapshot = z.infer<typeof SessionQueueSnapshot>;
|
|
1645
2071
|
|
|
1646
|
-
export const
|
|
1647
|
-
|
|
2072
|
+
export const CancelSessionQueueItemRequest = z.object({
|
|
2073
|
+
expectedQueueVersion: z.number().int().nonnegative(),
|
|
2074
|
+
expectedItemVersion: z.number().int().positive(),
|
|
2075
|
+
reason: z.string().min(1).optional(),
|
|
1648
2076
|
});
|
|
1649
|
-
export type
|
|
2077
|
+
export type CancelSessionQueueItemRequest = z.infer<typeof CancelSessionQueueItemRequest>;
|
|
1650
2078
|
|
|
1651
|
-
export const
|
|
1652
|
-
|
|
2079
|
+
export const SessionControlRequest = z.object({
|
|
2080
|
+
mode: z.enum(["pause", "resume"]),
|
|
2081
|
+
reason: z.string().min(1).optional(),
|
|
2082
|
+
clientEventId: z.string().min(1).optional(),
|
|
2083
|
+
expectedControlState: SessionControlState.optional(),
|
|
2084
|
+
expectedControlGeneration: z.number().int().nonnegative().optional(),
|
|
2085
|
+
expectedWorkspaceInferenceGeneration: z.number().int().nonnegative().optional(),
|
|
2086
|
+
});
|
|
2087
|
+
export type SessionControlRequest = z.infer<typeof SessionControlRequest>;
|
|
2088
|
+
|
|
2089
|
+
export const WorkspaceInferenceControlRequest = z.object({
|
|
2090
|
+
state: WorkspaceInferenceState,
|
|
2091
|
+
reason: z.string().min(1),
|
|
2092
|
+
clientEventId: z.string().min(1),
|
|
2093
|
+
expectedState: WorkspaceInferenceState,
|
|
2094
|
+
expectedGeneration: z.number().int().nonnegative(),
|
|
2095
|
+
exceptSessionIds: z.array(z.string().uuid()).default([]),
|
|
2096
|
+
});
|
|
2097
|
+
export type WorkspaceInferenceControlRequest = z.infer<typeof WorkspaceInferenceControlRequest>;
|
|
2098
|
+
|
|
2099
|
+
export const WorkspaceInferenceControlResponse = z.object({
|
|
2100
|
+
operationId: z.string().uuid(),
|
|
2101
|
+
state: WorkspaceInferenceState,
|
|
2102
|
+
generation: z.number().int().nonnegative(),
|
|
2103
|
+
affectedSessionIds: z.array(z.string().uuid()),
|
|
2104
|
+
controlSessionIds: z.array(z.string().uuid()),
|
|
2105
|
+
exceptionSessionIds: z.array(z.string().uuid()),
|
|
2106
|
+
});
|
|
2107
|
+
export type WorkspaceInferenceControlResponse = z.infer<typeof WorkspaceInferenceControlResponse>;
|
|
2108
|
+
|
|
2109
|
+
export const SystemUpdateClassification = z.enum(["success", "failure", "action_required", "info"]);
|
|
2110
|
+
export type SystemUpdateClassification = z.infer<typeof SystemUpdateClassification>;
|
|
2111
|
+
|
|
2112
|
+
export const SessionSystemUpdateKind = z.enum([
|
|
2113
|
+
"child_session_update",
|
|
2114
|
+
"scheduled_wake",
|
|
2115
|
+
"lifecycle_event",
|
|
2116
|
+
"runtime_notice",
|
|
2117
|
+
]);
|
|
2118
|
+
export type SessionSystemUpdateKind = z.infer<typeof SessionSystemUpdateKind>;
|
|
2119
|
+
|
|
2120
|
+
export const SessionSystemUpdateState = z.enum([
|
|
2121
|
+
"pending",
|
|
2122
|
+
"deferred",
|
|
2123
|
+
"delivered",
|
|
2124
|
+
"cancelled",
|
|
2125
|
+
"failed",
|
|
2126
|
+
]);
|
|
2127
|
+
export type SessionSystemUpdateState = z.infer<typeof SessionSystemUpdateState>;
|
|
2128
|
+
|
|
2129
|
+
export const SessionSystemUpdate = z.object({
|
|
2130
|
+
id: z.string().uuid(),
|
|
2131
|
+
sessionId: z.string().uuid(),
|
|
2132
|
+
kind: SessionSystemUpdateKind,
|
|
2133
|
+
classification: SystemUpdateClassification,
|
|
2134
|
+
sourceId: z.string(),
|
|
2135
|
+
dedupeKey: z.string(),
|
|
2136
|
+
summary: z.string(),
|
|
2137
|
+
payload: z.record(z.string(), z.unknown()),
|
|
2138
|
+
lineage: z.record(z.string(), z.unknown()),
|
|
2139
|
+
state: SessionSystemUpdateState,
|
|
2140
|
+
deliveredTurnId: z.string().uuid().nullable(),
|
|
2141
|
+
deliveredAt: z.string().nullable(),
|
|
2142
|
+
createdAt: z.string(),
|
|
2143
|
+
});
|
|
2144
|
+
export type SessionSystemUpdate = z.infer<typeof SessionSystemUpdate>;
|
|
2145
|
+
|
|
2146
|
+
export const VariableSetVariableName = z
|
|
2147
|
+
.string()
|
|
2148
|
+
.regex(/^[A-Z][A-Z0-9_]*$/)
|
|
2149
|
+
.max(128);
|
|
2150
|
+
export type VariableSetVariableName = z.infer<typeof VariableSetVariableName>;
|
|
2151
|
+
|
|
2152
|
+
function withVariableSetIdAlias<T extends z.ZodRawShape>(shape: T) {
|
|
2153
|
+
return z.preprocess((input) => {
|
|
2154
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
2155
|
+
return input;
|
|
2156
|
+
}
|
|
2157
|
+
const record = input as Record<string, unknown>;
|
|
2158
|
+
if (record.variableSetId !== undefined || record.environmentId === undefined) {
|
|
2159
|
+
return record;
|
|
2160
|
+
}
|
|
2161
|
+
return { ...record, variableSetId: record.environmentId };
|
|
2162
|
+
}, z.object(shape));
|
|
2163
|
+
}
|
|
1653
2164
|
|
|
1654
2165
|
// Metadata only by design: no schema in this file ever carries a variable value
|
|
1655
2166
|
// back to a client. Values are write-only and decrypted exclusively inside the
|
|
1656
2167
|
// worker at sandbox materialization time.
|
|
1657
|
-
export const
|
|
1658
|
-
name:
|
|
2168
|
+
export const VariableSetVariableMetadata = z.object({
|
|
2169
|
+
name: VariableSetVariableName,
|
|
1659
2170
|
version: z.number().int().positive(),
|
|
1660
2171
|
createdAt: z.string(),
|
|
1661
2172
|
updatedAt: z.string(),
|
|
1662
2173
|
});
|
|
1663
|
-
export type
|
|
2174
|
+
export type VariableSetVariableMetadata = z.infer<typeof VariableSetVariableMetadata>;
|
|
2175
|
+
/** @deprecated use VariableSetVariableMetadata */
|
|
2176
|
+
export const WorkspaceEnvironmentVariableMetadata = VariableSetVariableMetadata;
|
|
2177
|
+
/** @deprecated use VariableSetVariableMetadata */
|
|
2178
|
+
export type WorkspaceEnvironmentVariableMetadata = VariableSetVariableMetadata;
|
|
1664
2179
|
|
|
1665
|
-
export const
|
|
2180
|
+
export const VariableSet = z.object({
|
|
1666
2181
|
id: z.string().uuid(),
|
|
1667
2182
|
accountId: z.string().uuid(),
|
|
1668
2183
|
workspaceId: z.string().uuid(),
|
|
1669
2184
|
name: z.string(),
|
|
1670
2185
|
description: z.string().nullable(),
|
|
1671
|
-
variables: z.array(
|
|
2186
|
+
variables: z.array(VariableSetVariableMetadata),
|
|
1672
2187
|
createdAt: z.string(),
|
|
1673
2188
|
updatedAt: z.string(),
|
|
1674
2189
|
});
|
|
1675
|
-
export type
|
|
2190
|
+
export type VariableSet = z.infer<typeof VariableSet>;
|
|
2191
|
+
/** @deprecated use VariableSet */
|
|
2192
|
+
export const WorkspaceEnvironment = VariableSet;
|
|
2193
|
+
/** @deprecated use VariableSet */
|
|
2194
|
+
export type WorkspaceEnvironment = VariableSet;
|
|
1676
2195
|
|
|
1677
|
-
export const
|
|
2196
|
+
export const CreateVariableSetRequest = z.object({
|
|
1678
2197
|
name: z.string().min(1).max(120),
|
|
1679
2198
|
description: z.string().max(2000).optional(),
|
|
1680
|
-
variables: z
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
2199
|
+
variables: z
|
|
2200
|
+
.array(
|
|
2201
|
+
z.object({
|
|
2202
|
+
name: VariableSetVariableName,
|
|
2203
|
+
value: z.string().min(1).max(32768),
|
|
2204
|
+
}),
|
|
2205
|
+
)
|
|
2206
|
+
.default([]),
|
|
2207
|
+
});
|
|
2208
|
+
export type CreateVariableSetRequest = z.infer<typeof CreateVariableSetRequest>;
|
|
2209
|
+
/** @deprecated use CreateVariableSetRequest */
|
|
2210
|
+
export const CreateWorkspaceEnvironmentRequest = CreateVariableSetRequest;
|
|
2211
|
+
/** @deprecated use CreateVariableSetRequest */
|
|
2212
|
+
export type CreateWorkspaceEnvironmentRequest = CreateVariableSetRequest;
|
|
2213
|
+
|
|
2214
|
+
export const UpdateVariableSetRequest = z.object({
|
|
2215
|
+
name: z.string().min(1).max(120).optional(),
|
|
2216
|
+
description: z.string().max(2000).nullable().optional(),
|
|
2217
|
+
});
|
|
2218
|
+
export type UpdateVariableSetRequest = z.infer<typeof UpdateVariableSetRequest>;
|
|
2219
|
+
/** @deprecated use UpdateVariableSetRequest */
|
|
2220
|
+
export const UpdateWorkspaceEnvironmentRequest = UpdateVariableSetRequest;
|
|
2221
|
+
/** @deprecated use UpdateVariableSetRequest */
|
|
2222
|
+
export type UpdateWorkspaceEnvironmentRequest = UpdateVariableSetRequest;
|
|
2223
|
+
|
|
2224
|
+
export const SetVariableSetVariableRequest = z.object({
|
|
2225
|
+
value: z.string().min(1).max(32768),
|
|
2226
|
+
});
|
|
2227
|
+
export type SetVariableSetVariableRequest = z.infer<typeof SetVariableSetVariableRequest>;
|
|
2228
|
+
/** @deprecated use SetVariableSetVariableRequest */
|
|
2229
|
+
export const SetWorkspaceEnvironmentVariableRequest = SetVariableSetVariableRequest;
|
|
2230
|
+
/** @deprecated use SetVariableSetVariableRequest */
|
|
2231
|
+
export type SetWorkspaceEnvironmentVariableRequest = SetVariableSetVariableRequest;
|
|
2232
|
+
|
|
2233
|
+
// --- Rigs ---------------------------------------------------------------------
|
|
2234
|
+
// Workspace-scoped, versioned sandbox machine definitions. A rig is the named
|
|
2235
|
+
// truth; each sandbox is a disposable fork of a rig version. Versions are
|
|
2236
|
+
// append-only and content-immutable; exactly one is active per rig.
|
|
2237
|
+
|
|
2238
|
+
// A self-declared health check: a name + the shell command that must exit 0.
|
|
2239
|
+
export const RigCheck = z.object({
|
|
2240
|
+
name: z.string().min(1).max(120),
|
|
2241
|
+
command: z.string().min(1).max(8192),
|
|
1684
2242
|
});
|
|
1685
|
-
export type
|
|
2243
|
+
export type RigCheck = z.infer<typeof RigCheck>;
|
|
1686
2244
|
|
|
1687
|
-
export const
|
|
2245
|
+
export const RigVersion = z.object({
|
|
2246
|
+
id: z.string().uuid(),
|
|
2247
|
+
rigId: z.string().uuid(),
|
|
2248
|
+
version: z.number().int().positive(),
|
|
2249
|
+
image: z.string().nullable(),
|
|
2250
|
+
setupScript: z.string().nullable(),
|
|
2251
|
+
checks: z.array(RigCheck),
|
|
2252
|
+
credentialHooks: z.array(z.string()),
|
|
2253
|
+
defaultVariableSetIds: z.array(z.string().uuid()),
|
|
2254
|
+
changelog: z.string().nullable(),
|
|
2255
|
+
// Attribution: 'user:<subject>' | 'session:<id>' | 'system'.
|
|
2256
|
+
createdBy: z.string().nullable(),
|
|
2257
|
+
active: z.boolean(),
|
|
2258
|
+
createdAt: z.string(),
|
|
2259
|
+
});
|
|
2260
|
+
export type RigVersion = z.infer<typeof RigVersion>;
|
|
2261
|
+
|
|
2262
|
+
export const RigVerificationHealth = z.object({
|
|
2263
|
+
checkHealth: z.enum(["passing", "failing", "unknown"]),
|
|
2264
|
+
lastVerifiedAt: z.string().nullable(),
|
|
2265
|
+
});
|
|
2266
|
+
export type RigVerificationHealth = z.infer<typeof RigVerificationHealth>;
|
|
2267
|
+
|
|
2268
|
+
export const Rig = z.object({
|
|
2269
|
+
id: z.string().uuid(),
|
|
2270
|
+
accountId: z.string().uuid(),
|
|
2271
|
+
workspaceId: z.string().uuid(),
|
|
2272
|
+
name: z.string(),
|
|
2273
|
+
description: z.string().nullable(),
|
|
2274
|
+
createdBy: z.string().nullable(),
|
|
2275
|
+
// The rig's currently-active version (present after create; nullable so a
|
|
2276
|
+
// partial/list read can omit it without a schema change).
|
|
2277
|
+
activeVersion: RigVersion.nullable(),
|
|
2278
|
+
// Summary for the currently active version. null only when there is no active
|
|
2279
|
+
// version; otherwise "unknown" means the active version has no verification.
|
|
2280
|
+
activeVersionHealth: RigVerificationHealth.nullable(),
|
|
2281
|
+
versionCount: z.number().int().nonnegative(),
|
|
2282
|
+
createdAt: z.string(),
|
|
2283
|
+
updatedAt: z.string(),
|
|
2284
|
+
});
|
|
2285
|
+
export type Rig = z.infer<typeof Rig>;
|
|
2286
|
+
|
|
2287
|
+
export const RigChangeKind = z.enum(["setup_append", "definition_edit"]);
|
|
2288
|
+
export type RigChangeKind = z.infer<typeof RigChangeKind>;
|
|
2289
|
+
|
|
2290
|
+
export const RigChangeStatus = z.enum(["proposed", "verifying", "merged", "rejected", "failed"]);
|
|
2291
|
+
export type RigChangeStatus = z.infer<typeof RigChangeStatus>;
|
|
2292
|
+
|
|
2293
|
+
// A single check's outcome inside a verification run (populated in M4).
|
|
2294
|
+
export const RigCheckResult = z.object({
|
|
2295
|
+
name: z.string(),
|
|
2296
|
+
command: z.string(),
|
|
2297
|
+
exitCode: z.number().int().nullable(),
|
|
2298
|
+
output: z.string().optional(),
|
|
2299
|
+
});
|
|
2300
|
+
export type RigCheckResult = z.infer<typeof RigCheckResult>;
|
|
2301
|
+
|
|
2302
|
+
// The verification record a rig-CI run writes onto a change (M4). Open-ended
|
|
2303
|
+
// (passthrough) so M4 can enrich it without a contracts break.
|
|
2304
|
+
export const RigChangeVerification = z
|
|
2305
|
+
.object({
|
|
2306
|
+
startedAt: z.string().optional(),
|
|
2307
|
+
finishedAt: z.string().optional(),
|
|
2308
|
+
log: z.string().optional(),
|
|
2309
|
+
checkResults: z.array(RigCheckResult).optional(),
|
|
2310
|
+
})
|
|
2311
|
+
.passthrough();
|
|
2312
|
+
export type RigChangeVerification = z.infer<typeof RigChangeVerification>;
|
|
2313
|
+
|
|
2314
|
+
export const RigChange = z.object({
|
|
2315
|
+
id: z.string().uuid(),
|
|
2316
|
+
rigId: z.string().uuid(),
|
|
2317
|
+
baseVersionId: z.string().uuid().nullable(),
|
|
2318
|
+
kind: RigChangeKind,
|
|
2319
|
+
payload: z.record(z.string(), z.unknown()),
|
|
2320
|
+
status: RigChangeStatus,
|
|
2321
|
+
proposedBy: z.string().nullable(),
|
|
2322
|
+
verification: RigChangeVerification.nullable(),
|
|
2323
|
+
resultVersionId: z.string().uuid().nullable(),
|
|
2324
|
+
createdAt: z.string(),
|
|
2325
|
+
updatedAt: z.string(),
|
|
2326
|
+
});
|
|
2327
|
+
export type RigChange = z.infer<typeof RigChange>;
|
|
2328
|
+
|
|
2329
|
+
export const CreateRigRequest = z.object({
|
|
2330
|
+
name: z.string().min(1).max(120),
|
|
2331
|
+
description: z.string().max(2000).optional(),
|
|
2332
|
+
// Initial (version 1) content, inline.
|
|
2333
|
+
image: z.string().max(1024).optional(),
|
|
2334
|
+
setupScript: z.string().max(131072).optional(),
|
|
2335
|
+
checks: z.array(RigCheck).max(100).default([]),
|
|
2336
|
+
credentialHooks: z.array(z.string().min(1).max(200)).max(50).default([]),
|
|
2337
|
+
defaultVariableSetIds: z.array(z.string().uuid()).max(25).default([]),
|
|
2338
|
+
});
|
|
2339
|
+
export type CreateRigRequest = z.infer<typeof CreateRigRequest>;
|
|
2340
|
+
|
|
2341
|
+
export const UpdateRigRequest = z.object({
|
|
1688
2342
|
name: z.string().min(1).max(120).optional(),
|
|
1689
2343
|
description: z.string().max(2000).nullable().optional(),
|
|
1690
2344
|
});
|
|
1691
|
-
export type
|
|
2345
|
+
export type UpdateRigRequest = z.infer<typeof UpdateRigRequest>;
|
|
1692
2346
|
|
|
1693
|
-
|
|
1694
|
-
|
|
2347
|
+
// setup_append: the exact command that already worked (+ an optional note).
|
|
2348
|
+
export const RigSetupAppendPayload = z.object({
|
|
2349
|
+
command: z.string().min(1).max(8192),
|
|
2350
|
+
note: z.string().max(2000).optional(),
|
|
1695
2351
|
});
|
|
1696
|
-
export type
|
|
2352
|
+
export type RigSetupAppendPayload = z.infer<typeof RigSetupAppendPayload>;
|
|
2353
|
+
|
|
2354
|
+
// definition_edit: the full next-version content (all fields optional; unset
|
|
2355
|
+
// fields inherit from the base version at promote time).
|
|
2356
|
+
export const RigDefinitionEditPayload = z.object({
|
|
2357
|
+
image: z.string().max(1024).nullish(),
|
|
2358
|
+
setupScript: z.string().max(131072).nullish(),
|
|
2359
|
+
checks: z.array(RigCheck).max(100).optional(),
|
|
2360
|
+
credentialHooks: z.array(z.string().min(1).max(200)).max(50).optional(),
|
|
2361
|
+
defaultVariableSetIds: z.array(z.string().uuid()).max(25).optional(),
|
|
2362
|
+
changelog: z.string().max(4096).nullish(),
|
|
2363
|
+
});
|
|
2364
|
+
export type RigDefinitionEditPayload = z.infer<typeof RigDefinitionEditPayload>;
|
|
2365
|
+
|
|
2366
|
+
export const ProposeRigChangeRequest = z.discriminatedUnion("kind", [
|
|
2367
|
+
z.object({ kind: z.literal("setup_append"), payload: RigSetupAppendPayload }),
|
|
2368
|
+
z.object({ kind: z.literal("definition_edit"), payload: RigDefinitionEditPayload }),
|
|
2369
|
+
]);
|
|
2370
|
+
export type ProposeRigChangeRequest = z.infer<typeof ProposeRigChangeRequest>;
|
|
1697
2371
|
|
|
1698
2372
|
export const ScheduledTaskStatus = z.enum(["active", "paused"]);
|
|
1699
2373
|
export type ScheduledTaskStatus = z.infer<typeof ScheduledTaskStatus>;
|
|
@@ -1727,7 +2401,10 @@ export const ScheduledTaskScheduleSpec = z.discriminatedUnion("type", [
|
|
|
1727
2401
|
timeZone: z.string().min(1).default("UTC"),
|
|
1728
2402
|
hour: z.number().int().min(0).max(23),
|
|
1729
2403
|
minute: z.number().int().min(0).max(59),
|
|
1730
|
-
daysOfWeek: z
|
|
2404
|
+
daysOfWeek: z
|
|
2405
|
+
.array(z.enum(["SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY"]))
|
|
2406
|
+
.min(1)
|
|
2407
|
+
.optional(),
|
|
1731
2408
|
}),
|
|
1732
2409
|
]);
|
|
1733
2410
|
export type ScheduledTaskScheduleSpec = z.infer<typeof ScheduledTaskScheduleSpec>;
|
|
@@ -1756,7 +2433,13 @@ export const ScheduledTask = z.object({
|
|
|
1756
2433
|
overlapPolicy: ScheduledTaskOverlapPolicy,
|
|
1757
2434
|
agentConfig: ScheduledTaskAgentConfig,
|
|
1758
2435
|
reusableSessionId: z.string().uuid().nullable(),
|
|
1759
|
-
|
|
2436
|
+
variableSetId: z.string().uuid().nullable().default(null),
|
|
2437
|
+
/** @deprecated use variableSetId */
|
|
2438
|
+
environmentId: z.string().uuid().nullable().default(null),
|
|
2439
|
+
// The rig each run binds to (M3). Stored on the task; the ACTIVE version is
|
|
2440
|
+
// resolved PER FIRE (at dispatch), so a task always runs the rig's current
|
|
2441
|
+
// version rather than one frozen at task-create time. Null ⇒ rig-less runs.
|
|
2442
|
+
rigId: z.string().uuid().nullable().default(null),
|
|
1760
2443
|
metadata: z.record(z.string(), z.unknown()),
|
|
1761
2444
|
createdAt: z.string(),
|
|
1762
2445
|
updatedAt: z.string(),
|
|
@@ -1780,26 +2463,33 @@ export const ScheduledTaskRun = z.object({
|
|
|
1780
2463
|
});
|
|
1781
2464
|
export type ScheduledTaskRun = z.infer<typeof ScheduledTaskRun>;
|
|
1782
2465
|
|
|
1783
|
-
export const CreateScheduledTaskRequest =
|
|
2466
|
+
export const CreateScheduledTaskRequest = withVariableSetIdAlias({
|
|
1784
2467
|
name: z.string().min(1),
|
|
1785
2468
|
schedule: ScheduledTaskScheduleSpec,
|
|
1786
2469
|
runMode: ScheduledTaskRunMode.default("new_session_per_run"),
|
|
1787
2470
|
overlapPolicy: ScheduledTaskOverlapPolicy.default("allow_concurrent"),
|
|
1788
2471
|
agentConfig: ScheduledTaskAgentConfig,
|
|
1789
2472
|
status: ScheduledTaskStatus.default("active"),
|
|
2473
|
+
variableSetId: z.string().uuid().nullable().optional(),
|
|
1790
2474
|
environmentId: z.string().uuid().nullable().optional(),
|
|
2475
|
+
// The rig each run binds to (M3); its active version is resolved per fire.
|
|
2476
|
+
rigId: z.string().uuid().nullable().optional(),
|
|
1791
2477
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
1792
2478
|
});
|
|
1793
2479
|
export type CreateScheduledTaskRequest = z.infer<typeof CreateScheduledTaskRequest>;
|
|
1794
2480
|
|
|
1795
|
-
export const UpdateScheduledTaskRequest =
|
|
2481
|
+
export const UpdateScheduledTaskRequest = withVariableSetIdAlias({
|
|
1796
2482
|
name: z.string().min(1).optional(),
|
|
1797
2483
|
schedule: ScheduledTaskScheduleSpec.optional(),
|
|
1798
2484
|
runMode: ScheduledTaskRunMode.optional(),
|
|
1799
2485
|
overlapPolicy: ScheduledTaskOverlapPolicy.optional(),
|
|
1800
2486
|
agentConfig: ScheduledTaskAgentConfig.optional(),
|
|
1801
2487
|
status: ScheduledTaskStatus.optional(),
|
|
2488
|
+
variableSetId: z.string().uuid().nullable().optional(),
|
|
1802
2489
|
environmentId: z.string().uuid().nullable().optional(),
|
|
2490
|
+
// The rig each run binds to (M3); null clears it. Its active version is
|
|
2491
|
+
// resolved per fire, so an update takes effect on the next dispatch.
|
|
2492
|
+
rigId: z.string().uuid().nullable().optional(),
|
|
1803
2493
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
1804
2494
|
});
|
|
1805
2495
|
export type UpdateScheduledTaskRequest = z.infer<typeof UpdateScheduledTaskRequest>;
|
|
@@ -1854,7 +2544,9 @@ export const CapabilityPackScheduledTaskTemplate = z.object({
|
|
|
1854
2544
|
// instantiable templates; built-in packs may instead build prompts in code.
|
|
1855
2545
|
prompt: z.string().min(1).optional(),
|
|
1856
2546
|
});
|
|
1857
|
-
export type CapabilityPackScheduledTaskTemplate = z.infer<
|
|
2547
|
+
export type CapabilityPackScheduledTaskTemplate = z.infer<
|
|
2548
|
+
typeof CapabilityPackScheduledTaskTemplate
|
|
2549
|
+
>;
|
|
1858
2550
|
|
|
1859
2551
|
// One file inside a pack skill directory. Paths are workspace-relative POSIX
|
|
1860
2552
|
// paths inside the skill directory (for example "SKILL.md" or
|
|
@@ -1872,66 +2564,119 @@ export type CapabilityPackSkillFile = z.infer<typeof CapabilityPackSkillFile>;
|
|
|
1872
2564
|
// A skill delivered by a capability pack. The name doubles as the skill
|
|
1873
2565
|
// directory under the sandbox skill index (skills/<name>), so it must be a
|
|
1874
2566
|
// single safe path segment. Every skill must ship a top-level SKILL.md.
|
|
1875
|
-
export const CapabilityPackSkill = z
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2567
|
+
export const CapabilityPackSkill = z
|
|
2568
|
+
.object({
|
|
2569
|
+
name: z
|
|
2570
|
+
.string()
|
|
2571
|
+
.min(1)
|
|
2572
|
+
.max(64)
|
|
2573
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, {
|
|
2574
|
+
message: "skill name must be a single path segment of letters, digits, '.', '_' or '-'",
|
|
2575
|
+
}),
|
|
2576
|
+
description: z.string().min(1).max(2048).optional(),
|
|
2577
|
+
files: z.array(CapabilityPackSkillFile).min(1).max(64),
|
|
2578
|
+
})
|
|
2579
|
+
.superRefine((skill, ctx) => {
|
|
2580
|
+
const seen = new Set<string>();
|
|
2581
|
+
skill.files.forEach((file, index) => {
|
|
2582
|
+
if (seen.has(file.path)) {
|
|
2583
|
+
ctx.addIssue({
|
|
2584
|
+
code: "custom",
|
|
2585
|
+
message: `duplicate skill file path: ${file.path}`,
|
|
2586
|
+
path: ["files", index, "path"],
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
seen.add(file.path);
|
|
2590
|
+
});
|
|
2591
|
+
if (!skill.files.some((file) => file.path === "SKILL.md")) {
|
|
2592
|
+
ctx.addIssue({
|
|
2593
|
+
code: "custom",
|
|
2594
|
+
message: "skill must include a top-level SKILL.md file",
|
|
2595
|
+
path: ["files"],
|
|
2596
|
+
});
|
|
1886
2597
|
}
|
|
1887
|
-
seen.add(file.path);
|
|
1888
2598
|
});
|
|
1889
|
-
if (!skill.files.some((file) => file.path === "SKILL.md")) {
|
|
1890
|
-
ctx.addIssue({ code: "custom", message: "skill must include a top-level SKILL.md file", path: ["files"] });
|
|
1891
|
-
}
|
|
1892
|
-
});
|
|
1893
2599
|
export type CapabilityPackSkill = z.infer<typeof CapabilityPackSkill>;
|
|
1894
2600
|
|
|
1895
2601
|
function isSafePackSkillRelativePath(path: string): boolean {
|
|
1896
2602
|
if (path.startsWith("/") || path.includes("\\")) {
|
|
1897
2603
|
return false;
|
|
1898
2604
|
}
|
|
1899
|
-
return path
|
|
2605
|
+
return path
|
|
2606
|
+
.split("/")
|
|
2607
|
+
.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
1900
2608
|
}
|
|
1901
2609
|
|
|
1902
|
-
|
|
1903
|
-
id: z.string().min(1),
|
|
1904
|
-
name: z.string().min(1),
|
|
2610
|
+
const CapabilityPackVariableSet = z.object({
|
|
1905
2611
|
description: z.string().min(1),
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
version: z.string().min(1),
|
|
1909
|
-
// Container image ref (digest-pinned recommended) the pack's sessions run
|
|
1910
|
-
// in. At most one enabled pack per workspace may declare one; with none,
|
|
1911
|
-
// sessions use the deployment-wide image settings.
|
|
1912
|
-
sandboxImage: z.string().trim().min(1).max(512).optional(),
|
|
1913
|
-
// Skills delivered into the sandbox skill index when the pack is enabled.
|
|
1914
|
-
skills: z.array(CapabilityPackSkill).max(32).superRefine((skills, ctx) => {
|
|
1915
|
-
const seen = new Set<string>();
|
|
1916
|
-
skills.forEach((skill, index) => {
|
|
1917
|
-
const key = skill.name.toLowerCase();
|
|
1918
|
-
if (seen.has(key)) {
|
|
1919
|
-
ctx.addIssue({ code: "custom", message: `duplicate pack skill name: ${skill.name}`, path: [index, "name"] });
|
|
1920
|
-
}
|
|
1921
|
-
seen.add(key);
|
|
1922
|
-
});
|
|
1923
|
-
}).default([]),
|
|
1924
|
-
tools: z.array(ToolRef).default([]),
|
|
1925
|
-
connectors: z.array(CapabilityPackConnector).default([]),
|
|
1926
|
-
knowledge: z.array(CapabilityPackKnowledge).default([]),
|
|
1927
|
-
scheduledTaskTemplates: z.array(CapabilityPackScheduledTaskTemplate).default([]),
|
|
1928
|
-
environment: z.object({
|
|
1929
|
-
description: z.string().min(1),
|
|
1930
|
-
requiredVariables: z.array(WorkspaceEnvironmentVariableName).default([]),
|
|
1931
|
-
required: z.boolean().default(false),
|
|
1932
|
-
}).optional(),
|
|
1933
|
-
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
2612
|
+
requiredVariables: z.array(VariableSetVariableName).default([]),
|
|
2613
|
+
required: z.boolean().default(false),
|
|
1934
2614
|
});
|
|
2615
|
+
|
|
2616
|
+
export const CapabilityPack = z.preprocess(
|
|
2617
|
+
(input) => {
|
|
2618
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
2619
|
+
return input;
|
|
2620
|
+
}
|
|
2621
|
+
const record = input as Record<string, unknown>;
|
|
2622
|
+
if (record.variableSet !== undefined) {
|
|
2623
|
+
return record;
|
|
2624
|
+
}
|
|
2625
|
+
if (record.environment !== undefined) {
|
|
2626
|
+
const { environment: _environment, ...rest } = record;
|
|
2627
|
+
return { ...rest, variableSet: record.environment };
|
|
2628
|
+
}
|
|
2629
|
+
if (record.requiredVariables !== undefined) {
|
|
2630
|
+
const { requiredVariables: _requiredVariables, ...rest } = record;
|
|
2631
|
+
return {
|
|
2632
|
+
...rest,
|
|
2633
|
+
variableSet: {
|
|
2634
|
+
description: "Required variables",
|
|
2635
|
+
requiredVariables: record.requiredVariables,
|
|
2636
|
+
required: Array.isArray(record.requiredVariables) && record.requiredVariables.length > 0,
|
|
2637
|
+
},
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
return record;
|
|
2641
|
+
},
|
|
2642
|
+
z.object({
|
|
2643
|
+
id: z.string().min(1),
|
|
2644
|
+
name: z.string().min(1),
|
|
2645
|
+
description: z.string().min(1),
|
|
2646
|
+
role: z.string().min(1),
|
|
2647
|
+
category: z.string().min(1),
|
|
2648
|
+
version: z.string().min(1),
|
|
2649
|
+
// Container image ref (digest-pinned recommended) the pack's sessions run
|
|
2650
|
+
// in. At most one enabled pack per workspace may declare one; with none,
|
|
2651
|
+
// sessions use the deployment-wide image settings.
|
|
2652
|
+
sandboxImage: z.string().trim().min(1).max(512).optional(),
|
|
2653
|
+
// Skills delivered into the sandbox skill index when the pack is enabled.
|
|
2654
|
+
skills: z
|
|
2655
|
+
.array(CapabilityPackSkill)
|
|
2656
|
+
.max(32)
|
|
2657
|
+
.superRefine((skills, ctx) => {
|
|
2658
|
+
const seen = new Set<string>();
|
|
2659
|
+
skills.forEach((skill, index) => {
|
|
2660
|
+
const key = skill.name.toLowerCase();
|
|
2661
|
+
if (seen.has(key)) {
|
|
2662
|
+
ctx.addIssue({
|
|
2663
|
+
code: "custom",
|
|
2664
|
+
message: `duplicate pack skill name: ${skill.name}`,
|
|
2665
|
+
path: [index, "name"],
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
seen.add(key);
|
|
2669
|
+
});
|
|
2670
|
+
})
|
|
2671
|
+
.default([]),
|
|
2672
|
+
tools: z.array(ToolRef).default([]),
|
|
2673
|
+
connectors: z.array(CapabilityPackConnector).default([]),
|
|
2674
|
+
knowledge: z.array(CapabilityPackKnowledge).default([]),
|
|
2675
|
+
scheduledTaskTemplates: z.array(CapabilityPackScheduledTaskTemplate).default([]),
|
|
2676
|
+
variableSet: CapabilityPackVariableSet.optional(),
|
|
2677
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
2678
|
+
}),
|
|
2679
|
+
);
|
|
1935
2680
|
export type CapabilityPack = z.infer<typeof CapabilityPack>;
|
|
1936
2681
|
|
|
1937
2682
|
// Registering a pack stores the manifest itself; the request body is a full
|
|
@@ -1963,7 +2708,8 @@ export const PackInstallation = z.object({
|
|
|
1963
2708
|
});
|
|
1964
2709
|
export type PackInstallation = z.infer<typeof PackInstallation>;
|
|
1965
2710
|
|
|
1966
|
-
export const EnablePackRequest =
|
|
2711
|
+
export const EnablePackRequest = withVariableSetIdAlias({
|
|
2712
|
+
variableSetId: z.string().uuid().optional(),
|
|
1967
2713
|
environmentId: z.string().uuid().optional(),
|
|
1968
2714
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
1969
2715
|
});
|
|
@@ -2043,6 +2789,123 @@ export const CreateSocialPostRequest = z.object({
|
|
|
2043
2789
|
});
|
|
2044
2790
|
export type CreateSocialPostRequest = z.infer<typeof CreateSocialPostRequest>;
|
|
2045
2791
|
|
|
2792
|
+
export const ConnectionKind = z.enum(["oauth2", "api_key", "app_install", "delegated"]);
|
|
2793
|
+
export type ConnectionKind = z.infer<typeof ConnectionKind>;
|
|
2794
|
+
|
|
2795
|
+
export const ConnectionStatus = z.enum(["active", "needs_reauth", "revoked", "error"]);
|
|
2796
|
+
export type ConnectionStatus = z.infer<typeof ConnectionStatus>;
|
|
2797
|
+
|
|
2798
|
+
export const McpServerConnectionRef = z
|
|
2799
|
+
.object({
|
|
2800
|
+
connectionId: z.string().uuid().optional(),
|
|
2801
|
+
providerDomain: z.string().min(1),
|
|
2802
|
+
kind: ConnectionKind.optional(),
|
|
2803
|
+
scopes: z.array(z.string().min(1)).optional(),
|
|
2804
|
+
resource: z.string().min(1).optional(),
|
|
2805
|
+
subjectScope: z.enum(["workspace", "subject"]).optional(),
|
|
2806
|
+
})
|
|
2807
|
+
.strict();
|
|
2808
|
+
export type McpServerConnectionRef = z.infer<typeof McpServerConnectionRef>;
|
|
2809
|
+
|
|
2810
|
+
export const ConnectionMetadata = z.object({
|
|
2811
|
+
id: z.string().uuid(),
|
|
2812
|
+
accountId: z.string().uuid(),
|
|
2813
|
+
workspaceId: z.string().uuid(),
|
|
2814
|
+
subjectId: z.string().nullable(),
|
|
2815
|
+
providerDomain: z.string(),
|
|
2816
|
+
kind: ConnectionKind,
|
|
2817
|
+
status: ConnectionStatus,
|
|
2818
|
+
grantedScopes: z.array(z.string()),
|
|
2819
|
+
expiresAt: z.string().nullable(),
|
|
2820
|
+
lastRefreshAt: z.string().nullable(),
|
|
2821
|
+
lastUsedAt: z.string().nullable(),
|
|
2822
|
+
lastError: z.string().nullable(),
|
|
2823
|
+
version: z.number().int().positive(),
|
|
2824
|
+
metadata: z.record(z.string(), z.unknown()),
|
|
2825
|
+
createdBySubjectId: z.string().nullable(),
|
|
2826
|
+
updatedBySubjectId: z.string().nullable(),
|
|
2827
|
+
createdAt: z.string(),
|
|
2828
|
+
updatedAt: z.string(),
|
|
2829
|
+
});
|
|
2830
|
+
export type ConnectionMetadata = z.infer<typeof ConnectionMetadata>;
|
|
2831
|
+
|
|
2832
|
+
export const ConnectionCredentialBundle = z.record(z.string(), z.unknown());
|
|
2833
|
+
export type ConnectionCredentialBundle = z.infer<typeof ConnectionCredentialBundle>;
|
|
2834
|
+
|
|
2835
|
+
export const CreateConnectionRequest = z.object({
|
|
2836
|
+
providerDomain: z.string().min(1),
|
|
2837
|
+
kind: ConnectionKind,
|
|
2838
|
+
subjectId: z.string().min(1).nullable().optional(),
|
|
2839
|
+
credential: ConnectionCredentialBundle,
|
|
2840
|
+
grantedScopes: z.array(z.string().min(1)).default([]),
|
|
2841
|
+
expiresAt: z.string().datetime({ offset: true }).nullable().optional(),
|
|
2842
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
2843
|
+
});
|
|
2844
|
+
export type CreateConnectionRequest = z.infer<typeof CreateConnectionRequest>;
|
|
2845
|
+
|
|
2846
|
+
export const UpdateConnectionRequest = z.object({
|
|
2847
|
+
providerDomain: z.string().min(1).optional(),
|
|
2848
|
+
subjectId: z.string().min(1).nullable().optional(),
|
|
2849
|
+
kind: ConnectionKind.optional(),
|
|
2850
|
+
status: ConnectionStatus.optional(),
|
|
2851
|
+
credential: ConnectionCredentialBundle.optional(),
|
|
2852
|
+
grantedScopes: z.array(z.string().min(1)).optional(),
|
|
2853
|
+
expiresAt: z.string().datetime({ offset: true }).nullable().optional(),
|
|
2854
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
2855
|
+
});
|
|
2856
|
+
export type UpdateConnectionRequest = z.infer<typeof UpdateConnectionRequest>;
|
|
2857
|
+
|
|
2858
|
+
export const ConnectionResponse = z.object({
|
|
2859
|
+
connection: ConnectionMetadata,
|
|
2860
|
+
});
|
|
2861
|
+
export type ConnectionResponse = z.infer<typeof ConnectionResponse>;
|
|
2862
|
+
|
|
2863
|
+
export const ListConnectionsResponse = z.object({
|
|
2864
|
+
connections: z.array(ConnectionMetadata),
|
|
2865
|
+
});
|
|
2866
|
+
export type ListConnectionsResponse = z.infer<typeof ListConnectionsResponse>;
|
|
2867
|
+
|
|
2868
|
+
export const OAuthStartRequest = z
|
|
2869
|
+
.object({
|
|
2870
|
+
providerDomain: z.string().min(1).optional(),
|
|
2871
|
+
mcpUrl: z.string().url().optional(),
|
|
2872
|
+
resource: z.string().url().optional(),
|
|
2873
|
+
requestedScopes: z.array(z.string().min(1)).default([]),
|
|
2874
|
+
returnPath: z.string().min(1).optional(),
|
|
2875
|
+
connectionId: z.string().uuid().optional(),
|
|
2876
|
+
oauthClient: z
|
|
2877
|
+
.object({
|
|
2878
|
+
clientId: z.string().min(1),
|
|
2879
|
+
clientSecret: z.string().min(1).optional(),
|
|
2880
|
+
tokenEndpointAuthMethod: z
|
|
2881
|
+
.enum(["none", "client_secret_post", "client_secret_basic"])
|
|
2882
|
+
.optional(),
|
|
2883
|
+
})
|
|
2884
|
+
.optional(),
|
|
2885
|
+
})
|
|
2886
|
+
.refine((value) => Boolean(value.mcpUrl ?? value.resource), {
|
|
2887
|
+
message: "mcpUrl is required",
|
|
2888
|
+
path: ["mcpUrl"],
|
|
2889
|
+
});
|
|
2890
|
+
export type OAuthStartRequest = z.infer<typeof OAuthStartRequest>;
|
|
2891
|
+
|
|
2892
|
+
export const OAuthStartResponse = z.object({
|
|
2893
|
+
state: z.string().min(1),
|
|
2894
|
+
authorizationUrl: z.string().url().nullable(),
|
|
2895
|
+
expiresAt: z.string(),
|
|
2896
|
+
});
|
|
2897
|
+
export type OAuthStartResponse = z.infer<typeof OAuthStartResponse>;
|
|
2898
|
+
|
|
2899
|
+
export const IntegrationClientMetadata = z.object({
|
|
2900
|
+
client_id: z.string().url(),
|
|
2901
|
+
client_name: z.literal("OpenGeni"),
|
|
2902
|
+
redirect_uris: z.array(z.string().url()),
|
|
2903
|
+
token_endpoint_auth_method: z.literal("none"),
|
|
2904
|
+
grant_types: z.array(z.enum(["authorization_code", "refresh_token"])),
|
|
2905
|
+
response_types: z.array(z.literal("code")),
|
|
2906
|
+
});
|
|
2907
|
+
export type IntegrationClientMetadata = z.infer<typeof IntegrationClientMetadata>;
|
|
2908
|
+
|
|
2046
2909
|
export const MarketingDailyAnalysisTaskRequest = z.object({
|
|
2047
2910
|
name: z.string().min(1).optional(),
|
|
2048
2911
|
connectionIds: z.array(z.string().uuid()).default([]),
|
|
@@ -2060,12 +2923,24 @@ export type MarketingDailyAnalysisTaskRequest = z.infer<typeof MarketingDailyAna
|
|
|
2060
2923
|
export const CapabilityKind = z.enum(["pack", "mcp", "api", "skill", "plugin"]);
|
|
2061
2924
|
export type CapabilityKind = z.infer<typeof CapabilityKind>;
|
|
2062
2925
|
|
|
2063
|
-
export const CapabilitySource = z.enum([
|
|
2926
|
+
export const CapabilitySource = z.enum([
|
|
2927
|
+
"built_in",
|
|
2928
|
+
"configured",
|
|
2929
|
+
"public_registry",
|
|
2930
|
+
"registry",
|
|
2931
|
+
"manual",
|
|
2932
|
+
]);
|
|
2064
2933
|
export type CapabilitySource = z.infer<typeof CapabilitySource>;
|
|
2065
2934
|
|
|
2066
2935
|
export const CapabilityInstallationStatus = z.enum(["active", "disabled"]);
|
|
2067
2936
|
export type CapabilityInstallationStatus = z.infer<typeof CapabilityInstallationStatus>;
|
|
2068
2937
|
|
|
2938
|
+
export const CapabilityCatalogAuthKind = z.enum(["oauth2", "api_key", "none", "unknown"]);
|
|
2939
|
+
export type CapabilityCatalogAuthKind = z.infer<typeof CapabilityCatalogAuthKind>;
|
|
2940
|
+
|
|
2941
|
+
export const CapabilityCatalogTier = z.enum(["verified", "community"]);
|
|
2942
|
+
export type CapabilityCatalogTier = z.infer<typeof CapabilityCatalogTier>;
|
|
2943
|
+
|
|
2069
2944
|
export const CapabilityRuntime = z.object({
|
|
2070
2945
|
available: z.boolean().default(false),
|
|
2071
2946
|
mcpServerId: z.string().min(1).optional(),
|
|
@@ -2088,10 +2963,34 @@ export const CapabilityCatalogItem = z.object({
|
|
|
2088
2963
|
endpointUrl: z.string().url().nullable().default(null),
|
|
2089
2964
|
installUrl: z.string().url().nullable().default(null),
|
|
2090
2965
|
authModel: z.string().min(1).nullable().default(null),
|
|
2966
|
+
providerDomain: z.string().min(1).nullable().default(null),
|
|
2967
|
+
surfaceType: z.string().min(1).nullable().default(null),
|
|
2968
|
+
transport: z.string().min(1).nullable().default(null),
|
|
2969
|
+
mcpUrl: z.string().url().nullable().default(null),
|
|
2970
|
+
authKind: CapabilityCatalogAuthKind.nullable().default(null),
|
|
2971
|
+
credentialFacts: z.array(z.record(z.string(), z.unknown())).default([]),
|
|
2972
|
+
tier: CapabilityCatalogTier.nullable().default(null),
|
|
2973
|
+
provenance: z.string().min(1).nullable().default(null),
|
|
2974
|
+
logoAssetPath: z.string().min(1).nullable().default(null),
|
|
2975
|
+
importBatchId: z.string().uuid().nullable().default(null),
|
|
2976
|
+
stale: z.boolean().default(false),
|
|
2977
|
+
staleAt: z.string().nullable().default(null),
|
|
2091
2978
|
tools: z.array(ToolRef).default([]),
|
|
2092
2979
|
runtime: CapabilityRuntime.default({ available: false, notes: null }),
|
|
2093
2980
|
enabled: z.boolean().default(false),
|
|
2094
2981
|
enabledReason: z.string().nullable().default(null),
|
|
2982
|
+
// The connection backing this enabled installation, when the enable-time
|
|
2983
|
+
// connectionRef resolved to one (null for header/credential-free items —
|
|
2984
|
+
// that means "no connection involved", not "broken"). Lets the UI match
|
|
2985
|
+
// connection health by id instead of guessing from providerDomain alone.
|
|
2986
|
+
connectionRef: z
|
|
2987
|
+
.object({
|
|
2988
|
+
connectionId: z.string().min(1),
|
|
2989
|
+
providerDomain: z.string().min(1),
|
|
2990
|
+
kind: z.string().min(1),
|
|
2991
|
+
})
|
|
2992
|
+
.nullable()
|
|
2993
|
+
.default(null),
|
|
2095
2994
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
2096
2995
|
createdAt: z.string().optional(),
|
|
2097
2996
|
updatedAt: z.string().optional(),
|
|
@@ -2128,22 +3027,24 @@ export const CreateCapabilityCatalogItemRequest = z.object({
|
|
|
2128
3027
|
});
|
|
2129
3028
|
export type CreateCapabilityCatalogItemRequest = z.infer<typeof CreateCapabilityCatalogItemRequest>;
|
|
2130
3029
|
|
|
2131
|
-
export const EnableCapabilityRequest =
|
|
3030
|
+
export const EnableCapabilityRequest = withVariableSetIdAlias({
|
|
2132
3031
|
config: z.record(z.string(), z.unknown()).default({}),
|
|
2133
3032
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
3033
|
+
connectionRef: McpServerConnectionRef.optional(),
|
|
2134
3034
|
/**
|
|
2135
3035
|
* Credential headers for remote MCP capabilities (for example an
|
|
2136
3036
|
* Authorization bearer token). Values are encrypted at rest with the
|
|
2137
|
-
* workspace-
|
|
3037
|
+
* workspace-variable-sets key, injected only into the runtime MCP client,
|
|
2138
3038
|
* and never returned by the API — responses expose header names only.
|
|
2139
3039
|
*/
|
|
2140
3040
|
headers: z.record(z.string(), z.string()).default({}),
|
|
2141
3041
|
/**
|
|
2142
|
-
* Initial
|
|
3042
|
+
* Initial variableSet attachment for kind=pack capabilities. Mirrors the
|
|
2143
3043
|
* dedicated POST /packs/:id/enable body: required to enable an
|
|
2144
|
-
*
|
|
3044
|
+
* variableSet.required pack through the unified capability-enable path,
|
|
2145
3045
|
* optional otherwise. Ignored by non-pack capabilities.
|
|
2146
3046
|
*/
|
|
3047
|
+
variableSetId: z.string().uuid().optional(),
|
|
2147
3048
|
environmentId: z.string().uuid().optional(),
|
|
2148
3049
|
});
|
|
2149
3050
|
export type EnableCapabilityRequest = z.infer<typeof EnableCapabilityRequest>;
|
|
@@ -2191,7 +3092,16 @@ export const Session = z.object({
|
|
|
2191
3092
|
// stale in-flight op and retry against the new active sandbox.
|
|
2192
3093
|
activeSandboxId: z.string().uuid().nullable(),
|
|
2193
3094
|
activeEpoch: z.number().int().nonnegative(),
|
|
2194
|
-
|
|
3095
|
+
variableSetId: z.string().uuid().nullable().default(null),
|
|
3096
|
+
/** @deprecated use variableSetId */
|
|
3097
|
+
environmentId: z.string().uuid().nullable().default(null),
|
|
3098
|
+
// The rig this session rides (M3 runtime binding). Both are resolved and
|
|
3099
|
+
// FROZEN at session create: rigId names the rig, rigVersionId pins the exact
|
|
3100
|
+
// active version the session's box/env/setup/doctrine are built from for the
|
|
3101
|
+
// session's whole life (a later promote does NOT move an existing session).
|
|
3102
|
+
// Both null ⇒ a rig-less session (byte-for-byte today's behavior).
|
|
3103
|
+
rigId: z.string().uuid().nullable().default(null),
|
|
3104
|
+
rigVersionId: z.string().uuid().nullable().default(null),
|
|
2195
3105
|
// Non-default first-party MCP token permissions (manager-style sessions);
|
|
2196
3106
|
// null means the fixed worker default set.
|
|
2197
3107
|
firstPartyMcpPermissions: z.array(Permission).nullable(),
|
|
@@ -2210,9 +3120,18 @@ export const Session = z.object({
|
|
|
2210
3120
|
temporalWorkflowId: z.string().nullable(),
|
|
2211
3121
|
activeTurnId: z.string().uuid().nullable(),
|
|
2212
3122
|
// Actual input tokens of the last model call of the most recent turn; the
|
|
2213
|
-
// pre-turn
|
|
3123
|
+
// pre-turn portable context-compaction trigger reads it as its budget
|
|
2214
3124
|
// signal. Null until a turn with usage has completed.
|
|
2215
3125
|
lastInputTokens: z.number().int().nonnegative().nullable(),
|
|
3126
|
+
queueVersion: z.number().int().nonnegative(),
|
|
3127
|
+
queueHeadPosition: z.number().int(),
|
|
3128
|
+
queueTailPosition: z.number().int(),
|
|
3129
|
+
controlState: SessionControlState,
|
|
3130
|
+
controlGeneration: z.number().int().nonnegative(),
|
|
3131
|
+
controlReason: z.string().nullable(),
|
|
3132
|
+
controlChangedBy: z.string().nullable(),
|
|
3133
|
+
controlChangedAt: z.string().nullable(),
|
|
3134
|
+
workspaceRunExceptionGeneration: z.number().int().nonnegative().nullable(),
|
|
2216
3135
|
lastSequence: z.number().int().nonnegative(),
|
|
2217
3136
|
// Multi-account Codex (P1). codexPinnedCredentialId: the account this session is
|
|
2218
3137
|
// manually PINNED to (null ⇒ follow the workspace active pointer).
|
|
@@ -2220,33 +3139,100 @@ export const Session = z.object({
|
|
|
2220
3139
|
// "Running on:" indicator's source). Both are credential-row ids, null until set.
|
|
2221
3140
|
codexPinnedCredentialId: z.string().uuid().nullable(),
|
|
2222
3141
|
codexLastCredentialId: z.string().uuid().nullable(),
|
|
3142
|
+
/** Personal (authenticated subject) workspace pin state, never workspace-global. */
|
|
3143
|
+
pinned: z.boolean().default(false),
|
|
3144
|
+
/** Stable pin ordering key; null when this subject has not pinned the session. */
|
|
3145
|
+
pinnedAt: z.string().nullable().default(null),
|
|
3146
|
+
/** Optimistic pin-state revision; zero represents an absent pin relation. */
|
|
3147
|
+
pinVersion: z.number().int().nonnegative().default(0),
|
|
3148
|
+
/**
|
|
3149
|
+
* Server-authoritative hierarchy summary populated on session-list reads.
|
|
3150
|
+
* Detail reads may omit it. The rail uses this instead of guessing a tree
|
|
3151
|
+
* from whichever global recency page happened to be loaded.
|
|
3152
|
+
*/
|
|
3153
|
+
treeStats: z
|
|
3154
|
+
.object({
|
|
3155
|
+
directChildren: z.number().int().nonnegative(),
|
|
3156
|
+
totalDescendants: z.number().int().nonnegative(),
|
|
3157
|
+
runningDescendants: z.number().int().nonnegative(),
|
|
3158
|
+
queuedDescendants: z.number().int().nonnegative(),
|
|
3159
|
+
attentionDescendants: z.number().int().nonnegative(),
|
|
3160
|
+
pausedDescendants: z.number().int().nonnegative(),
|
|
3161
|
+
failedDescendants: z.number().int().nonnegative(),
|
|
3162
|
+
})
|
|
3163
|
+
.optional(),
|
|
2223
3164
|
createdAt: z.string(),
|
|
2224
3165
|
updatedAt: z.string(),
|
|
2225
3166
|
});
|
|
2226
3167
|
export type Session = z.infer<typeof Session>;
|
|
2227
3168
|
|
|
3169
|
+
export type SessionSummary = Session;
|
|
3170
|
+
|
|
3171
|
+
/**
|
|
3172
|
+
* The canonical session-list page. Pinned rows are returned separately and are
|
|
3173
|
+
* excluded from `sessions`, so a cursor can page ordinary recency rows without
|
|
3174
|
+
* duplicating a pin. Pins are filtered by the same parent/search predicates as
|
|
3175
|
+
* ordinary rows and ordered by pinnedAt DESC, id DESC.
|
|
3176
|
+
*/
|
|
3177
|
+
export const SessionListResponse = z.object({
|
|
3178
|
+
pinned: z.array(Session),
|
|
3179
|
+
sessions: z.array(Session),
|
|
3180
|
+
nextCursor: z.string().nullable(),
|
|
3181
|
+
});
|
|
3182
|
+
export type SessionListResponse = z.infer<typeof SessionListResponse>;
|
|
3183
|
+
|
|
3184
|
+
// Recursive: the TS type is declared first so the schema annotation can carry
|
|
3185
|
+
// the FULL recursive shape (a shallow annotation loses type information for
|
|
3186
|
+
// contracts consumers after one level of nesting).
|
|
3187
|
+
export type LineageNode = {
|
|
3188
|
+
session: SessionSummary;
|
|
3189
|
+
children: LineageNode[];
|
|
3190
|
+
};
|
|
3191
|
+
export const LineageNode: z.ZodType<LineageNode> = z.lazy(() =>
|
|
3192
|
+
z.object({
|
|
3193
|
+
session: Session,
|
|
3194
|
+
children: z.array(LineageNode),
|
|
3195
|
+
}),
|
|
3196
|
+
);
|
|
3197
|
+
|
|
3198
|
+
export const SessionLineageResponse = z.object({
|
|
3199
|
+
ancestors: z.array(Session),
|
|
3200
|
+
children: z.array(LineageNode),
|
|
3201
|
+
truncated: z.boolean().default(false),
|
|
3202
|
+
});
|
|
3203
|
+
export type SessionLineageResponse = z.infer<typeof SessionLineageResponse>;
|
|
3204
|
+
|
|
2228
3205
|
export const SessionEventType = z.enum([
|
|
2229
3206
|
"session.created",
|
|
2230
3207
|
"session.status.changed",
|
|
2231
3208
|
"session.requiresAction",
|
|
3209
|
+
"session.context.compaction.requested",
|
|
2232
3210
|
"session.context.compacted",
|
|
3211
|
+
"session.context.compaction.skipped",
|
|
2233
3212
|
"session.context.cleared",
|
|
2234
3213
|
"user.message",
|
|
2235
|
-
"user.
|
|
3214
|
+
"user.pause",
|
|
2236
3215
|
"user.approvalDecision",
|
|
2237
3216
|
"turn.queued",
|
|
2238
|
-
"turn.updated",
|
|
2239
3217
|
"turn.started",
|
|
2240
3218
|
"turn.completed",
|
|
2241
3219
|
"turn.failed",
|
|
2242
3220
|
"turn.cancelled",
|
|
2243
|
-
"turn.
|
|
3221
|
+
"turn.superseded",
|
|
3222
|
+
"turn.recovery.requested",
|
|
3223
|
+
"turn.capacity_waiting",
|
|
2244
3224
|
"agent.message.delta",
|
|
2245
3225
|
"agent.message.completed",
|
|
2246
3226
|
"agent.reasoning.delta",
|
|
2247
3227
|
"agent.toolCall.created",
|
|
2248
3228
|
"agent.toolCall.output",
|
|
3229
|
+
"agent.model.usage",
|
|
3230
|
+
"tool.auth_needed",
|
|
2249
3231
|
"agent.updated",
|
|
3232
|
+
"rig.setup.started",
|
|
3233
|
+
"rig.setup.completed",
|
|
3234
|
+
"rig.setup.skipped",
|
|
3235
|
+
"rig.setup.failed",
|
|
2250
3236
|
"sandbox.operation.started",
|
|
2251
3237
|
"sandbox.operation.completed",
|
|
2252
3238
|
"sandbox.operation.failed",
|
|
@@ -2257,7 +3243,22 @@ export const SessionEventType = z.enum([
|
|
|
2257
3243
|
"goal.completed",
|
|
2258
3244
|
"goal.paused",
|
|
2259
3245
|
"goal.resumed",
|
|
3246
|
+
"goal.cleared",
|
|
2260
3247
|
"goal.continuation",
|
|
3248
|
+
"system.update.pending",
|
|
3249
|
+
"system.update.delivered",
|
|
3250
|
+
"session.control.paused",
|
|
3251
|
+
"session.control.resumed",
|
|
3252
|
+
"session.control.steer_requested",
|
|
3253
|
+
"workspace.inference.paused",
|
|
3254
|
+
"workspace.inference.resumed",
|
|
3255
|
+
"session.queue.prompt.cancelled",
|
|
3256
|
+
"session.queue.history",
|
|
3257
|
+
// A terminal/stale activity callback is retained as an audit wrapper rather
|
|
3258
|
+
// than being dropped or emitted as though it belonged to the current turn.
|
|
3259
|
+
"turn.event.rejected_late",
|
|
3260
|
+
"memory.saved",
|
|
3261
|
+
"memory.corrected",
|
|
2261
3262
|
// Channel-B desktop pixel-plane signals (07-channel-b §1.2). The pixel socket
|
|
2262
3263
|
// carries opaque RFB and cannot carry a control message the client can act on,
|
|
2263
3264
|
// so these ride the durable, sequenced, gap-filled Channel-A SSE spine.
|
|
@@ -2290,9 +3291,94 @@ export const SessionEventType = z.enum([
|
|
|
2290
3291
|
// (manual switch in P1; failover/rotation in P3 reuse the same event). Drives
|
|
2291
3292
|
// the in-session "Running on:" indicator's live flip.
|
|
2292
3293
|
"codex.account.switched",
|
|
3294
|
+
// OPE-21 per-turn selection audit. Payload is metadata only: credential row
|
|
3295
|
+
// id, bounded strategy/reason, and pool counts — never token material.
|
|
3296
|
+
"codex.credential.selected",
|
|
3297
|
+
// OPE-21 durable zero-capacity wait lifecycle. Runtime/system events only;
|
|
3298
|
+
// no synthetic user message is created when capacity returns.
|
|
3299
|
+
"codex.capacity.waiting",
|
|
3300
|
+
"codex.capacity.resumed",
|
|
3301
|
+
"codex.capacity.superseded",
|
|
3302
|
+
// Sandbox durability observability (sandbox-file-persistence). The 2026-07
|
|
3303
|
+
// incidents (mid-session box death with /workspace loss; a fatal manifest-env
|
|
3304
|
+
// delta on a live box) were near-unattributable because box lifecycle left no
|
|
3305
|
+
// durable trace — only worker logs, which rotate within hours. These events
|
|
3306
|
+
// make every box transition and env recomputation drift readable from the DB
|
|
3307
|
+
// alone. Payloads carry ids/flags/key NAMES only — never env values (secrets).
|
|
3308
|
+
"sandbox.box.created", // box cold-created/cold-restored ({hydrated: "archive"|"none"})
|
|
3309
|
+
"sandbox.box.lost", // resume-by-id found the box gone (provider NotFound)
|
|
3310
|
+
"sandbox.box.terminated", // reaper drain terminated the box ({actor, persisted})
|
|
3311
|
+
"sandbox.box.snapshot", // mid-session /workspace snapshot persisted ({trigger})
|
|
3312
|
+
"sandbox.env.drift", // recomputed manifest env != live box env (key names only)
|
|
3313
|
+
// Active-sandbox pointer reconcile (issue #341 invariant B). Turn start found the
|
|
3314
|
+
// persisted (active_sandbox_id, active_epoch) pointing at a target the turn cannot
|
|
3315
|
+
// establish — a deleted/absent sandbox, a Modal sibling with no establisher, or a
|
|
3316
|
+
// selfhosted sandbox with no enrollment — and reset it to the session HOME under
|
|
3317
|
+
// the epoch fence instead of routing every op into the dead target. A VISIBLE,
|
|
3318
|
+
// never-silent downgrade: payload carries the typed reason + from/to epoch, never a
|
|
3319
|
+
// target id or command content. Announce-only; hits the timeline projection default
|
|
3320
|
+
// (no rendered item) like the other sandbox.* diagnostics.
|
|
3321
|
+
"session.route.reconciled",
|
|
3322
|
+
// Workbench v2 turn-end workspace capture (dossier §10.1). ANNOUNCE-ONLY: a new
|
|
3323
|
+
// capture revision was persisted at turn end; the client refetches the latest
|
|
3324
|
+
// capture. It carries metadata only (revision/turnId/capturedAt/leaseEpoch/stats),
|
|
3325
|
+
// never file content. Hits the timeline projection default case (ignored) — it
|
|
3326
|
+
// must NEVER gain a rendered timeline item without regenerating the golden
|
|
3327
|
+
// snapshots (dossier §7.3 golden-grammar gate).
|
|
3328
|
+
"workspace.revision.captured",
|
|
3329
|
+
// Repository discovery could not prove a complete capture. The worker
|
|
3330
|
+
// persisted a failed/degraded revision marker and clients must fall back to
|
|
3331
|
+
// the live box rather than trust a zero-repository snapshot.
|
|
3332
|
+
"workspace.revision.degraded",
|
|
3333
|
+
// Connected Machine (selfhosted) op-outcome observability (failure-visibility
|
|
3334
|
+
// doctrine, out-of-band plane). SESSION-scoped facts only: these fire for the
|
|
3335
|
+
// session whose turn ran the op (the two-planes rule — machine-plane facts like
|
|
3336
|
+
// pressure live in the M10 metrics DB, never as session events). Payloads carry
|
|
3337
|
+
// the op kind + a typed fault class + attempt count — NEVER command content.
|
|
3338
|
+
//
|
|
3339
|
+
// `machine.op.failed` fires ONLY for INFRASTRUCTURE fault classes (offline,
|
|
3340
|
+
// draining-exhausted, payload-too-large, reconnecting-timeout, OS/stream/protocol)
|
|
3341
|
+
// — a semantic miss the model asked about (a missing path, a consent gate, a
|
|
3342
|
+
// nonzero exit) is an OUTCOME, not an infra fault, and never fires this.
|
|
3343
|
+
// `machine.op.recovered` is the healed-fault leading indicator (a blip/backpressure
|
|
3344
|
+
// the transport absorbed): announce-only, quiet. Both hit the timeline projection's
|
|
3345
|
+
// quiet status-tick tier (the severity split's "degraded"); adding a rendered item
|
|
3346
|
+
// requires regenerating the golden snapshots (the golden-grammar gate).
|
|
3347
|
+
"machine.op.failed",
|
|
3348
|
+
"machine.op.recovered",
|
|
3349
|
+
// Connected Machine (selfhosted) LINK-plane observability (failure-visibility
|
|
3350
|
+
// doctrine). SESSION-scoped, ANNOUNCE-ONLY facts fanned out to the sessions that
|
|
3351
|
+
// had an active op running on the machine when its control link changed — never
|
|
3352
|
+
// to idle/historical sessions. Payloads carry ids / a typed reason / key-names
|
|
3353
|
+
// only, NEVER command content.
|
|
3354
|
+
//
|
|
3355
|
+
// `machine.link.lost` — the machine announced a clean GoingOffline (its control
|
|
3356
|
+
// link is going away) while a session had a running turn on it. `machine.link.
|
|
3357
|
+
// restored` — a reconnect Hello re-established the link that was previously lost.
|
|
3358
|
+
// `machine.runner.restarted` — the additional signal that the going-offline was
|
|
3359
|
+
// a self-update restart specifically (link.lost also fires for it; this
|
|
3360
|
+
// distinguishes a restart from a plain stop / host shutdown). All three hit the
|
|
3361
|
+
// timeline projection's quiet default tier (no rendered item); adding a rendered
|
|
3362
|
+
// item requires regenerating the golden snapshots (the golden-grammar gate).
|
|
3363
|
+
"machine.link.lost",
|
|
3364
|
+
"machine.link.restored",
|
|
3365
|
+
"machine.runner.restarted",
|
|
2293
3366
|
]);
|
|
2294
3367
|
export type SessionEventType = z.infer<typeof SessionEventType>;
|
|
2295
3368
|
|
|
3369
|
+
export const ToolAuthNeededPayload = z.object({
|
|
3370
|
+
serverId: z.string().min(1),
|
|
3371
|
+
toolName: z.string().min(1).nullable().optional(),
|
|
3372
|
+
providerDomain: z.string().min(1),
|
|
3373
|
+
connectionId: z.string().uuid().nullable().optional(),
|
|
3374
|
+
reason: z.enum(["missing_connection", "expired", "insufficient_scope", "refresh_failed"]),
|
|
3375
|
+
scopes: z.array(z.string().min(1)).optional(),
|
|
3376
|
+
resource: z.string().min(1).optional(),
|
|
3377
|
+
authorizationUrl: z.string().url().optional(),
|
|
3378
|
+
subjectId: z.string().min(1).nullable().optional(),
|
|
3379
|
+
});
|
|
3380
|
+
export type ToolAuthNeededPayload = z.infer<typeof ToolAuthNeededPayload>;
|
|
3381
|
+
|
|
2296
3382
|
// Channel-B stream-event payloads (07-channel-b §1.2). SessionEvent.payload is
|
|
2297
3383
|
// z.unknown() (NOT a discriminated union) — these are standalone schemas parsed
|
|
2298
3384
|
// explicitly at the producer (the API-direct handshake/rotation) and the SDK/
|
|
@@ -2416,13 +3502,17 @@ export type SandboxCommandOutputDeltaPayload = z.infer<typeof SandboxCommandOutp
|
|
|
2416
3502
|
export const FsChangeKind = z.enum(["created", "modified", "deleted", "renamed"]);
|
|
2417
3503
|
export type FsChangeKind = z.infer<typeof FsChangeKind>;
|
|
2418
3504
|
export const FsChangedPayload = z.object({
|
|
2419
|
-
changes: z
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
3505
|
+
changes: z
|
|
3506
|
+
.array(
|
|
3507
|
+
z.object({
|
|
3508
|
+
path: z.string(), // workspace-relative POSIX path
|
|
3509
|
+
kind: FsChangeKind,
|
|
3510
|
+
isDir: z.boolean().default(false),
|
|
3511
|
+
sizeBytes: z.number().int().nonnegative().nullable().default(null),
|
|
3512
|
+
oldPath: z.string().optional(), // for "renamed"
|
|
3513
|
+
}),
|
|
3514
|
+
)
|
|
3515
|
+
.min(1),
|
|
2426
3516
|
source: z.enum(["write", "watch", "agent"]).default("write"),
|
|
2427
3517
|
// Monotonic FS revision (per-lease, paired with leaseEpoch for staleness).
|
|
2428
3518
|
revision: z.number().int().nonnegative(),
|
|
@@ -2439,7 +3529,9 @@ export const GitChangedPayload = z.object({
|
|
|
2439
3529
|
ahead: z.number().int().nonnegative().default(0),
|
|
2440
3530
|
behind: z.number().int().nonnegative().default(0),
|
|
2441
3531
|
changedFileCount: z.number().int().nonnegative(),
|
|
2442
|
-
reason: z
|
|
3532
|
+
reason: z
|
|
3533
|
+
.enum(["commit", "checkout", "stage", "worktree", "fetch", "unknown"])
|
|
3534
|
+
.default("unknown"),
|
|
2443
3535
|
revision: z.number().int().nonnegative().default(0),
|
|
2444
3536
|
leaseEpoch: z.number().int().nonnegative().default(0),
|
|
2445
3537
|
});
|
|
@@ -2484,16 +3576,18 @@ export interface FsTreeNode {
|
|
|
2484
3576
|
children?: FsTreeNode[] | undefined;
|
|
2485
3577
|
truncated: boolean; // dir had more entries than the cap
|
|
2486
3578
|
}
|
|
2487
|
-
export const FsTreeNode: z.ZodType<FsTreeNode> = z.lazy(() =>
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
3579
|
+
export const FsTreeNode: z.ZodType<FsTreeNode> = z.lazy(() =>
|
|
3580
|
+
z.object({
|
|
3581
|
+
name: z.string(),
|
|
3582
|
+
path: z.string(),
|
|
3583
|
+
type: FsNodeType,
|
|
3584
|
+
sizeBytes: z.number().int().nonnegative().nullable(),
|
|
3585
|
+
mtimeMs: z.number().int().nonnegative().nullable(),
|
|
3586
|
+
mode: z.number().int().nullable(),
|
|
3587
|
+
children: z.array(FsTreeNode).optional(),
|
|
3588
|
+
truncated: z.boolean().default(false),
|
|
3589
|
+
}),
|
|
3590
|
+
) as z.ZodType<FsTreeNode>;
|
|
2497
3591
|
|
|
2498
3592
|
export const FsListRequest = z.object({
|
|
2499
3593
|
path: z.string().default(""), // "" = workspace root
|
|
@@ -2514,7 +3608,12 @@ export type FsEncoding = z.infer<typeof FsEncoding>;
|
|
|
2514
3608
|
export const FsReadRequest = z.object({
|
|
2515
3609
|
path: z.string(),
|
|
2516
3610
|
encoding: FsEncoding.default("utf8"),
|
|
2517
|
-
maxBytes: z
|
|
3611
|
+
maxBytes: z
|
|
3612
|
+
.number()
|
|
3613
|
+
.int()
|
|
3614
|
+
.positive()
|
|
3615
|
+
.max(25 * 1024 * 1024)
|
|
3616
|
+
.default(5 * 1024 * 1024),
|
|
2518
3617
|
});
|
|
2519
3618
|
export type FsReadRequest = z.infer<typeof FsReadRequest>;
|
|
2520
3619
|
export const FsReadResponse = z.object({
|
|
@@ -2578,7 +3677,15 @@ export type FsMkdirResponse = z.infer<typeof FsMkdirResponse>;
|
|
|
2578
3677
|
|
|
2579
3678
|
// --- A2 Git request/response (read-only; feeds Pierre diff/tree) -------------
|
|
2580
3679
|
export const GitFileStatusCode = z.enum([
|
|
2581
|
-
"added",
|
|
3680
|
+
"added",
|
|
3681
|
+
"modified",
|
|
3682
|
+
"deleted",
|
|
3683
|
+
"renamed",
|
|
3684
|
+
"copied",
|
|
3685
|
+
"untracked",
|
|
3686
|
+
"ignored",
|
|
3687
|
+
"conflicted",
|
|
3688
|
+
"typechange",
|
|
2582
3689
|
]);
|
|
2583
3690
|
export type GitFileStatusCode = z.infer<typeof GitFileStatusCode>;
|
|
2584
3691
|
export const GitFileStatus = z.object({
|
|
@@ -2645,7 +3752,12 @@ export const GitDiffRequest = z.object({
|
|
|
2645
3752
|
toRef: z.string().optional(),
|
|
2646
3753
|
pathspec: z.array(z.string()).default([]),
|
|
2647
3754
|
contextLines: z.number().int().min(0).max(10).default(3),
|
|
2648
|
-
maxBytesPerFile: z
|
|
3755
|
+
maxBytesPerFile: z
|
|
3756
|
+
.number()
|
|
3757
|
+
.int()
|
|
3758
|
+
.positive()
|
|
3759
|
+
.max(2 * 1024 * 1024)
|
|
3760
|
+
.default(512 * 1024),
|
|
2649
3761
|
});
|
|
2650
3762
|
export type GitDiffRequest = z.infer<typeof GitDiffRequest>;
|
|
2651
3763
|
export const GitDiffResponse = z.object({
|
|
@@ -2654,6 +3766,182 @@ export const GitDiffResponse = z.object({
|
|
|
2654
3766
|
});
|
|
2655
3767
|
export type GitDiffResponse = z.infer<typeof GitDiffResponse>;
|
|
2656
3768
|
|
|
3769
|
+
// ─── Workbench v2 turn-end workspace capture (dossier §10.1/§10.2) ────────────
|
|
3770
|
+
// A capture is a point-in-time snapshot of the session workspace's CHANGES,
|
|
3771
|
+
// probed live off the box at turn end (detectRepos → gitStatus/gitDiff → fsRead
|
|
3772
|
+
// after-images → fsList tree index). It is the cold/offline read source that
|
|
3773
|
+
// lets the workbench paint instantly with zero machine round-trips. Live always
|
|
3774
|
+
// wins when the box is warm; a capture is a labelled cache, never a replacement.
|
|
3775
|
+
// This is the shape the M1 worker writes and the M2 API serves inline.
|
|
3776
|
+
|
|
3777
|
+
// One touched file in the capture. `contentRef` is the content-addressed storage
|
|
3778
|
+
// key of its after-image blob (shared across revisions → the GC set-difference
|
|
3779
|
+
// key). Deleted / binary / >5MB (tooLarge) files carry no contentRef; the UI
|
|
3780
|
+
// renders "too large — open live" for tooLarge, and the diff hunks for the rest.
|
|
3781
|
+
export const WorkspaceCaptureFile = z.object({
|
|
3782
|
+
path: z.string(),
|
|
3783
|
+
status: GitFileStatusCode,
|
|
3784
|
+
// sha256 of the captured after-image bytes; null when deleted / tooLarge.
|
|
3785
|
+
hash: z.string().nullable(),
|
|
3786
|
+
// git blob sha of the HEAD version — the wake-on-edit flush guard (dossier
|
|
3787
|
+
// §10.1). null when the path is new/untracked (no HEAD blob).
|
|
3788
|
+
baseHash: z.string().nullable(),
|
|
3789
|
+
// Content-addressed storage key of the after-image; null when deleted /
|
|
3790
|
+
// tooLarge / binary (no inline content captured).
|
|
3791
|
+
contentRef: z.string().nullable(),
|
|
3792
|
+
sizeBytes: z.number().int().nonnegative(),
|
|
3793
|
+
isBinary: z.boolean().default(false),
|
|
3794
|
+
// >5MB per-file content guard tripped: content NOT captured, render "open live".
|
|
3795
|
+
tooLarge: z.boolean().default(false),
|
|
3796
|
+
deleted: z.boolean().default(false),
|
|
3797
|
+
});
|
|
3798
|
+
export type WorkspaceCaptureFile = z.infer<typeof WorkspaceCaptureFile>;
|
|
3799
|
+
|
|
3800
|
+
// One repo discovered in the workspace. `diff` is `git diff HEAD` (combined
|
|
3801
|
+
// staged+unstaged tracked changes vs HEAD — the review diff); `status` is the
|
|
3802
|
+
// full porcelain file list (drives the rail glyphs incl. untracked, which the
|
|
3803
|
+
// HEAD diff omits). root "" = the workspace root repo.
|
|
3804
|
+
export const WorkspaceCaptureRepo = z.object({
|
|
3805
|
+
root: z.string(),
|
|
3806
|
+
head: z.string().nullable(),
|
|
3807
|
+
detached: z.boolean().default(false),
|
|
3808
|
+
upstream: z.string().nullable(),
|
|
3809
|
+
ahead: z.number().int().nonnegative().default(0),
|
|
3810
|
+
behind: z.number().int().nonnegative().default(0),
|
|
3811
|
+
status: z.array(GitFileStatus),
|
|
3812
|
+
diff: z.array(GitFileDiff),
|
|
3813
|
+
});
|
|
3814
|
+
export type WorkspaceCaptureRepo = z.infer<typeof WorkspaceCaptureRepo>;
|
|
3815
|
+
|
|
3816
|
+
export const WorkspaceCaptureDegradedReason = z.enum([
|
|
3817
|
+
"repository_discovery_command_failed",
|
|
3818
|
+
"repository_discovery_timed_out",
|
|
3819
|
+
"repository_discovery_result_limit_exceeded",
|
|
3820
|
+
]);
|
|
3821
|
+
export type WorkspaceCaptureDegradedReason = z.infer<typeof WorkspaceCaptureDegradedReason>;
|
|
3822
|
+
|
|
3823
|
+
// Rollup counters — carried on the row (jsonb) and the announce event so the UI
|
|
3824
|
+
// can reserve layout (dossier §12 no-layout-shift) before fetching the manifest.
|
|
3825
|
+
export const WorkspaceCaptureStats = z.object({
|
|
3826
|
+
repoCount: z.number().int().nonnegative(),
|
|
3827
|
+
fileCount: z.number().int().nonnegative(),
|
|
3828
|
+
additions: z.number().int().nonnegative(),
|
|
3829
|
+
deletions: z.number().int().nonnegative(),
|
|
3830
|
+
totalBytes: z.number().int().nonnegative(),
|
|
3831
|
+
tooLargeCount: z.number().int().nonnegative(),
|
|
3832
|
+
binaryCount: z.number().int().nonnegative(),
|
|
3833
|
+
treeEntryCount: z.number().int().nonnegative(),
|
|
3834
|
+
treeTruncated: z.boolean().default(false),
|
|
3835
|
+
durationMs: z.number().int().nonnegative(),
|
|
3836
|
+
// sha256 over the change surface (per-file path/hash/status + per-repo diff
|
|
3837
|
+
// summary, tree/mtime excluded). The empty-turn gate skips a capture whose
|
|
3838
|
+
// fingerprint equals the previous revision's — "no new revision when nothing
|
|
3839
|
+
// changed" holds even when the tree stays dirty across read-only turns.
|
|
3840
|
+
fingerprint: z.string().optional(),
|
|
3841
|
+
});
|
|
3842
|
+
export type WorkspaceCaptureStats = z.infer<typeof WorkspaceCaptureStats>;
|
|
3843
|
+
|
|
3844
|
+
// The single manifest blob (one per revision). Holds everything the workbench
|
|
3845
|
+
// needs for a cold paint: the tree index, per-repo status+diff, and the file
|
|
3846
|
+
// index (after-image refs). The M2 API serves this inline when small (≤2MB).
|
|
3847
|
+
export const WorkspaceCaptureManifest = z.object({
|
|
3848
|
+
version: z.literal(1),
|
|
3849
|
+
revision: z.number().int().nonnegative(),
|
|
3850
|
+
capturedAt: z.string(),
|
|
3851
|
+
turnId: z.string().nullable(),
|
|
3852
|
+
leaseEpoch: z.number().int().nonnegative(),
|
|
3853
|
+
treeIndex: FsTreeNode,
|
|
3854
|
+
treeTruncated: z.boolean().default(false),
|
|
3855
|
+
repos: z.array(WorkspaceCaptureRepo),
|
|
3856
|
+
files: z.array(WorkspaceCaptureFile),
|
|
3857
|
+
stats: WorkspaceCaptureStats,
|
|
3858
|
+
});
|
|
3859
|
+
export type WorkspaceCaptureManifest = z.infer<typeof WorkspaceCaptureManifest>;
|
|
3860
|
+
|
|
3861
|
+
// Announce-only event payload (dossier §10.1). Metadata only — never content.
|
|
3862
|
+
export const WorkspaceRevisionCapturedPayload = z.object({
|
|
3863
|
+
revision: z.number().int().nonnegative(),
|
|
3864
|
+
turnId: z.string().nullable(),
|
|
3865
|
+
capturedAt: z.string(),
|
|
3866
|
+
leaseEpoch: z.number().int().nonnegative(),
|
|
3867
|
+
stats: WorkspaceCaptureStats,
|
|
3868
|
+
});
|
|
3869
|
+
export type WorkspaceRevisionCapturedPayload = z.infer<typeof WorkspaceRevisionCapturedPayload>;
|
|
3870
|
+
|
|
3871
|
+
export const WorkspaceRevisionDegradedPayload = z.object({
|
|
3872
|
+
revision: z.number().int().nonnegative(),
|
|
3873
|
+
turnId: z.string().nullable(),
|
|
3874
|
+
capturedAt: z.string(),
|
|
3875
|
+
leaseEpoch: z.number().int().nonnegative(),
|
|
3876
|
+
reason: WorkspaceCaptureDegradedReason,
|
|
3877
|
+
});
|
|
3878
|
+
export type WorkspaceRevisionDegradedPayload = z.infer<typeof WorkspaceRevisionDegradedPayload>;
|
|
3879
|
+
|
|
3880
|
+
// --- M2 capture READ API (dossier §10.3) -------------------------------------
|
|
3881
|
+
// A short-TTL signed GET URL minted PER REQUEST (never stored). The manifest is
|
|
3882
|
+
// served inline for the ≤2MB common case (the <200ms one-round-trip paint); a
|
|
3883
|
+
// >2MB manifest and a >256KB single-file after-image fall back to one of these.
|
|
3884
|
+
export const WorkspaceCaptureSignedUrl = z.object({
|
|
3885
|
+
url: z.string().url(),
|
|
3886
|
+
expiresAt: z.string(),
|
|
3887
|
+
});
|
|
3888
|
+
export type WorkspaceCaptureSignedUrl = z.infer<typeof WorkspaceCaptureSignedUrl>;
|
|
3889
|
+
|
|
3890
|
+
// GET …/sessions/:sid/workspace/capture. `{available:false}` when no capture row
|
|
3891
|
+
// exists yet (or its manifest blob was GC'd) — the client falls back to the
|
|
3892
|
+
// live/wake path (status-quo behavior, NEVER an error → served 200). When
|
|
3893
|
+
// available: the row metadata (revision/turn/epoch/stats/size) is always inline;
|
|
3894
|
+
// the manifest is inline (`manifest`) for the ≤2MB common case and a signed GET
|
|
3895
|
+
// URL (`manifestUrl`) above that. Exactly one of manifest/manifestUrl is non-null.
|
|
3896
|
+
export const GetWorkspaceCaptureResponse = z.discriminatedUnion("available", [
|
|
3897
|
+
z.object({
|
|
3898
|
+
available: z.literal(false),
|
|
3899
|
+
// Optional for additive compatibility with older servers. New servers set
|
|
3900
|
+
// these fields when the newest durable revision is an explicit degraded
|
|
3901
|
+
// marker rather than "no capture exists yet".
|
|
3902
|
+
degradedReason: WorkspaceCaptureDegradedReason.nullable().optional(),
|
|
3903
|
+
revision: z.number().int().nonnegative().nullable().optional(),
|
|
3904
|
+
capturedAt: z.string().nullable().optional(),
|
|
3905
|
+
turnId: z.string().nullable().optional(),
|
|
3906
|
+
leaseEpoch: z.number().int().nonnegative().nullable().optional(),
|
|
3907
|
+
}),
|
|
3908
|
+
z.object({
|
|
3909
|
+
available: z.literal(true),
|
|
3910
|
+
revision: z.number().int().nonnegative(),
|
|
3911
|
+
capturedAt: z.string(),
|
|
3912
|
+
turnId: z.string().nullable(),
|
|
3913
|
+
leaseEpoch: z.number().int().nonnegative(),
|
|
3914
|
+
sizeBytes: z.number().int().nonnegative(),
|
|
3915
|
+
stats: WorkspaceCaptureStats,
|
|
3916
|
+
manifest: WorkspaceCaptureManifest.nullable().default(null),
|
|
3917
|
+
manifestUrl: WorkspaceCaptureSignedUrl.nullable().default(null),
|
|
3918
|
+
}),
|
|
3919
|
+
]);
|
|
3920
|
+
export type GetWorkspaceCaptureResponse = z.infer<typeof GetWorkspaceCaptureResponse>;
|
|
3921
|
+
|
|
3922
|
+
// GET …/sessions/:sid/workspace/capture/file?path=…&revision=…. A single
|
|
3923
|
+
// after-image resolved from the (revision|latest) manifest. The file metadata
|
|
3924
|
+
// (from the manifest entry) is always present; `content` is inline for ≤256KB
|
|
3925
|
+
// (base64 for binary, utf8 otherwise), else a signed GET URL (`contentUrl`) to
|
|
3926
|
+
// the raw content-addressed blob. A tooLarge marker (or a captured file with no
|
|
3927
|
+
// content blob — e.g. the after-image was GC'd) returns metadata only, no
|
|
3928
|
+
// content and no URL. Path-not-in-manifest / deleted → 404 at the route (not
|
|
3929
|
+
// represented here).
|
|
3930
|
+
export const GetWorkspaceCaptureFileResponse = z.object({
|
|
3931
|
+
path: z.string(),
|
|
3932
|
+
revision: z.number().int().nonnegative(),
|
|
3933
|
+
status: GitFileStatusCode,
|
|
3934
|
+
hash: z.string().nullable(),
|
|
3935
|
+
baseHash: z.string().nullable(),
|
|
3936
|
+
sizeBytes: z.number().int().nonnegative(),
|
|
3937
|
+
isBinary: z.boolean(),
|
|
3938
|
+
tooLarge: z.boolean(),
|
|
3939
|
+
encoding: FsEncoding.nullable().default(null), // set iff content is inline
|
|
3940
|
+
content: z.string().nullable().default(null), // inline ≤256KB (per encoding)
|
|
3941
|
+
contentUrl: WorkspaceCaptureSignedUrl.nullable().default(null), // signed >256KB
|
|
3942
|
+
});
|
|
3943
|
+
export type GetWorkspaceCaptureFileResponse = z.infer<typeof GetWorkspaceCaptureFileResponse>;
|
|
3944
|
+
|
|
2657
3945
|
export const GitLogRequest = z.object({
|
|
2658
3946
|
path: z.string().default(""),
|
|
2659
3947
|
ref: z.string().default("HEAD"),
|
|
@@ -2681,13 +3969,25 @@ export const GitShowRequest = z.object({
|
|
|
2681
3969
|
ref: z.string(), // a commit/tag/tree-ish
|
|
2682
3970
|
filePath: z.string().optional(), // ref + filePath => raw blob ("open file at commit")
|
|
2683
3971
|
encoding: FsEncoding.default("utf8"),
|
|
2684
|
-
maxBytesPerFile: z
|
|
3972
|
+
maxBytesPerFile: z
|
|
3973
|
+
.number()
|
|
3974
|
+
.int()
|
|
3975
|
+
.positive()
|
|
3976
|
+
.max(2 * 1024 * 1024)
|
|
3977
|
+
.default(512 * 1024),
|
|
2685
3978
|
});
|
|
2686
3979
|
export type GitShowRequest = z.infer<typeof GitShowRequest>;
|
|
2687
3980
|
export const GitShowResponse = z.object({
|
|
2688
3981
|
commit: GitCommit.nullable(), // null when fetching a raw blob
|
|
2689
3982
|
files: z.array(GitFileDiff), // commit diff vs first parent
|
|
2690
|
-
blob: z
|
|
3983
|
+
blob: z
|
|
3984
|
+
.object({
|
|
3985
|
+
content: z.string(),
|
|
3986
|
+
encoding: FsEncoding,
|
|
3987
|
+
sizeBytes: z.number().int(),
|
|
3988
|
+
truncated: z.boolean(),
|
|
3989
|
+
})
|
|
3990
|
+
.nullable(),
|
|
2691
3991
|
revision: z.number().int().nonnegative(),
|
|
2692
3992
|
});
|
|
2693
3993
|
export type GitShowResponse = z.infer<typeof GitShowResponse>;
|
|
@@ -2735,7 +4035,11 @@ export const PtyOpenResponse = z.object({
|
|
|
2735
4035
|
export type PtyOpenResponse = z.infer<typeof PtyOpenResponse>;
|
|
2736
4036
|
export const PtyWriteRequest = z.object({ ptyId: z.string().uuid(), data: z.string() }); // utf-8 stdin
|
|
2737
4037
|
export type PtyWriteRequest = z.infer<typeof PtyWriteRequest>;
|
|
2738
|
-
export const PtyResizeRequest = z.object({
|
|
4038
|
+
export const PtyResizeRequest = z.object({
|
|
4039
|
+
ptyId: z.string().uuid(),
|
|
4040
|
+
cols: z.number().int().positive(),
|
|
4041
|
+
rows: z.number().int().positive(),
|
|
4042
|
+
});
|
|
2739
4043
|
export type PtyResizeRequest = z.infer<typeof PtyResizeRequest>;
|
|
2740
4044
|
export const PtyCloseRequest = z.object({ ptyId: z.string().uuid() });
|
|
2741
4045
|
export type PtyCloseRequest = z.infer<typeof PtyCloseRequest>;
|
|
@@ -2764,10 +4068,36 @@ export const SessionEvent = z.object({
|
|
|
2764
4068
|
occurredAt: z.string(),
|
|
2765
4069
|
clientEventId: z.string().min(1).nullable().optional(),
|
|
2766
4070
|
turnId: z.string().uuid().nullable().optional(),
|
|
4071
|
+
turnGeneration: z.number().int().nonnegative().nullable().optional(),
|
|
4072
|
+
turnAttemptId: z.string().uuid().nullable().optional(),
|
|
4073
|
+
turnAssociation: z.enum(["current", "late_rejected", "duplicate"]).nullable().optional(),
|
|
4074
|
+
duplicateOfEventId: z.string().uuid().nullable().optional(),
|
|
4075
|
+
duplicateReason: z.string().min(1).nullable().optional(),
|
|
2767
4076
|
});
|
|
2768
4077
|
export type SessionEvent = z.infer<typeof SessionEvent>;
|
|
2769
4078
|
|
|
2770
|
-
export const
|
|
4079
|
+
export const SessionQueueMutationResponse = z.object({
|
|
4080
|
+
snapshot: SessionQueueSnapshot,
|
|
4081
|
+
events: z.array(SessionEvent),
|
|
4082
|
+
shouldWake: z.boolean(),
|
|
4083
|
+
});
|
|
4084
|
+
export type SessionQueueMutationResponse = z.infer<typeof SessionQueueMutationResponse>;
|
|
4085
|
+
|
|
4086
|
+
export const SessionControlResponse = z.object({
|
|
4087
|
+
operationId: z.string().uuid(),
|
|
4088
|
+
event: SessionEvent,
|
|
4089
|
+
controlState: SessionControlState,
|
|
4090
|
+
controlGeneration: z.number().int().nonnegative(),
|
|
4091
|
+
expectedActiveTurnId: z.string().uuid().nullable(),
|
|
4092
|
+
expectedExecutionGeneration: z.number().int().nonnegative().nullable(),
|
|
4093
|
+
expectedAttemptId: z.string().uuid().nullable(),
|
|
4094
|
+
deliveryEventId: z.string().uuid().nullable(),
|
|
4095
|
+
shouldSignalControl: z.boolean(),
|
|
4096
|
+
shouldWake: z.boolean(),
|
|
4097
|
+
});
|
|
4098
|
+
export type SessionControlResponse = z.infer<typeof SessionControlResponse>;
|
|
4099
|
+
|
|
4100
|
+
export const CreateSessionRequest = withVariableSetIdAlias({
|
|
2771
4101
|
initialMessage: z.string().min(1),
|
|
2772
4102
|
// Per-session agent persona/system instructions (org-visible metadata, NOT a
|
|
2773
4103
|
// secret). Rides the SAME system-level instructions channel the per-workspace
|
|
@@ -2776,7 +4106,7 @@ export const CreateSessionRequest = z.object({
|
|
|
2776
4106
|
// leaking them into the user-visible timeline (it is NEVER emitted as an
|
|
2777
4107
|
// event, unlike goal/initialMessage). Trimmed, non-empty. The 32768-char cap
|
|
2778
4108
|
// matches the codebase's largest free-form string convention (workspace
|
|
2779
|
-
//
|
|
4109
|
+
// variable set variable values). Absent ⇒ byte-identical to today.
|
|
2780
4110
|
instructions: z.string().trim().min(1).max(32768).optional(),
|
|
2781
4111
|
resources: z.array(ResourceRef).default([]),
|
|
2782
4112
|
tools: z.array(ToolRef).default([]),
|
|
@@ -2795,9 +4125,15 @@ export const CreateSessionRequest = z.object({
|
|
|
2795
4125
|
// (the agent's resolve_cwd handles both). Only valid WITH targetSandboxId
|
|
2796
4126
|
// (workingDir alone is a 422); omitted ⇒ the machine's default workspace_root.
|
|
2797
4127
|
workingDir: z.string().min(1).optional(),
|
|
2798
|
-
//
|
|
4128
|
+
// Variable set attachment is fixed at session creation; follow-up
|
|
2799
4129
|
// user.message events cannot switch or add one.
|
|
4130
|
+
variableSetId: z.string().uuid().optional(),
|
|
2800
4131
|
environmentId: z.string().uuid().optional(),
|
|
4132
|
+
// The rig to bind this session to (M3). Its ACTIVE version is resolved and
|
|
4133
|
+
// FROZEN onto the session at create. Omitted ⇒ the workspace's default rig
|
|
4134
|
+
// (workspaces.default_rig_id) when set, else a rig-less session (today's
|
|
4135
|
+
// behavior). An id that does not name a rig in the workspace is a 422.
|
|
4136
|
+
rigId: z.string().uuid().optional(),
|
|
2801
4137
|
goal: GoalSpec.optional(),
|
|
2802
4138
|
clientEventId: z.string().min(1).optional(),
|
|
2803
4139
|
// Workspace-scoped CREATE idempotency key: collapses concurrent/retried
|
|
@@ -2809,7 +4145,7 @@ export const CreateSessionRequest = z.object({
|
|
|
2809
4145
|
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
2810
4146
|
// Permissions the session's first-party MCP token should carry instead of
|
|
2811
4147
|
// the fixed worker default — how an operator hands a manager-style session
|
|
2812
|
-
// the orchestration/
|
|
4148
|
+
// the orchestration/variableSet/github tools. Capped at creation: every
|
|
2813
4149
|
// requested permission must be held by the creating grant (no escalation).
|
|
2814
4150
|
firstPartyMcpPermissions: z.array(Permission).optional(),
|
|
2815
4151
|
// Third-party MCP servers attached only to this session. Credential headers are
|
|
@@ -2827,16 +4163,14 @@ export const CreateSessionRequest = z.object({
|
|
|
2827
4163
|
// A shared spawn inherits the box's (backend, os) — it is literally the same
|
|
2828
4164
|
// box; the child cannot pick its own backend. Cross-workspace sharing is
|
|
2829
4165
|
// forbidden by construction (the parent/group reads are RLS-workspace-scoped).
|
|
2830
|
-
// ENV-AWARE: the box's
|
|
2831
|
-
// the SAME
|
|
4166
|
+
// ENV-AWARE: the box's variable set is fixed at creation, so a share requires
|
|
4167
|
+
// the SAME variableSetId as the creator's box. On a mismatch the inherited
|
|
2832
4168
|
// default silently falls back to an own box; an explicit "shared"/{groupId}
|
|
2833
4169
|
// request 422s at create (instead of the first turn dying on the SDK's
|
|
2834
4170
|
// manifest-env guard).
|
|
2835
|
-
sandbox: z
|
|
2836
|
-
z.literal("shared"),
|
|
2837
|
-
|
|
2838
|
-
z.object({ groupId: z.string().uuid() }),
|
|
2839
|
-
]).optional(),
|
|
4171
|
+
sandbox: z
|
|
4172
|
+
.union([z.literal("shared"), z.literal("new"), z.object({ groupId: z.string().uuid() })])
|
|
4173
|
+
.optional(),
|
|
2840
4174
|
});
|
|
2841
4175
|
export type CreateSessionRequest = z.infer<typeof CreateSessionRequest>;
|
|
2842
4176
|
|
|
@@ -2855,11 +4189,6 @@ export const ClientSessionEvent = z.discriminatedUnion("type", [
|
|
|
2855
4189
|
mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
|
|
2856
4190
|
}),
|
|
2857
4191
|
}),
|
|
2858
|
-
z.object({
|
|
2859
|
-
type: z.literal("user.interrupt"),
|
|
2860
|
-
clientEventId: z.string().min(1).optional(),
|
|
2861
|
-
payload: z.object({ reason: z.string().optional() }).default({}),
|
|
2862
|
-
}),
|
|
2863
4192
|
z.object({
|
|
2864
4193
|
type: z.literal("user.approvalDecision"),
|
|
2865
4194
|
clientEventId: z.string().min(1).optional(),
|
|
@@ -2872,6 +4201,25 @@ export const ClientSessionEvent = z.discriminatedUnion("type", [
|
|
|
2872
4201
|
]);
|
|
2873
4202
|
export type ClientSessionEvent = z.infer<typeof ClientSessionEvent>;
|
|
2874
4203
|
|
|
4204
|
+
export const SteerSessionMessageRequest = z.object({
|
|
4205
|
+
text: z.string().min(1),
|
|
4206
|
+
resources: z.array(ResourceRef).default([]),
|
|
4207
|
+
tools: z.array(ToolRef).default([]),
|
|
4208
|
+
model: z.string().min(1).optional(),
|
|
4209
|
+
reasoningEffort: ReasoningEffort.optional(),
|
|
4210
|
+
clientEventId: z.string().min(1).optional(),
|
|
4211
|
+
expectedControlGeneration: z.number().int().nonnegative().optional(),
|
|
4212
|
+
expectedWorkspaceInferenceGeneration: z.number().int().nonnegative().optional(),
|
|
4213
|
+
mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
|
|
4214
|
+
});
|
|
4215
|
+
export type SteerSessionMessageRequest = z.infer<typeof SteerSessionMessageRequest>;
|
|
4216
|
+
|
|
4217
|
+
export const SteerSessionMessageResponse = z.object({
|
|
4218
|
+
accepted: SessionEvent,
|
|
4219
|
+
turn: SessionTurn,
|
|
4220
|
+
});
|
|
4221
|
+
export type SteerSessionMessageResponse = z.infer<typeof SteerSessionMessageResponse>;
|
|
4222
|
+
|
|
2875
4223
|
export const SessionBusMessage = z.object({
|
|
2876
4224
|
workspaceId: z.string().uuid(),
|
|
2877
4225
|
sessionId: z.string().uuid(),
|
|
@@ -3175,7 +4523,13 @@ export const DeviceEnrollmentPollRequest = z.object({
|
|
|
3175
4523
|
});
|
|
3176
4524
|
export type DeviceEnrollmentPollRequest = z.infer<typeof DeviceEnrollmentPollRequest>;
|
|
3177
4525
|
|
|
3178
|
-
export const DeviceEnrollmentState = z.enum([
|
|
4526
|
+
export const DeviceEnrollmentState = z.enum([
|
|
4527
|
+
"pending",
|
|
4528
|
+
"authorized",
|
|
4529
|
+
"denied",
|
|
4530
|
+
"expired",
|
|
4531
|
+
"disabled",
|
|
4532
|
+
]);
|
|
3179
4533
|
export type DeviceEnrollmentState = z.infer<typeof DeviceEnrollmentState>;
|
|
3180
4534
|
|
|
3181
4535
|
// The EnrollmentCredentials (field names match the proto's JSON). natsAccountCreds
|
|
@@ -3227,6 +4581,11 @@ export const EnrollmentSummary = z.object({
|
|
|
3227
4581
|
pubkey: z.string(),
|
|
3228
4582
|
exposure: z.literal("whole-machine"),
|
|
3229
4583
|
hasDisplay: z.boolean(),
|
|
4584
|
+
// Present (non-null) only when a display EXISTS but capture is blocked (macOS
|
|
4585
|
+
// Screen Recording / TCC not granted): a human, actionable reason so the UI can
|
|
4586
|
+
// show "display: capture not granted" instead of a bare "headless". null == capture
|
|
4587
|
+
// permitted OR genuinely headless.
|
|
4588
|
+
desktopUnavailableReason: z.string().nullish(),
|
|
3230
4589
|
allowScreenControl: z.boolean(),
|
|
3231
4590
|
status: z.enum(["active", "revoked"]),
|
|
3232
4591
|
os: EnrollmentOs,
|
|
@@ -3407,6 +4766,10 @@ export const MachineView = z.object({
|
|
|
3407
4766
|
os: z.string(),
|
|
3408
4767
|
arch: z.string(),
|
|
3409
4768
|
hasDisplay: z.boolean(),
|
|
4769
|
+
// Non-null only when a display exists but capture is blocked (macOS Screen
|
|
4770
|
+
// Recording / TCC not granted) — the UI can surface "display: capture not granted".
|
|
4771
|
+
// null == capture permitted OR headless.
|
|
4772
|
+
desktopUnavailableReason: z.string().nullish(),
|
|
3410
4773
|
allowScreenControl: z.boolean(),
|
|
3411
4774
|
sharedSessionCount: z.number().int(),
|
|
3412
4775
|
lastSeenAt: z.string().nullable(),
|
|
@@ -3448,6 +4811,19 @@ export const SwapActiveSandboxResponse = z.object({
|
|
|
3448
4811
|
activeSandboxId: z.string().nullable(),
|
|
3449
4812
|
activeEpoch: z.number().int(),
|
|
3450
4813
|
reason: z.string().optional(),
|
|
4814
|
+
// Typed rejection discriminant (issue #341). Present only when swapped is false,
|
|
4815
|
+
// so a client distinguishes a deleted/absent target from an unaddressable
|
|
4816
|
+
// enrollment from a backend the turn cannot establish from a lost epoch race —
|
|
4817
|
+
// without parsing the human reason string.
|
|
4818
|
+
code: z
|
|
4819
|
+
.enum([
|
|
4820
|
+
"stale_pointer",
|
|
4821
|
+
"offline_enrollment",
|
|
4822
|
+
"unsupported_backend_context",
|
|
4823
|
+
"transient_establishment",
|
|
4824
|
+
"concurrent_swap",
|
|
4825
|
+
])
|
|
4826
|
+
.optional(),
|
|
3451
4827
|
});
|
|
3452
4828
|
export type SwapActiveSandboxResponse = z.infer<typeof SwapActiveSandboxResponse>;
|
|
3453
4829
|
|
|
@@ -3470,7 +4846,7 @@ export type MachineMetricsSeriesResponse = z.infer<typeof MachineMetricsSeriesRe
|
|
|
3470
4846
|
export const ClientModel = z.object({
|
|
3471
4847
|
id: z.string(),
|
|
3472
4848
|
label: z.string(),
|
|
3473
|
-
provider: z.string(),
|
|
4849
|
+
provider: z.string(), // provider id
|
|
3474
4850
|
providerLabel: z.string(),
|
|
3475
4851
|
api: z.enum(["responses", "chat"]),
|
|
3476
4852
|
contextWindowTokens: z.number().int().positive().optional(),
|
|
@@ -3490,10 +4866,14 @@ export const ClientConfig = z.object({
|
|
|
3490
4866
|
models: z.array(ClientModel).default([]),
|
|
3491
4867
|
defaultReasoningEffort: ReasoningEffort,
|
|
3492
4868
|
allowedReasoningEfforts: z.array(ReasoningEffort).min(1),
|
|
3493
|
-
mcpServers: z
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
4869
|
+
mcpServers: z
|
|
4870
|
+
.array(
|
|
4871
|
+
z.object({
|
|
4872
|
+
id: z.string(),
|
|
4873
|
+
name: z.string(),
|
|
4874
|
+
}),
|
|
4875
|
+
)
|
|
4876
|
+
.default([]),
|
|
3497
4877
|
fileUploads: z.object({
|
|
3498
4878
|
enabled: z.boolean(),
|
|
3499
4879
|
maxSizeBytes: z.number().int().positive(),
|
|
@@ -3504,11 +4884,13 @@ export const ClientConfig = z.object({
|
|
|
3504
4884
|
// at all (P4.4). Per-session availability is negotiated on /stream-capabilities
|
|
3505
4885
|
// (it depends on the session's pinned backend); this is the coarse on/off the
|
|
3506
4886
|
// client uses to decide whether to even attempt the fs/git/terminal panels.
|
|
3507
|
-
structuredServices: z
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
4887
|
+
structuredServices: z
|
|
4888
|
+
.object({
|
|
4889
|
+
fileSystem: z.boolean(),
|
|
4890
|
+
git: z.boolean(),
|
|
4891
|
+
terminalEvents: z.boolean(),
|
|
4892
|
+
})
|
|
4893
|
+
.default({ fileSystem: false, git: false, terminalEvents: false }),
|
|
3512
4894
|
});
|
|
3513
4895
|
export type ClientConfig = z.infer<typeof ClientConfig>;
|
|
3514
4896
|
|
|
@@ -3547,6 +4929,42 @@ function constantTimeEqual(actual: string, expected: string): boolean {
|
|
|
3547
4929
|
|
|
3548
4930
|
export type HealthResponse = {
|
|
3549
4931
|
service: string;
|
|
3550
|
-
|
|
4932
|
+
variableSet: string;
|
|
3551
4933
|
ok: boolean;
|
|
3552
4934
|
};
|
|
4935
|
+
|
|
4936
|
+
/**
|
|
4937
|
+
* Per-workspace model/provider availability policy (see @opengeni/db
|
|
4938
|
+
* workspace_model_policies). NULL fields = unrestricted; a non-null
|
|
4939
|
+
* allowedProviders is a strict allowlist over RESOLVED provider identities
|
|
4940
|
+
* (the built-in OpenAI/Azure client's id — "openai"/"azure" — including the
|
|
4941
|
+
* legacy null-resolution fallback; "codex-subscription" for the ChatGPT/Codex
|
|
4942
|
+
* overlay; registry providers by their declared ids); a non-null allowedModels
|
|
4943
|
+
* is an additional exact-model-id allowlist. Pure and shared so the API edge
|
|
4944
|
+
* (422) and the worker's authoritative post-resolution gate (fail-loud
|
|
4945
|
+
* turn.failed, never a silent remap) can never disagree on semantics.
|
|
4946
|
+
*/
|
|
4947
|
+
export type WorkspaceModelPolicyContract = {
|
|
4948
|
+
allowedProviders: string[] | null;
|
|
4949
|
+
allowedModels: string[] | null;
|
|
4950
|
+
};
|
|
4951
|
+
|
|
4952
|
+
export type WorkspaceModelPolicyVerdict =
|
|
4953
|
+
| { allowed: true }
|
|
4954
|
+
| { allowed: false; reason: "provider" | "model" };
|
|
4955
|
+
|
|
4956
|
+
export function evaluateWorkspaceModelPolicy(
|
|
4957
|
+
policy: WorkspaceModelPolicyContract | null | undefined,
|
|
4958
|
+
candidate: { providerId: string; modelId: string },
|
|
4959
|
+
): WorkspaceModelPolicyVerdict {
|
|
4960
|
+
if (!policy) {
|
|
4961
|
+
return { allowed: true };
|
|
4962
|
+
}
|
|
4963
|
+
if (policy.allowedProviders !== null && !policy.allowedProviders.includes(candidate.providerId)) {
|
|
4964
|
+
return { allowed: false, reason: "provider" };
|
|
4965
|
+
}
|
|
4966
|
+
if (policy.allowedModels !== null && !policy.allowedModels.includes(candidate.modelId)) {
|
|
4967
|
+
return { allowed: false, reason: "model" };
|
|
4968
|
+
}
|
|
4969
|
+
return { allowed: true };
|
|
4970
|
+
}
|