@opengeni/contracts 0.9.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/dist/index.d.ts +2861 -289
- package/dist/index.js +984 -151
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/index.ts +1392 -243
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
|
]);
|
|
@@ -472,8 +475,12 @@ export const Permission = z.enum([
|
|
|
472
475
|
"api_keys:manage",
|
|
473
476
|
"connections:read",
|
|
474
477
|
"connections:write",
|
|
478
|
+
/** @deprecated alias of variable-sets:manage */
|
|
475
479
|
"environments:manage",
|
|
480
|
+
/** @deprecated alias of variable-sets:use */
|
|
476
481
|
"environments:use",
|
|
482
|
+
"variable-sets:manage",
|
|
483
|
+
"variable-sets:use",
|
|
477
484
|
// Attach or rotate per-session third-party MCP server credentials. Deliberately
|
|
478
485
|
// not part of the worker's default first-party MCP permission set: a sandboxed
|
|
479
486
|
// agent must not be able to hand itself new bearer credentials.
|
|
@@ -490,6 +497,13 @@ export const Permission = z.enum([
|
|
|
490
497
|
// admin-shaped action. workspace:admin is the super-wildcard over both.
|
|
491
498
|
"enrollments:read",
|
|
492
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",
|
|
493
507
|
]);
|
|
494
508
|
export type Permission = z.infer<typeof Permission>;
|
|
495
509
|
|
|
@@ -532,14 +546,66 @@ export const Workspace = z.object({
|
|
|
532
546
|
// Per-workspace agent persona template (white-label override). null means
|
|
533
547
|
// the deployment default (OPENGENI_AGENT_INSTRUCTIONS_TEMPLATE /
|
|
534
548
|
// DEFAULT_AGENT_INSTRUCTIONS) is used. The runtime always injects the
|
|
535
|
-
// non-bypassable CORE (goal-loop ownership +
|
|
549
|
+
// non-bypassable CORE (goal-loop ownership + variableSet block), so an
|
|
536
550
|
// override restyles the persona without dropping that contract.
|
|
537
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(),
|
|
538
563
|
createdAt: z.string(),
|
|
539
564
|
updatedAt: z.string(),
|
|
540
565
|
});
|
|
541
566
|
export type Workspace = z.infer<typeof Workspace>;
|
|
542
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
|
+
|
|
543
609
|
export const AccountGrant = z.object({
|
|
544
610
|
accountId: z.string().uuid(),
|
|
545
611
|
subjectId: z.string().min(1),
|
|
@@ -580,17 +646,32 @@ export const DelegatedAccessTokenPayload = z.object({
|
|
|
580
646
|
// Worker-asserted session scope for first-party MCP calls (HMAC-signed, not
|
|
581
647
|
// agent-controlled); enables session-scoped tools such as goal management.
|
|
582
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(),
|
|
583
655
|
exp: z.number().int().positive(),
|
|
584
656
|
});
|
|
585
657
|
export type DelegatedAccessTokenPayload = z.infer<typeof DelegatedAccessTokenPayload>;
|
|
586
658
|
|
|
587
|
-
export async function signDelegatedAccessToken(
|
|
588
|
-
|
|
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
|
+
);
|
|
589
666
|
const signature = await hmacSha256Base64Url(secret, encodedPayload);
|
|
590
667
|
return `ogd_${encodedPayload}.${signature}`;
|
|
591
668
|
}
|
|
592
669
|
|
|
593
|
-
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> {
|
|
594
675
|
if (!token.startsWith("ogd_")) {
|
|
595
676
|
return null;
|
|
596
677
|
}
|
|
@@ -605,7 +686,9 @@ export async function verifyDelegatedAccessToken(secret: string, token: string,
|
|
|
605
686
|
if (!constantTimeEqual(signature, expected)) {
|
|
606
687
|
return null;
|
|
607
688
|
}
|
|
608
|
-
const payload = DelegatedAccessTokenPayload.safeParse(
|
|
689
|
+
const payload = DelegatedAccessTokenPayload.safeParse(
|
|
690
|
+
JSON.parse(base64UrlDecode(encodedPayload)),
|
|
691
|
+
);
|
|
609
692
|
if (!payload.success || payload.data.exp < nowSeconds) {
|
|
610
693
|
return null;
|
|
611
694
|
}
|
|
@@ -633,13 +716,20 @@ export const EnrollmentBearerPayload = z.object({
|
|
|
633
716
|
});
|
|
634
717
|
export type EnrollmentBearerPayload = z.infer<typeof EnrollmentBearerPayload>;
|
|
635
718
|
|
|
636
|
-
export async function signEnrollmentBearer(
|
|
719
|
+
export async function signEnrollmentBearer(
|
|
720
|
+
secret: string,
|
|
721
|
+
payload: EnrollmentBearerPayload,
|
|
722
|
+
): Promise<string> {
|
|
637
723
|
const encodedPayload = base64UrlEncode(JSON.stringify(EnrollmentBearerPayload.parse(payload)));
|
|
638
724
|
const signature = await hmacSha256Base64Url(secret, encodedPayload);
|
|
639
725
|
return `oge_${encodedPayload}.${signature}`;
|
|
640
726
|
}
|
|
641
727
|
|
|
642
|
-
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> {
|
|
643
733
|
if (!token.startsWith("oge_")) {
|
|
644
734
|
return null;
|
|
645
735
|
}
|
|
@@ -689,7 +779,10 @@ export const EnrollTokenPayload = z.object({
|
|
|
689
779
|
});
|
|
690
780
|
export type EnrollTokenPayload = z.infer<typeof EnrollTokenPayload>;
|
|
691
781
|
|
|
692
|
-
export async function signEnrollToken(
|
|
782
|
+
export async function signEnrollToken(
|
|
783
|
+
secret: string,
|
|
784
|
+
payload: EnrollTokenPayload,
|
|
785
|
+
): Promise<string> {
|
|
693
786
|
const encodedPayload = base64UrlEncode(JSON.stringify(EnrollTokenPayload.parse(payload)));
|
|
694
787
|
const signature = await hmacSha256Base64Url(secret, encodedPayload);
|
|
695
788
|
return `oget_${encodedPayload}.${signature}`;
|
|
@@ -703,7 +796,11 @@ export async function signEnrollToken(secret: string, payload: EnrollTokenPayloa
|
|
|
703
796
|
* bearer fails the prefix gate; a same-secret token that lacks the typ claim fails
|
|
704
797
|
* the schema gate — both halves of the domain separation are enforced here.
|
|
705
798
|
*/
|
|
706
|
-
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> {
|
|
707
804
|
if (!token.startsWith("oget_")) {
|
|
708
805
|
return null;
|
|
709
806
|
}
|
|
@@ -765,7 +862,10 @@ export const StreamTokenPayload = z.object({
|
|
|
765
862
|
});
|
|
766
863
|
export type StreamTokenPayload = z.infer<typeof StreamTokenPayload>;
|
|
767
864
|
|
|
768
|
-
export async function signStreamToken(
|
|
865
|
+
export async function signStreamToken(
|
|
866
|
+
secret: string,
|
|
867
|
+
payload: StreamTokenPayload,
|
|
868
|
+
): Promise<string> {
|
|
769
869
|
const encodedPayload = base64UrlEncode(JSON.stringify(StreamTokenPayload.parse(payload)));
|
|
770
870
|
const signature = await hmacSha256Base64Url(secret, encodedPayload);
|
|
771
871
|
return `ogs_${encodedPayload}.${signature}`;
|
|
@@ -781,7 +881,11 @@ export async function signStreamToken(secret: string, payload: StreamTokenPayloa
|
|
|
781
881
|
* lease + route params — verify proves the token is authentic + unexpired, the
|
|
782
882
|
* caller proves it is for THIS box's current epoch and THIS workspace+session.
|
|
783
883
|
*/
|
|
784
|
-
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> {
|
|
785
889
|
if (!token.startsWith("ogs_")) {
|
|
786
890
|
return null;
|
|
787
891
|
}
|
|
@@ -858,7 +962,11 @@ export async function signRelayToken(secret: string, payload: RelayTokenPayload)
|
|
|
858
962
|
* The channel-key scope (claim.workspaceId/agentId vs the StreamOpen channel key)
|
|
859
963
|
* is enforced by the relay at USE — verify proves authenticity + freshness only.
|
|
860
964
|
*/
|
|
861
|
-
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> {
|
|
862
970
|
if (!token.startsWith("ogr_")) {
|
|
863
971
|
return null;
|
|
864
972
|
}
|
|
@@ -1064,7 +1172,12 @@ export type LimitDecision = z.infer<typeof LimitDecision>;
|
|
|
1064
1172
|
// the admitted quantity so a PULL host can grant a partial allowance.
|
|
1065
1173
|
export const EntitlementDecision = z.discriminatedUnion("allowed", [
|
|
1066
1174
|
z.object({ allowed: z.literal(true), quantity: z.number().optional() }),
|
|
1067
|
-
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
|
+
}),
|
|
1068
1181
|
]);
|
|
1069
1182
|
export type EntitlementDecision = z.infer<typeof EntitlementDecision>;
|
|
1070
1183
|
|
|
@@ -1079,16 +1192,32 @@ export type EntitlementsPort = {
|
|
|
1079
1192
|
admitRun(input: AdmitRunInput): Promise<EntitlementDecision>;
|
|
1080
1193
|
};
|
|
1081
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
|
+
|
|
1082
1211
|
// ============ P4a — Connection-credential provider (§7.6) ============
|
|
1083
1212
|
//
|
|
1084
1213
|
// The host-providable per-run credential-mint seam over OpenGeni's TWO
|
|
1085
1214
|
// run-scoped credential sites in the worker:
|
|
1086
|
-
// - GIT credentials:
|
|
1087
|
-
// `sandboxEnvironmentForRun` (
|
|
1088
|
-
//
|
|
1089
|
-
//
|
|
1090
|
-
// - SANDBOX secrets: the decrypted
|
|
1091
|
-
// `
|
|
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
|
|
1092
1221
|
// `environmentsEncryptionKeyBytes(settings)`).
|
|
1093
1222
|
//
|
|
1094
1223
|
// In embedded/separate topologies the HOST owns these external connections
|
|
@@ -1100,28 +1229,41 @@ export type EntitlementsPort = {
|
|
|
1100
1229
|
// FORK-7 CROSS-CHECK (the host-mapping safety guardrail): a credential
|
|
1101
1230
|
// provider returns the `workspaceId` it scoped the credential to, and the
|
|
1102
1231
|
// activity ASSERTS it agrees with the run's workspace BEFORE injecting
|
|
1103
|
-
//
|
|
1232
|
+
// any git provider token seed (or applying decrypted environment values). A host mapping bug that
|
|
1104
1233
|
// returns tenant B's creds while the run is tenant A is thereby caught at the
|
|
1105
1234
|
// seam, never silently injected into tenant A's sandbox.
|
|
1106
1235
|
|
|
1107
1236
|
export type GitCredentialsRequest = {
|
|
1108
1237
|
accountId: string;
|
|
1109
1238
|
workspaceId: string;
|
|
1110
|
-
//
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
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.
|
|
1113
1251
|
installationId: number;
|
|
1114
1252
|
repositoryIds: number[];
|
|
1115
1253
|
};
|
|
1116
1254
|
|
|
1117
1255
|
export type GitCredentials = {
|
|
1118
|
-
// The minted
|
|
1119
|
-
//
|
|
1120
|
-
//
|
|
1121
|
-
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;
|
|
1122
1260
|
// FORK-7 echo: the workspace the provider scoped this token to. The activity
|
|
1123
1261
|
// asserts `workspaceId === request.workspaceId` before injecting.
|
|
1124
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;
|
|
1125
1267
|
// Optional git identity override. When omitted the activity falls back to
|
|
1126
1268
|
// today's `githubAppBotIdentity(settings)`.
|
|
1127
1269
|
identity?: { name: string; email: string } | null;
|
|
@@ -1130,20 +1272,20 @@ export type GitCredentials = {
|
|
|
1130
1272
|
export type SandboxSecretsRequest = {
|
|
1131
1273
|
accountId: string;
|
|
1132
1274
|
workspaceId: string;
|
|
1133
|
-
// The
|
|
1275
|
+
// The variable set the run's session declares (null = unattached;
|
|
1134
1276
|
// the provider, like the self-mint path, returns null values for it).
|
|
1135
|
-
|
|
1277
|
+
variableSetId: string;
|
|
1136
1278
|
};
|
|
1137
1279
|
|
|
1138
1280
|
export type SandboxSecrets = {
|
|
1139
|
-
// The decrypted
|
|
1281
|
+
// The decrypted variableSet values the run injects, replacing the local
|
|
1140
1282
|
// `environmentsEncryptionKeyBytes` decrypt. Same shape the self-mint path
|
|
1141
1283
|
// produces (plaintext name→value).
|
|
1142
1284
|
values: Record<string, string>;
|
|
1143
1285
|
// FORK-7 echo: the workspace the provider scoped these secrets to.
|
|
1144
1286
|
workspaceId: string;
|
|
1145
|
-
// Optional
|
|
1146
|
-
//
|
|
1287
|
+
// Optional variableSet metadata; when omitted the activity uses the
|
|
1288
|
+
// variableSetId as both id and name (the local decrypt carries the row's
|
|
1147
1289
|
// id/name/description, but only `id` is load-bearing downstream).
|
|
1148
1290
|
id?: string;
|
|
1149
1291
|
name?: string;
|
|
@@ -1154,8 +1296,8 @@ export type ConnectionCredentialsPort = {
|
|
|
1154
1296
|
// Both legs are optional: a host may drive ONLY git creds (BYO-GitHub-App)
|
|
1155
1297
|
// and leave sandbox secrets to OpenGeni's local decrypt, or vice-versa. An
|
|
1156
1298
|
// unset leg falls through to today's self-mint for THAT leg only.
|
|
1157
|
-
gitCredentials
|
|
1158
|
-
sandboxSecrets
|
|
1299
|
+
gitCredentials?(input: GitCredentialsRequest): Promise<GitCredentials>;
|
|
1300
|
+
sandboxSecrets?(input: SandboxSecretsRequest): Promise<SandboxSecrets>;
|
|
1159
1301
|
};
|
|
1160
1302
|
|
|
1161
1303
|
// ============ P4a — GitHub App API port (BYO-App, §7.6 / SPIKE-2 remainder) ===
|
|
@@ -1187,9 +1329,7 @@ export type GitHubAppApiPort = {
|
|
|
1187
1329
|
code: string;
|
|
1188
1330
|
installationId: number;
|
|
1189
1331
|
}) => Promise<GitHubInstallationSummary>;
|
|
1190
|
-
listRepositories?: (input: {
|
|
1191
|
-
installationIds?: number[];
|
|
1192
|
-
}) => Promise<GitHubRepository[]>;
|
|
1332
|
+
listRepositories?: (input: { installationIds?: number[] }) => Promise<GitHubRepository[]>;
|
|
1193
1333
|
};
|
|
1194
1334
|
|
|
1195
1335
|
export const BillingBalance = z.object({
|
|
@@ -1202,10 +1342,14 @@ export type BillingBalance = z.infer<typeof BillingBalance>;
|
|
|
1202
1342
|
|
|
1203
1343
|
export const CreateCheckoutRequest = z.object({
|
|
1204
1344
|
accountId: z.string().uuid().optional(),
|
|
1205
|
-
amountUsd: z
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
+
),
|
|
1209
1353
|
successUrl: z.string().url().optional(),
|
|
1210
1354
|
cancelUrl: z.string().url().optional(),
|
|
1211
1355
|
});
|
|
@@ -1223,6 +1367,11 @@ export const RepositoryResourceRef = z.object({
|
|
|
1223
1367
|
ref: z.string().min(1),
|
|
1224
1368
|
mountPath: z.string().min(1).optional(),
|
|
1225
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(),
|
|
1226
1375
|
githubInstallationId: z.number().int().positive().optional(),
|
|
1227
1376
|
githubRepositoryId: z.number().int().positive().optional(),
|
|
1228
1377
|
});
|
|
@@ -1241,7 +1390,13 @@ export type ResourceRef = z.infer<typeof ResourceRef>;
|
|
|
1241
1390
|
export const FileStatus = z.enum(["pending_upload", "ready", "failed", "expired", "deleted"]);
|
|
1242
1391
|
export type FileStatus = z.infer<typeof FileStatus>;
|
|
1243
1392
|
|
|
1244
|
-
export const FileUploadStatus = z.enum([
|
|
1393
|
+
export const FileUploadStatus = z.enum([
|
|
1394
|
+
"pending",
|
|
1395
|
+
"cleanup_pending",
|
|
1396
|
+
"completed",
|
|
1397
|
+
"expired",
|
|
1398
|
+
"failed",
|
|
1399
|
+
]);
|
|
1245
1400
|
export type FileUploadStatus = z.infer<typeof FileUploadStatus>;
|
|
1246
1401
|
|
|
1247
1402
|
export const FileAsset = z.object({
|
|
@@ -1292,7 +1447,16 @@ export type FileDownloadUrlResponse = z.infer<typeof FileDownloadUrlResponse>;
|
|
|
1292
1447
|
export const DocumentStatus = z.enum(["queued", "indexing", "ready", "failed"]);
|
|
1293
1448
|
export type DocumentStatus = z.infer<typeof DocumentStatus>;
|
|
1294
1449
|
|
|
1295
|
-
export const KnowledgeSourceKind = z.enum([
|
|
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
|
+
]);
|
|
1296
1460
|
export type KnowledgeSourceKind = z.infer<typeof KnowledgeSourceKind>;
|
|
1297
1461
|
|
|
1298
1462
|
export const DocumentSearchMode = z.enum(["hybrid", "vector", "keyword"]);
|
|
@@ -1389,10 +1553,28 @@ export const DocumentSearchRequest = z.object({
|
|
|
1389
1553
|
});
|
|
1390
1554
|
export type DocumentSearchRequest = z.infer<typeof DocumentSearchRequest>;
|
|
1391
1555
|
|
|
1392
|
-
|
|
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
|
+
]);
|
|
1393
1569
|
export type KnowledgeMemoryStatus = z.infer<typeof KnowledgeMemoryStatus>;
|
|
1394
1570
|
|
|
1395
|
-
export const KnowledgeMemoryKind = z.enum([
|
|
1571
|
+
export const KnowledgeMemoryKind = z.enum([
|
|
1572
|
+
"semantic",
|
|
1573
|
+
"episodic",
|
|
1574
|
+
"procedural",
|
|
1575
|
+
"decision",
|
|
1576
|
+
"preference",
|
|
1577
|
+
]);
|
|
1396
1578
|
export type KnowledgeMemoryKind = z.infer<typeof KnowledgeMemoryKind>;
|
|
1397
1579
|
|
|
1398
1580
|
export const KnowledgeSourceRef = z.object({
|
|
@@ -1417,13 +1599,29 @@ export const KnowledgeMemory = z.object({
|
|
|
1417
1599
|
createdBySessionId: z.string().uuid().nullable(),
|
|
1418
1600
|
reviewedBy: z.string().nullable(),
|
|
1419
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(),
|
|
1420
1613
|
createdAt: z.string(),
|
|
1421
1614
|
updatedAt: z.string(),
|
|
1422
1615
|
});
|
|
1423
1616
|
export type KnowledgeMemory = z.infer<typeof KnowledgeMemory>;
|
|
1424
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.
|
|
1425
1623
|
export const CreateKnowledgeMemoryRequest = z.object({
|
|
1426
|
-
status: KnowledgeMemoryStatus.default("
|
|
1624
|
+
status: KnowledgeMemoryStatus.default("active"),
|
|
1427
1625
|
kind: KnowledgeMemoryKind.default("semantic"),
|
|
1428
1626
|
scope: z.string().min(1).default("workspace"),
|
|
1429
1627
|
text: z.string().min(1),
|
|
@@ -1431,6 +1629,8 @@ export const CreateKnowledgeMemoryRequest = z.object({
|
|
|
1431
1629
|
confidence: z.number().min(0).max(1).default(0.5),
|
|
1432
1630
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
1433
1631
|
createdBySessionId: z.string().uuid().optional(),
|
|
1632
|
+
pinned: z.boolean().optional(),
|
|
1633
|
+
replacesId: z.string().min(1).optional(),
|
|
1434
1634
|
});
|
|
1435
1635
|
export type CreateKnowledgeMemoryRequest = z.infer<typeof CreateKnowledgeMemoryRequest>;
|
|
1436
1636
|
|
|
@@ -1443,9 +1643,12 @@ export const UpdateKnowledgeMemoryRequest = z.object({
|
|
|
1443
1643
|
confidence: z.number().min(0).max(1).optional(),
|
|
1444
1644
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
1445
1645
|
reviewedBy: z.string().min(1).optional(),
|
|
1646
|
+
// Human audit action: pin (never decays) / unpin.
|
|
1647
|
+
pinned: z.boolean().optional(),
|
|
1446
1648
|
});
|
|
1447
1649
|
export type UpdateKnowledgeMemoryRequest = z.infer<typeof UpdateKnowledgeMemoryRequest>;
|
|
1448
1650
|
|
|
1651
|
+
// GET list/filter over knowledge memories (curated + memory).
|
|
1449
1652
|
export const KnowledgeMemorySearchRequest = z.object({
|
|
1450
1653
|
query: z.string().min(1).optional(),
|
|
1451
1654
|
status: KnowledgeMemoryStatus.optional(),
|
|
@@ -1455,6 +1658,32 @@ export const KnowledgeMemorySearchRequest = z.object({
|
|
|
1455
1658
|
});
|
|
1456
1659
|
export type KnowledgeMemorySearchRequest = z.infer<typeof KnowledgeMemorySearchRequest>;
|
|
1457
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
|
+
|
|
1458
1687
|
export const ToolRef = z.object({
|
|
1459
1688
|
kind: z.literal("mcp"),
|
|
1460
1689
|
id: z.string().min(1),
|
|
@@ -1469,13 +1698,19 @@ export const ToolRef = z.object({
|
|
|
1469
1698
|
export type ToolRef = z.infer<typeof ToolRef>;
|
|
1470
1699
|
|
|
1471
1700
|
const registryId = /^[A-Za-z0-9_-]+$/;
|
|
1472
|
-
const httpsUrl = z
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
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
|
+
);
|
|
1479
1714
|
|
|
1480
1715
|
export const SessionMcpServerInput = z.object({
|
|
1481
1716
|
id: z.string().min(1).regex(registryId),
|
|
@@ -1502,13 +1737,15 @@ export const SessionMcpCredentialUpdateInput = z.object({
|
|
|
1502
1737
|
});
|
|
1503
1738
|
export type SessionMcpCredentialUpdateInput = z.infer<typeof SessionMcpCredentialUpdateInput>;
|
|
1504
1739
|
|
|
1505
|
-
export const SessionMcpServerMetadata = z
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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();
|
|
1512
1749
|
export type SessionMcpServerMetadata = z.infer<typeof SessionMcpServerMetadata>;
|
|
1513
1750
|
|
|
1514
1751
|
export class ResourceRefConflictError extends Error {
|
|
@@ -1547,8 +1784,14 @@ export function mergeResourceRefs(
|
|
|
1547
1784
|
options: { rejectConflicts?: boolean } = {},
|
|
1548
1785
|
): ResourceRef[] {
|
|
1549
1786
|
const out = [...existing];
|
|
1550
|
-
const mountPaths = new Map(
|
|
1551
|
-
|
|
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
|
+
);
|
|
1552
1795
|
const exact = new Set(existing.map(stableJson));
|
|
1553
1796
|
|
|
1554
1797
|
for (const resource of additions) {
|
|
@@ -1559,12 +1802,16 @@ export function mergeResourceRefs(
|
|
|
1559
1802
|
if (options.rejectConflicts) {
|
|
1560
1803
|
const existingAtMount = resource.mountPath ? mountPaths.get(resource.mountPath) : undefined;
|
|
1561
1804
|
if (existingAtMount && existingAtMount !== serialized) {
|
|
1562
|
-
throw new ResourceRefConflictError(
|
|
1805
|
+
throw new ResourceRefConflictError(
|
|
1806
|
+
`resource mount path is already attached: ${resource.mountPath}`,
|
|
1807
|
+
);
|
|
1563
1808
|
}
|
|
1564
1809
|
const identity = resourceIdentityKey(resource);
|
|
1565
1810
|
const existingIdentity = identities.get(identity);
|
|
1566
1811
|
if (existingIdentity && existingIdentity !== serialized) {
|
|
1567
|
-
throw new ResourceRefConflictError(
|
|
1812
|
+
throw new ResourceRefConflictError(
|
|
1813
|
+
`resource is already attached with different settings: ${identity}`,
|
|
1814
|
+
);
|
|
1568
1815
|
}
|
|
1569
1816
|
}
|
|
1570
1817
|
out.push(resource);
|
|
@@ -1577,9 +1824,17 @@ export function mergeResourceRefs(
|
|
|
1577
1824
|
return out;
|
|
1578
1825
|
}
|
|
1579
1826
|
|
|
1580
|
-
export function reasoningEffortForMetadata(
|
|
1827
|
+
export function reasoningEffortForMetadata(
|
|
1828
|
+
metadata: Record<string, unknown>,
|
|
1829
|
+
fallback: ReasoningEffort,
|
|
1830
|
+
): ReasoningEffort {
|
|
1581
1831
|
const value = metadata.reasoningEffort;
|
|
1582
|
-
return value === "none" ||
|
|
1832
|
+
return value === "none" ||
|
|
1833
|
+
value === "minimal" ||
|
|
1834
|
+
value === "low" ||
|
|
1835
|
+
value === "medium" ||
|
|
1836
|
+
value === "high" ||
|
|
1837
|
+
value === "xhigh"
|
|
1583
1838
|
? value
|
|
1584
1839
|
: fallback;
|
|
1585
1840
|
}
|
|
@@ -1600,17 +1855,44 @@ function sortJson(value: unknown): unknown {
|
|
|
1600
1855
|
return value.map(sortJson);
|
|
1601
1856
|
}
|
|
1602
1857
|
if (value && typeof value === "object") {
|
|
1603
|
-
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
|
+
);
|
|
1604
1863
|
}
|
|
1605
1864
|
return value;
|
|
1606
1865
|
}
|
|
1607
1866
|
|
|
1608
|
-
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
|
+
]);
|
|
1609
1878
|
export type SessionTurnStatus = z.infer<typeof SessionTurnStatus>;
|
|
1610
1879
|
|
|
1611
|
-
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
|
+
]);
|
|
1612
1888
|
export type SessionTurnSource = z.infer<typeof SessionTurnSource>;
|
|
1613
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
|
+
|
|
1614
1896
|
export const SessionGoalStatus = z.enum(["active", "paused", "completed"]);
|
|
1615
1897
|
export type SessionGoalStatus = z.infer<typeof SessionGoalStatus>;
|
|
1616
1898
|
|
|
@@ -1619,7 +1901,7 @@ export type SessionGoalCreatedBy = z.infer<typeof SessionGoalCreatedBy>;
|
|
|
1619
1901
|
|
|
1620
1902
|
export const SessionGoalPausedReason = z.enum([
|
|
1621
1903
|
"agent",
|
|
1622
|
-
"
|
|
1904
|
+
"user_pause",
|
|
1623
1905
|
"api",
|
|
1624
1906
|
"no_progress",
|
|
1625
1907
|
"max_auto_continuations",
|
|
@@ -1667,6 +1949,18 @@ export const UpdateSessionRequest = z.object({
|
|
|
1667
1949
|
});
|
|
1668
1950
|
export type UpdateSessionRequest = z.infer<typeof UpdateSessionRequest>;
|
|
1669
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
|
+
|
|
1670
1964
|
// Operator context controls (slash-command palette: /clear, /compact). These
|
|
1671
1965
|
// are session/operator actions, NOT a structured way to talk to the agent —
|
|
1672
1966
|
// the human↔agent channel stays plain chat. Both require `sessions:control`.
|
|
@@ -1709,9 +2003,11 @@ export function isClearedRunStateBlob(serialized: string | null | undefined): bo
|
|
|
1709
2003
|
}
|
|
1710
2004
|
try {
|
|
1711
2005
|
const parsed = JSON.parse(serialized) as unknown;
|
|
1712
|
-
return
|
|
1713
|
-
|
|
1714
|
-
|
|
2006
|
+
return (
|
|
2007
|
+
typeof parsed === "object" &&
|
|
2008
|
+
parsed !== null &&
|
|
2009
|
+
(parsed as Record<string, unknown>)[CLEARED_RUN_STATE_MARKER] === true
|
|
2010
|
+
);
|
|
1715
2011
|
} catch {
|
|
1716
2012
|
return false;
|
|
1717
2013
|
}
|
|
@@ -1723,9 +2019,10 @@ export type CompactSessionContextRequest = z.infer<typeof CompactSessionContextR
|
|
|
1723
2019
|
|
|
1724
2020
|
/** Outcome of a manual /compact trigger. */
|
|
1725
2021
|
export const CompactSessionContextResult = z.object({
|
|
1726
|
-
//
|
|
1727
|
-
//
|
|
1728
|
-
|
|
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"]),
|
|
1729
2026
|
message: z.string(),
|
|
1730
2027
|
});
|
|
1731
2028
|
export type CompactSessionContextResult = z.infer<typeof CompactSessionContextResult>;
|
|
@@ -1738,7 +2035,7 @@ export const SessionTurn = z.object({
|
|
|
1738
2035
|
temporalWorkflowId: z.string(),
|
|
1739
2036
|
status: SessionTurnStatus,
|
|
1740
2037
|
source: SessionTurnSource,
|
|
1741
|
-
position: z.number().int()
|
|
2038
|
+
position: z.number().int(),
|
|
1742
2039
|
prompt: z.string().min(1),
|
|
1743
2040
|
resources: z.array(ResourceRef),
|
|
1744
2041
|
tools: z.array(ToolRef),
|
|
@@ -1748,6 +2045,12 @@ export const SessionTurn = z.object({
|
|
|
1748
2045
|
// Per-turn OS override. NULL = inherit the session's sandboxOs.
|
|
1749
2046
|
sandboxOs: SandboxOs.nullable(),
|
|
1750
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(),
|
|
1751
2054
|
startedAt: z.string().nullable(),
|
|
1752
2055
|
finishedAt: z.string().nullable(),
|
|
1753
2056
|
createdAt: z.string(),
|
|
@@ -1755,68 +2058,316 @@ export const SessionTurn = z.object({
|
|
|
1755
2058
|
});
|
|
1756
2059
|
export type SessionTurn = z.infer<typeof SessionTurn>;
|
|
1757
2060
|
|
|
1758
|
-
export const
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
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),
|
|
2069
|
+
});
|
|
2070
|
+
export type SessionQueueSnapshot = z.infer<typeof SessionQueueSnapshot>;
|
|
2071
|
+
|
|
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(),
|
|
1766
2076
|
});
|
|
1767
|
-
export type
|
|
2077
|
+
export type CancelSessionQueueItemRequest = z.infer<typeof CancelSessionQueueItemRequest>;
|
|
2078
|
+
|
|
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>;
|
|
1768
2119
|
|
|
1769
|
-
export const
|
|
1770
|
-
|
|
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(),
|
|
1771
2143
|
});
|
|
1772
|
-
export type
|
|
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>;
|
|
1773
2151
|
|
|
1774
|
-
|
|
1775
|
-
|
|
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
|
+
}
|
|
1776
2164
|
|
|
1777
2165
|
// Metadata only by design: no schema in this file ever carries a variable value
|
|
1778
2166
|
// back to a client. Values are write-only and decrypted exclusively inside the
|
|
1779
2167
|
// worker at sandbox materialization time.
|
|
1780
|
-
export const
|
|
1781
|
-
name:
|
|
2168
|
+
export const VariableSetVariableMetadata = z.object({
|
|
2169
|
+
name: VariableSetVariableName,
|
|
1782
2170
|
version: z.number().int().positive(),
|
|
1783
2171
|
createdAt: z.string(),
|
|
1784
2172
|
updatedAt: z.string(),
|
|
1785
2173
|
});
|
|
1786
|
-
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;
|
|
1787
2179
|
|
|
1788
|
-
export const
|
|
2180
|
+
export const VariableSet = z.object({
|
|
1789
2181
|
id: z.string().uuid(),
|
|
1790
2182
|
accountId: z.string().uuid(),
|
|
1791
2183
|
workspaceId: z.string().uuid(),
|
|
1792
2184
|
name: z.string(),
|
|
1793
2185
|
description: z.string().nullable(),
|
|
1794
|
-
variables: z.array(
|
|
2186
|
+
variables: z.array(VariableSetVariableMetadata),
|
|
1795
2187
|
createdAt: z.string(),
|
|
1796
2188
|
updatedAt: z.string(),
|
|
1797
2189
|
});
|
|
1798
|
-
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;
|
|
1799
2195
|
|
|
1800
|
-
export const
|
|
2196
|
+
export const CreateVariableSetRequest = z.object({
|
|
1801
2197
|
name: z.string().min(1).max(120),
|
|
1802
2198
|
description: z.string().max(2000).optional(),
|
|
1803
|
-
variables: z
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
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),
|
|
1807
2226
|
});
|
|
1808
|
-
export type
|
|
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;
|
|
1809
2232
|
|
|
1810
|
-
|
|
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),
|
|
2242
|
+
});
|
|
2243
|
+
export type RigCheck = z.infer<typeof RigCheck>;
|
|
2244
|
+
|
|
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({
|
|
1811
2342
|
name: z.string().min(1).max(120).optional(),
|
|
1812
2343
|
description: z.string().max(2000).nullable().optional(),
|
|
1813
2344
|
});
|
|
1814
|
-
export type
|
|
2345
|
+
export type UpdateRigRequest = z.infer<typeof UpdateRigRequest>;
|
|
1815
2346
|
|
|
1816
|
-
|
|
1817
|
-
|
|
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(),
|
|
1818
2351
|
});
|
|
1819
|
-
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>;
|
|
1820
2371
|
|
|
1821
2372
|
export const ScheduledTaskStatus = z.enum(["active", "paused"]);
|
|
1822
2373
|
export type ScheduledTaskStatus = z.infer<typeof ScheduledTaskStatus>;
|
|
@@ -1850,7 +2401,10 @@ export const ScheduledTaskScheduleSpec = z.discriminatedUnion("type", [
|
|
|
1850
2401
|
timeZone: z.string().min(1).default("UTC"),
|
|
1851
2402
|
hour: z.number().int().min(0).max(23),
|
|
1852
2403
|
minute: z.number().int().min(0).max(59),
|
|
1853
|
-
daysOfWeek: z
|
|
2404
|
+
daysOfWeek: z
|
|
2405
|
+
.array(z.enum(["SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY"]))
|
|
2406
|
+
.min(1)
|
|
2407
|
+
.optional(),
|
|
1854
2408
|
}),
|
|
1855
2409
|
]);
|
|
1856
2410
|
export type ScheduledTaskScheduleSpec = z.infer<typeof ScheduledTaskScheduleSpec>;
|
|
@@ -1879,7 +2433,13 @@ export const ScheduledTask = z.object({
|
|
|
1879
2433
|
overlapPolicy: ScheduledTaskOverlapPolicy,
|
|
1880
2434
|
agentConfig: ScheduledTaskAgentConfig,
|
|
1881
2435
|
reusableSessionId: z.string().uuid().nullable(),
|
|
1882
|
-
|
|
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),
|
|
1883
2443
|
metadata: z.record(z.string(), z.unknown()),
|
|
1884
2444
|
createdAt: z.string(),
|
|
1885
2445
|
updatedAt: z.string(),
|
|
@@ -1903,26 +2463,33 @@ export const ScheduledTaskRun = z.object({
|
|
|
1903
2463
|
});
|
|
1904
2464
|
export type ScheduledTaskRun = z.infer<typeof ScheduledTaskRun>;
|
|
1905
2465
|
|
|
1906
|
-
export const CreateScheduledTaskRequest =
|
|
2466
|
+
export const CreateScheduledTaskRequest = withVariableSetIdAlias({
|
|
1907
2467
|
name: z.string().min(1),
|
|
1908
2468
|
schedule: ScheduledTaskScheduleSpec,
|
|
1909
2469
|
runMode: ScheduledTaskRunMode.default("new_session_per_run"),
|
|
1910
2470
|
overlapPolicy: ScheduledTaskOverlapPolicy.default("allow_concurrent"),
|
|
1911
2471
|
agentConfig: ScheduledTaskAgentConfig,
|
|
1912
2472
|
status: ScheduledTaskStatus.default("active"),
|
|
2473
|
+
variableSetId: z.string().uuid().nullable().optional(),
|
|
1913
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(),
|
|
1914
2477
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
1915
2478
|
});
|
|
1916
2479
|
export type CreateScheduledTaskRequest = z.infer<typeof CreateScheduledTaskRequest>;
|
|
1917
2480
|
|
|
1918
|
-
export const UpdateScheduledTaskRequest =
|
|
2481
|
+
export const UpdateScheduledTaskRequest = withVariableSetIdAlias({
|
|
1919
2482
|
name: z.string().min(1).optional(),
|
|
1920
2483
|
schedule: ScheduledTaskScheduleSpec.optional(),
|
|
1921
2484
|
runMode: ScheduledTaskRunMode.optional(),
|
|
1922
2485
|
overlapPolicy: ScheduledTaskOverlapPolicy.optional(),
|
|
1923
2486
|
agentConfig: ScheduledTaskAgentConfig.optional(),
|
|
1924
2487
|
status: ScheduledTaskStatus.optional(),
|
|
2488
|
+
variableSetId: z.string().uuid().nullable().optional(),
|
|
1925
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(),
|
|
1926
2493
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
1927
2494
|
});
|
|
1928
2495
|
export type UpdateScheduledTaskRequest = z.infer<typeof UpdateScheduledTaskRequest>;
|
|
@@ -1977,7 +2544,9 @@ export const CapabilityPackScheduledTaskTemplate = z.object({
|
|
|
1977
2544
|
// instantiable templates; built-in packs may instead build prompts in code.
|
|
1978
2545
|
prompt: z.string().min(1).optional(),
|
|
1979
2546
|
});
|
|
1980
|
-
export type CapabilityPackScheduledTaskTemplate = z.infer<
|
|
2547
|
+
export type CapabilityPackScheduledTaskTemplate = z.infer<
|
|
2548
|
+
typeof CapabilityPackScheduledTaskTemplate
|
|
2549
|
+
>;
|
|
1981
2550
|
|
|
1982
2551
|
// One file inside a pack skill directory. Paths are workspace-relative POSIX
|
|
1983
2552
|
// paths inside the skill directory (for example "SKILL.md" or
|
|
@@ -1995,66 +2564,119 @@ export type CapabilityPackSkillFile = z.infer<typeof CapabilityPackSkillFile>;
|
|
|
1995
2564
|
// A skill delivered by a capability pack. The name doubles as the skill
|
|
1996
2565
|
// directory under the sandbox skill index (skills/<name>), so it must be a
|
|
1997
2566
|
// single safe path segment. Every skill must ship a top-level SKILL.md.
|
|
1998
|
-
export const CapabilityPackSkill = z
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
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
|
+
});
|
|
2009
2597
|
}
|
|
2010
|
-
seen.add(file.path);
|
|
2011
2598
|
});
|
|
2012
|
-
if (!skill.files.some((file) => file.path === "SKILL.md")) {
|
|
2013
|
-
ctx.addIssue({ code: "custom", message: "skill must include a top-level SKILL.md file", path: ["files"] });
|
|
2014
|
-
}
|
|
2015
|
-
});
|
|
2016
2599
|
export type CapabilityPackSkill = z.infer<typeof CapabilityPackSkill>;
|
|
2017
2600
|
|
|
2018
2601
|
function isSafePackSkillRelativePath(path: string): boolean {
|
|
2019
2602
|
if (path.startsWith("/") || path.includes("\\")) {
|
|
2020
2603
|
return false;
|
|
2021
2604
|
}
|
|
2022
|
-
return path
|
|
2605
|
+
return path
|
|
2606
|
+
.split("/")
|
|
2607
|
+
.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
2023
2608
|
}
|
|
2024
2609
|
|
|
2025
|
-
|
|
2026
|
-
id: z.string().min(1),
|
|
2027
|
-
name: z.string().min(1),
|
|
2610
|
+
const CapabilityPackVariableSet = z.object({
|
|
2028
2611
|
description: z.string().min(1),
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
version: z.string().min(1),
|
|
2032
|
-
// Container image ref (digest-pinned recommended) the pack's sessions run
|
|
2033
|
-
// in. At most one enabled pack per workspace may declare one; with none,
|
|
2034
|
-
// sessions use the deployment-wide image settings.
|
|
2035
|
-
sandboxImage: z.string().trim().min(1).max(512).optional(),
|
|
2036
|
-
// Skills delivered into the sandbox skill index when the pack is enabled.
|
|
2037
|
-
skills: z.array(CapabilityPackSkill).max(32).superRefine((skills, ctx) => {
|
|
2038
|
-
const seen = new Set<string>();
|
|
2039
|
-
skills.forEach((skill, index) => {
|
|
2040
|
-
const key = skill.name.toLowerCase();
|
|
2041
|
-
if (seen.has(key)) {
|
|
2042
|
-
ctx.addIssue({ code: "custom", message: `duplicate pack skill name: ${skill.name}`, path: [index, "name"] });
|
|
2043
|
-
}
|
|
2044
|
-
seen.add(key);
|
|
2045
|
-
});
|
|
2046
|
-
}).default([]),
|
|
2047
|
-
tools: z.array(ToolRef).default([]),
|
|
2048
|
-
connectors: z.array(CapabilityPackConnector).default([]),
|
|
2049
|
-
knowledge: z.array(CapabilityPackKnowledge).default([]),
|
|
2050
|
-
scheduledTaskTemplates: z.array(CapabilityPackScheduledTaskTemplate).default([]),
|
|
2051
|
-
environment: z.object({
|
|
2052
|
-
description: z.string().min(1),
|
|
2053
|
-
requiredVariables: z.array(WorkspaceEnvironmentVariableName).default([]),
|
|
2054
|
-
required: z.boolean().default(false),
|
|
2055
|
-
}).optional(),
|
|
2056
|
-
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
2612
|
+
requiredVariables: z.array(VariableSetVariableName).default([]),
|
|
2613
|
+
required: z.boolean().default(false),
|
|
2057
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
|
+
);
|
|
2058
2680
|
export type CapabilityPack = z.infer<typeof CapabilityPack>;
|
|
2059
2681
|
|
|
2060
2682
|
// Registering a pack stores the manifest itself; the request body is a full
|
|
@@ -2086,7 +2708,8 @@ export const PackInstallation = z.object({
|
|
|
2086
2708
|
});
|
|
2087
2709
|
export type PackInstallation = z.infer<typeof PackInstallation>;
|
|
2088
2710
|
|
|
2089
|
-
export const EnablePackRequest =
|
|
2711
|
+
export const EnablePackRequest = withVariableSetIdAlias({
|
|
2712
|
+
variableSetId: z.string().uuid().optional(),
|
|
2090
2713
|
environmentId: z.string().uuid().optional(),
|
|
2091
2714
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
2092
2715
|
});
|
|
@@ -2172,14 +2795,16 @@ export type ConnectionKind = z.infer<typeof ConnectionKind>;
|
|
|
2172
2795
|
export const ConnectionStatus = z.enum(["active", "needs_reauth", "revoked", "error"]);
|
|
2173
2796
|
export type ConnectionStatus = z.infer<typeof ConnectionStatus>;
|
|
2174
2797
|
|
|
2175
|
-
export const McpServerConnectionRef = z
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
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();
|
|
2183
2808
|
export type McpServerConnectionRef = z.infer<typeof McpServerConnectionRef>;
|
|
2184
2809
|
|
|
2185
2810
|
export const ConnectionMetadata = z.object({
|
|
@@ -2240,17 +2865,28 @@ export const ListConnectionsResponse = z.object({
|
|
|
2240
2865
|
});
|
|
2241
2866
|
export type ListConnectionsResponse = z.infer<typeof ListConnectionsResponse>;
|
|
2242
2867
|
|
|
2243
|
-
export const OAuthStartRequest = z
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
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
|
+
});
|
|
2254
2890
|
export type OAuthStartRequest = z.infer<typeof OAuthStartRequest>;
|
|
2255
2891
|
|
|
2256
2892
|
export const OAuthStartResponse = z.object({
|
|
@@ -2287,7 +2923,13 @@ export type MarketingDailyAnalysisTaskRequest = z.infer<typeof MarketingDailyAna
|
|
|
2287
2923
|
export const CapabilityKind = z.enum(["pack", "mcp", "api", "skill", "plugin"]);
|
|
2288
2924
|
export type CapabilityKind = z.infer<typeof CapabilityKind>;
|
|
2289
2925
|
|
|
2290
|
-
export const CapabilitySource = z.enum([
|
|
2926
|
+
export const CapabilitySource = z.enum([
|
|
2927
|
+
"built_in",
|
|
2928
|
+
"configured",
|
|
2929
|
+
"public_registry",
|
|
2930
|
+
"registry",
|
|
2931
|
+
"manual",
|
|
2932
|
+
]);
|
|
2291
2933
|
export type CapabilitySource = z.infer<typeof CapabilitySource>;
|
|
2292
2934
|
|
|
2293
2935
|
export const CapabilityInstallationStatus = z.enum(["active", "disabled"]);
|
|
@@ -2337,6 +2979,18 @@ export const CapabilityCatalogItem = z.object({
|
|
|
2337
2979
|
runtime: CapabilityRuntime.default({ available: false, notes: null }),
|
|
2338
2980
|
enabled: z.boolean().default(false),
|
|
2339
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),
|
|
2340
2994
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
2341
2995
|
createdAt: z.string().optional(),
|
|
2342
2996
|
updatedAt: z.string().optional(),
|
|
@@ -2373,23 +3027,24 @@ export const CreateCapabilityCatalogItemRequest = z.object({
|
|
|
2373
3027
|
});
|
|
2374
3028
|
export type CreateCapabilityCatalogItemRequest = z.infer<typeof CreateCapabilityCatalogItemRequest>;
|
|
2375
3029
|
|
|
2376
|
-
export const EnableCapabilityRequest =
|
|
3030
|
+
export const EnableCapabilityRequest = withVariableSetIdAlias({
|
|
2377
3031
|
config: z.record(z.string(), z.unknown()).default({}),
|
|
2378
3032
|
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
2379
3033
|
connectionRef: McpServerConnectionRef.optional(),
|
|
2380
3034
|
/**
|
|
2381
3035
|
* Credential headers for remote MCP capabilities (for example an
|
|
2382
3036
|
* Authorization bearer token). Values are encrypted at rest with the
|
|
2383
|
-
* workspace-
|
|
3037
|
+
* workspace-variable-sets key, injected only into the runtime MCP client,
|
|
2384
3038
|
* and never returned by the API — responses expose header names only.
|
|
2385
3039
|
*/
|
|
2386
3040
|
headers: z.record(z.string(), z.string()).default({}),
|
|
2387
3041
|
/**
|
|
2388
|
-
* Initial
|
|
3042
|
+
* Initial variableSet attachment for kind=pack capabilities. Mirrors the
|
|
2389
3043
|
* dedicated POST /packs/:id/enable body: required to enable an
|
|
2390
|
-
*
|
|
3044
|
+
* variableSet.required pack through the unified capability-enable path,
|
|
2391
3045
|
* optional otherwise. Ignored by non-pack capabilities.
|
|
2392
3046
|
*/
|
|
3047
|
+
variableSetId: z.string().uuid().optional(),
|
|
2393
3048
|
environmentId: z.string().uuid().optional(),
|
|
2394
3049
|
});
|
|
2395
3050
|
export type EnableCapabilityRequest = z.infer<typeof EnableCapabilityRequest>;
|
|
@@ -2437,7 +3092,16 @@ export const Session = z.object({
|
|
|
2437
3092
|
// stale in-flight op and retry against the new active sandbox.
|
|
2438
3093
|
activeSandboxId: z.string().uuid().nullable(),
|
|
2439
3094
|
activeEpoch: z.number().int().nonnegative(),
|
|
2440
|
-
|
|
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),
|
|
2441
3105
|
// Non-default first-party MCP token permissions (manager-style sessions);
|
|
2442
3106
|
// null means the fixed worker default set.
|
|
2443
3107
|
firstPartyMcpPermissions: z.array(Permission).nullable(),
|
|
@@ -2456,9 +3120,18 @@ export const Session = z.object({
|
|
|
2456
3120
|
temporalWorkflowId: z.string().nullable(),
|
|
2457
3121
|
activeTurnId: z.string().uuid().nullable(),
|
|
2458
3122
|
// Actual input tokens of the last model call of the most recent turn; the
|
|
2459
|
-
// pre-turn
|
|
3123
|
+
// pre-turn portable context-compaction trigger reads it as its budget
|
|
2460
3124
|
// signal. Null until a turn with usage has completed.
|
|
2461
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(),
|
|
2462
3135
|
lastSequence: z.number().int().nonnegative(),
|
|
2463
3136
|
// Multi-account Codex (P1). codexPinnedCredentialId: the account this session is
|
|
2464
3137
|
// manually PINNED to (null ⇒ follow the workspace active pointer).
|
|
@@ -2466,34 +3139,100 @@ export const Session = z.object({
|
|
|
2466
3139
|
// "Running on:" indicator's source). Both are credential-row ids, null until set.
|
|
2467
3140
|
codexPinnedCredentialId: z.string().uuid().nullable(),
|
|
2468
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(),
|
|
2469
3164
|
createdAt: z.string(),
|
|
2470
3165
|
updatedAt: z.string(),
|
|
2471
3166
|
});
|
|
2472
3167
|
export type Session = z.infer<typeof Session>;
|
|
2473
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
|
+
|
|
2474
3205
|
export const SessionEventType = z.enum([
|
|
2475
3206
|
"session.created",
|
|
2476
3207
|
"session.status.changed",
|
|
2477
3208
|
"session.requiresAction",
|
|
3209
|
+
"session.context.compaction.requested",
|
|
2478
3210
|
"session.context.compacted",
|
|
3211
|
+
"session.context.compaction.skipped",
|
|
2479
3212
|
"session.context.cleared",
|
|
2480
3213
|
"user.message",
|
|
2481
|
-
"user.
|
|
3214
|
+
"user.pause",
|
|
2482
3215
|
"user.approvalDecision",
|
|
2483
3216
|
"turn.queued",
|
|
2484
|
-
"turn.updated",
|
|
2485
3217
|
"turn.started",
|
|
2486
3218
|
"turn.completed",
|
|
2487
3219
|
"turn.failed",
|
|
2488
3220
|
"turn.cancelled",
|
|
2489
|
-
"turn.
|
|
3221
|
+
"turn.superseded",
|
|
3222
|
+
"turn.recovery.requested",
|
|
3223
|
+
"turn.capacity_waiting",
|
|
2490
3224
|
"agent.message.delta",
|
|
2491
3225
|
"agent.message.completed",
|
|
2492
3226
|
"agent.reasoning.delta",
|
|
2493
3227
|
"agent.toolCall.created",
|
|
2494
3228
|
"agent.toolCall.output",
|
|
3229
|
+
"agent.model.usage",
|
|
2495
3230
|
"tool.auth_needed",
|
|
2496
3231
|
"agent.updated",
|
|
3232
|
+
"rig.setup.started",
|
|
3233
|
+
"rig.setup.completed",
|
|
3234
|
+
"rig.setup.skipped",
|
|
3235
|
+
"rig.setup.failed",
|
|
2497
3236
|
"sandbox.operation.started",
|
|
2498
3237
|
"sandbox.operation.completed",
|
|
2499
3238
|
"sandbox.operation.failed",
|
|
@@ -2504,7 +3243,22 @@ export const SessionEventType = z.enum([
|
|
|
2504
3243
|
"goal.completed",
|
|
2505
3244
|
"goal.paused",
|
|
2506
3245
|
"goal.resumed",
|
|
3246
|
+
"goal.cleared",
|
|
2507
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",
|
|
2508
3262
|
// Channel-B desktop pixel-plane signals (07-channel-b §1.2). The pixel socket
|
|
2509
3263
|
// carries opaque RFB and cannot carry a control message the client can act on,
|
|
2510
3264
|
// so these ride the durable, sequenced, gap-filled Channel-A SSE spine.
|
|
@@ -2537,6 +3291,78 @@ export const SessionEventType = z.enum([
|
|
|
2537
3291
|
// (manual switch in P1; failover/rotation in P3 reuse the same event). Drives
|
|
2538
3292
|
// the in-session "Running on:" indicator's live flip.
|
|
2539
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",
|
|
2540
3366
|
]);
|
|
2541
3367
|
export type SessionEventType = z.infer<typeof SessionEventType>;
|
|
2542
3368
|
|
|
@@ -2676,13 +3502,17 @@ export type SandboxCommandOutputDeltaPayload = z.infer<typeof SandboxCommandOutp
|
|
|
2676
3502
|
export const FsChangeKind = z.enum(["created", "modified", "deleted", "renamed"]);
|
|
2677
3503
|
export type FsChangeKind = z.infer<typeof FsChangeKind>;
|
|
2678
3504
|
export const FsChangedPayload = z.object({
|
|
2679
|
-
changes: z
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
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),
|
|
2686
3516
|
source: z.enum(["write", "watch", "agent"]).default("write"),
|
|
2687
3517
|
// Monotonic FS revision (per-lease, paired with leaseEpoch for staleness).
|
|
2688
3518
|
revision: z.number().int().nonnegative(),
|
|
@@ -2699,7 +3529,9 @@ export const GitChangedPayload = z.object({
|
|
|
2699
3529
|
ahead: z.number().int().nonnegative().default(0),
|
|
2700
3530
|
behind: z.number().int().nonnegative().default(0),
|
|
2701
3531
|
changedFileCount: z.number().int().nonnegative(),
|
|
2702
|
-
reason: z
|
|
3532
|
+
reason: z
|
|
3533
|
+
.enum(["commit", "checkout", "stage", "worktree", "fetch", "unknown"])
|
|
3534
|
+
.default("unknown"),
|
|
2703
3535
|
revision: z.number().int().nonnegative().default(0),
|
|
2704
3536
|
leaseEpoch: z.number().int().nonnegative().default(0),
|
|
2705
3537
|
});
|
|
@@ -2744,16 +3576,18 @@ export interface FsTreeNode {
|
|
|
2744
3576
|
children?: FsTreeNode[] | undefined;
|
|
2745
3577
|
truncated: boolean; // dir had more entries than the cap
|
|
2746
3578
|
}
|
|
2747
|
-
export const FsTreeNode: z.ZodType<FsTreeNode> = z.lazy(() =>
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
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>;
|
|
2757
3591
|
|
|
2758
3592
|
export const FsListRequest = z.object({
|
|
2759
3593
|
path: z.string().default(""), // "" = workspace root
|
|
@@ -2774,7 +3608,12 @@ export type FsEncoding = z.infer<typeof FsEncoding>;
|
|
|
2774
3608
|
export const FsReadRequest = z.object({
|
|
2775
3609
|
path: z.string(),
|
|
2776
3610
|
encoding: FsEncoding.default("utf8"),
|
|
2777
|
-
maxBytes: z
|
|
3611
|
+
maxBytes: z
|
|
3612
|
+
.number()
|
|
3613
|
+
.int()
|
|
3614
|
+
.positive()
|
|
3615
|
+
.max(25 * 1024 * 1024)
|
|
3616
|
+
.default(5 * 1024 * 1024),
|
|
2778
3617
|
});
|
|
2779
3618
|
export type FsReadRequest = z.infer<typeof FsReadRequest>;
|
|
2780
3619
|
export const FsReadResponse = z.object({
|
|
@@ -2838,7 +3677,15 @@ export type FsMkdirResponse = z.infer<typeof FsMkdirResponse>;
|
|
|
2838
3677
|
|
|
2839
3678
|
// --- A2 Git request/response (read-only; feeds Pierre diff/tree) -------------
|
|
2840
3679
|
export const GitFileStatusCode = z.enum([
|
|
2841
|
-
"added",
|
|
3680
|
+
"added",
|
|
3681
|
+
"modified",
|
|
3682
|
+
"deleted",
|
|
3683
|
+
"renamed",
|
|
3684
|
+
"copied",
|
|
3685
|
+
"untracked",
|
|
3686
|
+
"ignored",
|
|
3687
|
+
"conflicted",
|
|
3688
|
+
"typechange",
|
|
2842
3689
|
]);
|
|
2843
3690
|
export type GitFileStatusCode = z.infer<typeof GitFileStatusCode>;
|
|
2844
3691
|
export const GitFileStatus = z.object({
|
|
@@ -2905,7 +3752,12 @@ export const GitDiffRequest = z.object({
|
|
|
2905
3752
|
toRef: z.string().optional(),
|
|
2906
3753
|
pathspec: z.array(z.string()).default([]),
|
|
2907
3754
|
contextLines: z.number().int().min(0).max(10).default(3),
|
|
2908
|
-
maxBytesPerFile: z
|
|
3755
|
+
maxBytesPerFile: z
|
|
3756
|
+
.number()
|
|
3757
|
+
.int()
|
|
3758
|
+
.positive()
|
|
3759
|
+
.max(2 * 1024 * 1024)
|
|
3760
|
+
.default(512 * 1024),
|
|
2909
3761
|
});
|
|
2910
3762
|
export type GitDiffRequest = z.infer<typeof GitDiffRequest>;
|
|
2911
3763
|
export const GitDiffResponse = z.object({
|
|
@@ -2914,6 +3766,182 @@ export const GitDiffResponse = z.object({
|
|
|
2914
3766
|
});
|
|
2915
3767
|
export type GitDiffResponse = z.infer<typeof GitDiffResponse>;
|
|
2916
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
|
+
|
|
2917
3945
|
export const GitLogRequest = z.object({
|
|
2918
3946
|
path: z.string().default(""),
|
|
2919
3947
|
ref: z.string().default("HEAD"),
|
|
@@ -2941,13 +3969,25 @@ export const GitShowRequest = z.object({
|
|
|
2941
3969
|
ref: z.string(), // a commit/tag/tree-ish
|
|
2942
3970
|
filePath: z.string().optional(), // ref + filePath => raw blob ("open file at commit")
|
|
2943
3971
|
encoding: FsEncoding.default("utf8"),
|
|
2944
|
-
maxBytesPerFile: z
|
|
3972
|
+
maxBytesPerFile: z
|
|
3973
|
+
.number()
|
|
3974
|
+
.int()
|
|
3975
|
+
.positive()
|
|
3976
|
+
.max(2 * 1024 * 1024)
|
|
3977
|
+
.default(512 * 1024),
|
|
2945
3978
|
});
|
|
2946
3979
|
export type GitShowRequest = z.infer<typeof GitShowRequest>;
|
|
2947
3980
|
export const GitShowResponse = z.object({
|
|
2948
3981
|
commit: GitCommit.nullable(), // null when fetching a raw blob
|
|
2949
3982
|
files: z.array(GitFileDiff), // commit diff vs first parent
|
|
2950
|
-
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(),
|
|
2951
3991
|
revision: z.number().int().nonnegative(),
|
|
2952
3992
|
});
|
|
2953
3993
|
export type GitShowResponse = z.infer<typeof GitShowResponse>;
|
|
@@ -2995,7 +4035,11 @@ export const PtyOpenResponse = z.object({
|
|
|
2995
4035
|
export type PtyOpenResponse = z.infer<typeof PtyOpenResponse>;
|
|
2996
4036
|
export const PtyWriteRequest = z.object({ ptyId: z.string().uuid(), data: z.string() }); // utf-8 stdin
|
|
2997
4037
|
export type PtyWriteRequest = z.infer<typeof PtyWriteRequest>;
|
|
2998
|
-
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
|
+
});
|
|
2999
4043
|
export type PtyResizeRequest = z.infer<typeof PtyResizeRequest>;
|
|
3000
4044
|
export const PtyCloseRequest = z.object({ ptyId: z.string().uuid() });
|
|
3001
4045
|
export type PtyCloseRequest = z.infer<typeof PtyCloseRequest>;
|
|
@@ -3024,10 +4068,36 @@ export const SessionEvent = z.object({
|
|
|
3024
4068
|
occurredAt: z.string(),
|
|
3025
4069
|
clientEventId: z.string().min(1).nullable().optional(),
|
|
3026
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(),
|
|
3027
4076
|
});
|
|
3028
4077
|
export type SessionEvent = z.infer<typeof SessionEvent>;
|
|
3029
4078
|
|
|
3030
|
-
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({
|
|
3031
4101
|
initialMessage: z.string().min(1),
|
|
3032
4102
|
// Per-session agent persona/system instructions (org-visible metadata, NOT a
|
|
3033
4103
|
// secret). Rides the SAME system-level instructions channel the per-workspace
|
|
@@ -3036,7 +4106,7 @@ export const CreateSessionRequest = z.object({
|
|
|
3036
4106
|
// leaking them into the user-visible timeline (it is NEVER emitted as an
|
|
3037
4107
|
// event, unlike goal/initialMessage). Trimmed, non-empty. The 32768-char cap
|
|
3038
4108
|
// matches the codebase's largest free-form string convention (workspace
|
|
3039
|
-
//
|
|
4109
|
+
// variable set variable values). Absent ⇒ byte-identical to today.
|
|
3040
4110
|
instructions: z.string().trim().min(1).max(32768).optional(),
|
|
3041
4111
|
resources: z.array(ResourceRef).default([]),
|
|
3042
4112
|
tools: z.array(ToolRef).default([]),
|
|
@@ -3055,9 +4125,15 @@ export const CreateSessionRequest = z.object({
|
|
|
3055
4125
|
// (the agent's resolve_cwd handles both). Only valid WITH targetSandboxId
|
|
3056
4126
|
// (workingDir alone is a 422); omitted ⇒ the machine's default workspace_root.
|
|
3057
4127
|
workingDir: z.string().min(1).optional(),
|
|
3058
|
-
//
|
|
4128
|
+
// Variable set attachment is fixed at session creation; follow-up
|
|
3059
4129
|
// user.message events cannot switch or add one.
|
|
4130
|
+
variableSetId: z.string().uuid().optional(),
|
|
3060
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(),
|
|
3061
4137
|
goal: GoalSpec.optional(),
|
|
3062
4138
|
clientEventId: z.string().min(1).optional(),
|
|
3063
4139
|
// Workspace-scoped CREATE idempotency key: collapses concurrent/retried
|
|
@@ -3069,7 +4145,7 @@ export const CreateSessionRequest = z.object({
|
|
|
3069
4145
|
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
3070
4146
|
// Permissions the session's first-party MCP token should carry instead of
|
|
3071
4147
|
// the fixed worker default — how an operator hands a manager-style session
|
|
3072
|
-
// the orchestration/
|
|
4148
|
+
// the orchestration/variableSet/github tools. Capped at creation: every
|
|
3073
4149
|
// requested permission must be held by the creating grant (no escalation).
|
|
3074
4150
|
firstPartyMcpPermissions: z.array(Permission).optional(),
|
|
3075
4151
|
// Third-party MCP servers attached only to this session. Credential headers are
|
|
@@ -3087,16 +4163,14 @@ export const CreateSessionRequest = z.object({
|
|
|
3087
4163
|
// A shared spawn inherits the box's (backend, os) — it is literally the same
|
|
3088
4164
|
// box; the child cannot pick its own backend. Cross-workspace sharing is
|
|
3089
4165
|
// forbidden by construction (the parent/group reads are RLS-workspace-scoped).
|
|
3090
|
-
// ENV-AWARE: the box's
|
|
3091
|
-
// 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
|
|
3092
4168
|
// default silently falls back to an own box; an explicit "shared"/{groupId}
|
|
3093
4169
|
// request 422s at create (instead of the first turn dying on the SDK's
|
|
3094
4170
|
// manifest-env guard).
|
|
3095
|
-
sandbox: z
|
|
3096
|
-
z.literal("shared"),
|
|
3097
|
-
|
|
3098
|
-
z.object({ groupId: z.string().uuid() }),
|
|
3099
|
-
]).optional(),
|
|
4171
|
+
sandbox: z
|
|
4172
|
+
.union([z.literal("shared"), z.literal("new"), z.object({ groupId: z.string().uuid() })])
|
|
4173
|
+
.optional(),
|
|
3100
4174
|
});
|
|
3101
4175
|
export type CreateSessionRequest = z.infer<typeof CreateSessionRequest>;
|
|
3102
4176
|
|
|
@@ -3115,11 +4189,6 @@ export const ClientSessionEvent = z.discriminatedUnion("type", [
|
|
|
3115
4189
|
mcpCredentialUpdates: z.array(SessionMcpCredentialUpdateInput).optional(),
|
|
3116
4190
|
}),
|
|
3117
4191
|
}),
|
|
3118
|
-
z.object({
|
|
3119
|
-
type: z.literal("user.interrupt"),
|
|
3120
|
-
clientEventId: z.string().min(1).optional(),
|
|
3121
|
-
payload: z.object({ reason: z.string().optional() }).default({}),
|
|
3122
|
-
}),
|
|
3123
4192
|
z.object({
|
|
3124
4193
|
type: z.literal("user.approvalDecision"),
|
|
3125
4194
|
clientEventId: z.string().min(1).optional(),
|
|
@@ -3132,6 +4201,25 @@ export const ClientSessionEvent = z.discriminatedUnion("type", [
|
|
|
3132
4201
|
]);
|
|
3133
4202
|
export type ClientSessionEvent = z.infer<typeof ClientSessionEvent>;
|
|
3134
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
|
+
|
|
3135
4223
|
export const SessionBusMessage = z.object({
|
|
3136
4224
|
workspaceId: z.string().uuid(),
|
|
3137
4225
|
sessionId: z.string().uuid(),
|
|
@@ -3435,7 +4523,13 @@ export const DeviceEnrollmentPollRequest = z.object({
|
|
|
3435
4523
|
});
|
|
3436
4524
|
export type DeviceEnrollmentPollRequest = z.infer<typeof DeviceEnrollmentPollRequest>;
|
|
3437
4525
|
|
|
3438
|
-
export const DeviceEnrollmentState = z.enum([
|
|
4526
|
+
export const DeviceEnrollmentState = z.enum([
|
|
4527
|
+
"pending",
|
|
4528
|
+
"authorized",
|
|
4529
|
+
"denied",
|
|
4530
|
+
"expired",
|
|
4531
|
+
"disabled",
|
|
4532
|
+
]);
|
|
3439
4533
|
export type DeviceEnrollmentState = z.infer<typeof DeviceEnrollmentState>;
|
|
3440
4534
|
|
|
3441
4535
|
// The EnrollmentCredentials (field names match the proto's JSON). natsAccountCreds
|
|
@@ -3717,6 +4811,19 @@ export const SwapActiveSandboxResponse = z.object({
|
|
|
3717
4811
|
activeSandboxId: z.string().nullable(),
|
|
3718
4812
|
activeEpoch: z.number().int(),
|
|
3719
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(),
|
|
3720
4827
|
});
|
|
3721
4828
|
export type SwapActiveSandboxResponse = z.infer<typeof SwapActiveSandboxResponse>;
|
|
3722
4829
|
|
|
@@ -3739,7 +4846,7 @@ export type MachineMetricsSeriesResponse = z.infer<typeof MachineMetricsSeriesRe
|
|
|
3739
4846
|
export const ClientModel = z.object({
|
|
3740
4847
|
id: z.string(),
|
|
3741
4848
|
label: z.string(),
|
|
3742
|
-
provider: z.string(),
|
|
4849
|
+
provider: z.string(), // provider id
|
|
3743
4850
|
providerLabel: z.string(),
|
|
3744
4851
|
api: z.enum(["responses", "chat"]),
|
|
3745
4852
|
contextWindowTokens: z.number().int().positive().optional(),
|
|
@@ -3759,10 +4866,14 @@ export const ClientConfig = z.object({
|
|
|
3759
4866
|
models: z.array(ClientModel).default([]),
|
|
3760
4867
|
defaultReasoningEffort: ReasoningEffort,
|
|
3761
4868
|
allowedReasoningEfforts: z.array(ReasoningEffort).min(1),
|
|
3762
|
-
mcpServers: z
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
4869
|
+
mcpServers: z
|
|
4870
|
+
.array(
|
|
4871
|
+
z.object({
|
|
4872
|
+
id: z.string(),
|
|
4873
|
+
name: z.string(),
|
|
4874
|
+
}),
|
|
4875
|
+
)
|
|
4876
|
+
.default([]),
|
|
3766
4877
|
fileUploads: z.object({
|
|
3767
4878
|
enabled: z.boolean(),
|
|
3768
4879
|
maxSizeBytes: z.number().int().positive(),
|
|
@@ -3773,11 +4884,13 @@ export const ClientConfig = z.object({
|
|
|
3773
4884
|
// at all (P4.4). Per-session availability is negotiated on /stream-capabilities
|
|
3774
4885
|
// (it depends on the session's pinned backend); this is the coarse on/off the
|
|
3775
4886
|
// client uses to decide whether to even attempt the fs/git/terminal panels.
|
|
3776
|
-
structuredServices: z
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
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 }),
|
|
3781
4894
|
});
|
|
3782
4895
|
export type ClientConfig = z.infer<typeof ClientConfig>;
|
|
3783
4896
|
|
|
@@ -3816,6 +4929,42 @@ function constantTimeEqual(actual: string, expected: string): boolean {
|
|
|
3816
4929
|
|
|
3817
4930
|
export type HealthResponse = {
|
|
3818
4931
|
service: string;
|
|
3819
|
-
|
|
4932
|
+
variableSet: string;
|
|
3820
4933
|
ok: boolean;
|
|
3821
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
|
+
}
|