@opengeni/contracts 4.1.0 → 5.0.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.
Files changed (69) hide show
  1. package/dist/atlassian.d.ts +1 -0
  2. package/dist/atlassian.js +4 -4
  3. package/dist/bundled-skills.d.ts +5 -1
  4. package/dist/{chunk-HR6NB3CA.js → chunk-CM7E7PFV.js} +8341 -8206
  5. package/dist/chunk-CM7E7PFV.js.map +1 -0
  6. package/dist/{chunk-O5LEQHDM.js → chunk-ENJTPHKP.js} +2 -2
  7. package/dist/chunk-KUCZDFJ5.js +186 -0
  8. package/dist/chunk-KUCZDFJ5.js.map +1 -0
  9. package/dist/{chunk-5LYFSZ7O.js → chunk-YJEOWXOE.js} +8 -8
  10. package/dist/company-profile.d.ts +9 -6
  11. package/dist/connect.js +4 -4
  12. package/dist/connection-authority.d.ts +9 -317
  13. package/dist/connection-authority.js +7 -27
  14. package/dist/connector-tool-permissions.d.ts +73 -0
  15. package/dist/editable-artifact-codec-registry.js +2 -2
  16. package/dist/editable-artifact-live.js +2 -2
  17. package/dist/editable-artifact-serialized-commit.js +3 -3
  18. package/dist/editable-artifacts.js +13 -13
  19. package/dist/google-drive.d.ts +1 -0
  20. package/dist/google-drive.js +4 -4
  21. package/dist/host-mcp-bindings.d.ts +5 -0
  22. package/dist/host-mcp-bindings.js +34 -8
  23. package/dist/host-mcp-bindings.js.map +1 -1
  24. package/dist/index.d.ts +771 -3222
  25. package/dist/index.js +121 -75
  26. package/dist/knowledge-entries.d.ts +33 -0
  27. package/dist/knowledge-preparation.d.ts +207 -0
  28. package/dist/managed-sign-in-methods.d.ts +56 -0
  29. package/dist/managed-sign-in-methods.js +46 -0
  30. package/dist/managed-sign-in-methods.js.map +1 -0
  31. package/dist/mcp-oauth.d.ts +2 -2
  32. package/dist/organization-integration-policy.d.ts +27 -0
  33. package/dist/organization-usage.d.ts +102 -0
  34. package/dist/personal-github.d.ts +1 -0
  35. package/dist/personal-github.js +4 -4
  36. package/dist/remember.d.ts +6 -4
  37. package/dist/sandbox-provider-command.d.ts +91 -1
  38. package/dist/sandbox-recovery.d.ts +101 -0
  39. package/dist/session-goal-reports.d.ts +21 -0
  40. package/dist/session-message-search.d.ts +68 -0
  41. package/dist/skill-catalog-context.d.ts +7 -0
  42. package/dist/skills.d.ts +15 -0
  43. package/package.json +4 -4
  44. package/src/bundled-skills.ts +2 -0
  45. package/src/connection-authority.ts +25 -225
  46. package/src/connector-tool-permissions.ts +59 -0
  47. package/src/host-mcp-bindings.ts +37 -5
  48. package/src/index.ts +406 -755
  49. package/src/knowledge-entries.ts +4 -0
  50. package/src/knowledge-preparation.ts +50 -0
  51. package/src/managed-sign-in-methods.ts +37 -0
  52. package/src/mcp-oauth.ts +16 -18
  53. package/src/organization-integration-policy.ts +54 -0
  54. package/src/organization-usage.ts +45 -0
  55. package/src/sandbox-provider-command.ts +57 -1
  56. package/src/sandbox-recovery.ts +61 -0
  57. package/src/session-goal-reports.ts +45 -0
  58. package/src/session-message-search.ts +54 -0
  59. package/src/skill-catalog-context.ts +50 -0
  60. package/src/skills.ts +21 -6
  61. package/dist/chunk-HR6NB3CA.js.map +0 -1
  62. package/dist/chunk-J6QIYKO5.js +0 -348
  63. package/dist/chunk-J6QIYKO5.js.map +0 -1
  64. package/dist/host-mcp-resolvers.d.ts +0 -32
  65. package/dist/host-mcp-resolvers.js +0 -34
  66. package/dist/host-mcp-resolvers.js.map +0 -1
  67. package/src/host-mcp-resolvers.ts +0 -50
  68. /package/dist/{chunk-O5LEQHDM.js.map → chunk-ENJTPHKP.js.map} +0 -0
  69. /package/dist/{chunk-5LYFSZ7O.js.map → chunk-YJEOWXOE.js.map} +0 -0
@@ -66,6 +66,8 @@ export const KnowledgeEntrySource = z
66
66
  version: z.string().min(1).max(2048).optional(),
67
67
  capturedAt: z.string().datetime({ offset: true }).optional(),
68
68
  retention: z.enum(["full_text", "passages", "reference"]).optional(),
69
+ /** Supporting evidence stays retrievable by ID without entering default discovery. */
70
+ purpose: z.enum(["evidence", "reference"]).optional(),
69
71
  })
70
72
  .strict();
71
73
  export type KnowledgeEntrySource = z.infer<typeof KnowledgeEntrySource>;
@@ -252,6 +254,8 @@ export const KnowledgeEntryListRequest = z
252
254
  /** Top-level entries with no visible collection in the selected scope. */
253
255
  rootOnly: z.boolean().optional(),
254
256
  fileId: z.uuid().optional(),
257
+ /** Include supporting sources in published discovery; never changes access. */
258
+ includeEvidence: z.boolean().optional(),
255
259
  kind: KnowledgeEntryKind.optional(),
256
260
  view: z.enum(["published", "needs_review", "archived", "rejected"]).default("published"),
257
261
  sessionId: z.uuid().optional(),
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ import { KnowledgeEntryListResponse, KnowledgeEntryScope } from "./knowledge-entries";
3
+
4
+ /** Read-only discovery before a model decides whether and where to save. */
5
+ export const KnowledgeSavePreparationRequest = z
6
+ .object({
7
+ query: z.string().trim().min(1).max(4096),
8
+ limit: z.number().int().positive().max(20).default(8),
9
+ collectionCursors: z
10
+ .object({
11
+ published: z.string().min(1).max(2048).nullable(),
12
+ needs_review: z.string().min(1).max(2048).nullable(),
13
+ })
14
+ .strict()
15
+ .optional(),
16
+ })
17
+ .strict();
18
+ export type KnowledgeSavePreparationRequest = z.input<typeof KnowledgeSavePreparationRequest>;
19
+
20
+ export const KnowledgeCollectionDescriptor = z
21
+ .object({
22
+ id: z.uuid(),
23
+ revisionId: z.uuid(),
24
+ version: z.number().int().positive(),
25
+ scope: KnowledgeEntryScope,
26
+ view: z.enum(["published", "needs_review"]),
27
+ title: z.string(),
28
+ description: z.string(),
29
+ descriptionTruncated: z.boolean(),
30
+ parentIds: z.array(z.uuid()),
31
+ })
32
+ .strict();
33
+
34
+ export const KnowledgeSavePreparationResponse = z
35
+ .object({
36
+ collections: z.object({
37
+ entries: z.array(KnowledgeCollectionDescriptor),
38
+ complete: z.boolean(),
39
+ nextCursors: z.object({
40
+ published: z.string().nullable(),
41
+ needs_review: z.string().nullable(),
42
+ }),
43
+ }),
44
+ matches: z.object({
45
+ published: KnowledgeEntryListResponse,
46
+ needs_review: KnowledgeEntryListResponse,
47
+ }),
48
+ })
49
+ .strict();
50
+ export type KnowledgeSavePreparationResponse = z.infer<typeof KnowledgeSavePreparationResponse>;
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+
3
+ export const ManagedSignInProvider = z.enum(["google", "github"]);
4
+ export const ManagedSignInMethods = z.object({
5
+ identityId: z.string().uuid(),
6
+ email: z.string().email(),
7
+ emailVerified: z.boolean(),
8
+ identityRevision: z.number().int().positive(),
9
+ freshAuthenticationRequired: z.boolean(),
10
+ methods: z.array(
11
+ z.object({
12
+ provider: z.enum(["credential", "google", "github"]),
13
+ connected: z.boolean(),
14
+ available: z.boolean(),
15
+ canDisconnect: z.boolean(),
16
+ implicitRelinkingSuppressed: z.boolean(),
17
+ }),
18
+ ),
19
+ });
20
+ export type ManagedSignInMethods = z.infer<typeof ManagedSignInMethods>;
21
+ export const ManagedSignInMutation = z.object({
22
+ expectedIdentityId: z.string().uuid(),
23
+ operationId: z.string().uuid(),
24
+ expectedIdentityRevision: z.number().int().positive(),
25
+ });
26
+ export const ManagedSignInProviderMutation = ManagedSignInMutation.extend({
27
+ provider: ManagedSignInProvider,
28
+ }).strict();
29
+ export const ManagedSignInPasswordMutation = ManagedSignInMutation.extend({
30
+ newPassword: z.string().min(8).max(128),
31
+ currentPassword: z.string().min(1).max(1024).optional(),
32
+ }).strict();
33
+ export const ManagedSignInMutationResponse = z.object({
34
+ reauthenticationRequired: z.literal(true),
35
+ notification: z.enum(["sent", "failed", "outcome_unknown"]),
36
+ });
37
+ export const ManagedSignInConnectResponse = z.object({ url: z.string().url() });
package/src/mcp-oauth.ts CHANGED
@@ -8,24 +8,22 @@ export const MCP_OAUTH_CONSENT_TTL_SECONDS = 10 * 60;
8
8
 
9
9
  const RedirectUri = z.string().url().max(2_048);
10
10
 
11
- export const McpOAuthClientRegistrationRequest = z
12
- .object({
13
- redirect_uris: z.array(RedirectUri).min(1).max(16),
14
- client_name: z.string().trim().min(1).max(200).optional(),
15
- application_type: z.enum(["native", "web"]).optional(),
16
- token_endpoint_auth_method: z.literal("none").default("none"),
17
- scope: z.literal(MCP_OAUTH_SCOPE).optional(),
18
- grant_types: z
19
- .array(z.enum(["authorization_code", "refresh_token"]))
20
- .min(1)
21
- .max(2)
22
- .refine((grantTypes) => grantTypes.includes("authorization_code"), {
23
- message: "authorization_code grant is required",
24
- })
25
- .default(["authorization_code", "refresh_token"]),
26
- response_types: z.array(z.literal("code")).min(1).max(1).default(["code"]),
27
- })
28
- .strict();
11
+ export const McpOAuthClientRegistrationRequest = z.object({
12
+ redirect_uris: z.array(RedirectUri).min(1).max(16),
13
+ client_name: z.string().trim().min(1).max(200).optional(),
14
+ application_type: z.enum(["native", "web"]).optional(),
15
+ token_endpoint_auth_method: z.literal("none").default("none"),
16
+ scope: z.literal(MCP_OAUTH_SCOPE).optional(),
17
+ grant_types: z
18
+ .array(z.enum(["authorization_code", "refresh_token"]))
19
+ .min(1)
20
+ .max(2)
21
+ .refine((grantTypes) => grantTypes.includes("authorization_code"), {
22
+ message: "authorization_code grant is required",
23
+ })
24
+ .default(["authorization_code", "refresh_token"]),
25
+ response_types: z.array(z.literal("code")).min(1).max(1).default(["code"]),
26
+ });
29
27
  export type McpOAuthClientRegistrationRequest = z.infer<typeof McpOAuthClientRegistrationRequest>;
30
28
 
31
29
  export const McpOAuthClientRegistrationResponse = McpOAuthClientRegistrationRequest.extend({
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+
3
+ /** Catalog identities, never endpoint/domain matching. Custom transports are explicit categories. */
4
+ export const OrganizationIntegrationKey = z
5
+ .string()
6
+ .min(1)
7
+ .max(200)
8
+ .regex(/^[a-z0-9][a-z0-9._:-]*$/);
9
+ const keys = z
10
+ .array(OrganizationIntegrationKey)
11
+ .max(10000)
12
+ .refine((values) => new Set(values).size === values.length, "Duplicate integration identities");
13
+ export const OrganizationIntegrationPolicy = z
14
+ .object({
15
+ mode: z.enum(["unrestricted", "restricted"]).default("unrestricted"),
16
+ allowedIntegrationKeys: keys.default([]),
17
+ revision: z.number().int().nonnegative().safe().default(0),
18
+ })
19
+ .strict();
20
+ export type OrganizationIntegrationPolicy = z.infer<typeof OrganizationIntegrationPolicy>;
21
+
22
+ export const UpdateOrganizationIntegrationPolicyRequest = z
23
+ .object({
24
+ mode: z.enum(["unrestricted", "restricted"]),
25
+ allowedIntegrationKeys: keys,
26
+ expectedRevision: z
27
+ .number()
28
+ .int()
29
+ .nonnegative()
30
+ .max(Number.MAX_SAFE_INTEGER - 1),
31
+ operationId: z.string().uuid(),
32
+ })
33
+ .strict();
34
+ export type UpdateOrganizationIntegrationPolicyRequest = z.infer<
35
+ typeof UpdateOrganizationIntegrationPolicyRequest
36
+ >;
37
+
38
+ export class OrganizationIntegrationDeniedError extends Error {
39
+ constructor() {
40
+ super("Integration acquisition is not allowed by organization policy");
41
+ this.name = "OrganizationIntegrationDeniedError";
42
+ }
43
+ }
44
+
45
+ /** integrationKey must come from trusted classification, not client-supplied metadata. */
46
+ export function assertOrganizationIntegrationAllowed(
47
+ policy: OrganizationIntegrationPolicy,
48
+ integrationKey: string | null,
49
+ ): void {
50
+ if (policy.mode === "unrestricted") return;
51
+ if (integrationKey === null || !policy.allowedIntegrationKeys.includes(integrationKey)) {
52
+ throw new OrganizationIntegrationDeniedError();
53
+ }
54
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+
3
+ export const OrganizationUsagePeriod = z.enum(["today", "week", "month", "ytd"]);
4
+ export type OrganizationUsagePeriod = z.infer<typeof OrganizationUsagePeriod>;
5
+ export const OrganizationUsageQuery = z.object({
6
+ period: OrganizationUsagePeriod.default("month"),
7
+ });
8
+ export const OrganizationUsageWorkspacePageQuery = OrganizationUsageQuery.extend({
9
+ until: z.string().datetime(),
10
+ afterWorkspaceId: z.string().uuid().optional(),
11
+ });
12
+ // PostgreSQL SUM(bigint) must never pass through a JavaScript number.
13
+ export const OrganizationUsageTotal = z.object({
14
+ eventType: z.string(),
15
+ unit: z.string(),
16
+ quantity: z.string().regex(/^-?\d+$/),
17
+ eventCount: z.string().regex(/^\d+$/),
18
+ });
19
+ export const OrganizationUsageSummary = z.object({
20
+ accountId: z.string().uuid(),
21
+ period: OrganizationUsagePeriod,
22
+ since: z.string().datetime(),
23
+ until: z.string().datetime(),
24
+ granularity: z.enum(["hour", "day"]),
25
+ totals: z.array(OrganizationUsageTotal),
26
+ buckets: z.array(z.object({ bucket: z.string(), totals: z.array(OrganizationUsageTotal) })),
27
+ workspaces: z
28
+ .array(
29
+ z.object({
30
+ workspaceId: z.string().uuid(),
31
+ name: z.string().nullable(),
32
+ totals: z.array(OrganizationUsageTotal),
33
+ }),
34
+ )
35
+ .max(50),
36
+ nextWorkspaceCursor: z.string().uuid().nullable(),
37
+ });
38
+ export type OrganizationUsageSummary = z.infer<typeof OrganizationUsageSummary>;
39
+
40
+ /** A page deliberately excludes totals and charts: those are not recomputed. */
41
+ export const OrganizationUsageWorkspacePage = OrganizationUsageSummary.omit({
42
+ totals: true,
43
+ buckets: true,
44
+ });
45
+ export type OrganizationUsageWorkspacePage = z.infer<typeof OrganizationUsageWorkspacePage>;
@@ -13,7 +13,7 @@ const ModalOutputCursor = z
13
13
 
14
14
  /** Provider-owned execution identity and acknowledged output cursors. These
15
15
  * values are retained by the control plane, never recovered from sandbox files. */
16
- export const SandboxProviderCommand = z
16
+ export const ModalLegacyProviderCommand = z
17
17
  .object({
18
18
  kind: z.literal("modal-control-v1"),
19
19
  sandboxId: z.string().min(1).max(200),
@@ -23,4 +23,60 @@ export const SandboxProviderCommand = z
23
23
  streams: z.object({ stdout: ModalOutputCursor, stderr: ModalOutputCursor }).strict(),
24
24
  })
25
25
  .strict();
26
+ export type ModalLegacyProviderCommand = z.infer<typeof ModalLegacyProviderCommand>;
27
+
28
+ const ModalByteCursor = z
29
+ .object({
30
+ byteOffset: z.number().int().nonnegative().safe(),
31
+ utf8Remainder: z
32
+ .string()
33
+ .max(8)
34
+ .regex(/^[A-Za-z0-9+/]*={0,2}$/u),
35
+ eof: z.boolean(),
36
+ exitCode: z.number().int().nullable(),
37
+ })
38
+ .strict()
39
+ .refine((cursor) => cursor.exitCode === null || cursor.eof, {
40
+ message: "A terminal output cursor must have reached EOF",
41
+ });
42
+
43
+ export const CommandSupervisionDescriptor = z
44
+ .object({
45
+ protocol: z.literal("native-subreaper-v1"),
46
+ invocationId: z.string().uuid(),
47
+ nonce: z.string().regex(/^[a-f0-9]{64}$/u),
48
+ controlPath: z.string().regex(/^\/tmp\/opengeni-supervision\/[a-f0-9-]{36}\.sock$/u),
49
+ })
50
+ .strict();
51
+ export type CommandSupervisionDescriptor = z.infer<typeof CommandSupervisionDescriptor>;
52
+
53
+ export const CommandSupervisionReceipt = z
54
+ .object({
55
+ protocol: z.literal("native-subreaper-v1"),
56
+ invocationId: z.string().uuid(),
57
+ receiptId: z.string().uuid(),
58
+ leaderExitCode: z.number().int(),
59
+ })
60
+ .strict();
61
+ export type CommandSupervisionReceipt = z.infer<typeof CommandSupervisionReceipt>;
62
+
63
+ export const ModalRouterProviderCommand = z
64
+ .object({
65
+ kind: z.literal("modal-router-v1"),
66
+ sandboxId: z.string().min(1).max(200),
67
+ taskId: z.string().min(1).max(200),
68
+ execId: z.string().uuid(),
69
+ pty: z.boolean().optional(),
70
+ supervision: CommandSupervisionDescriptor.optional(),
71
+ streams: z.object({ stdout: ModalByteCursor, stderr: ModalByteCursor }).strict(),
72
+ })
73
+ .strict();
74
+ export type ModalRouterProviderCommand = z.infer<typeof ModalRouterProviderCommand>;
75
+
76
+ /** Legacy locators are read-only compatibility for already launched commands;
77
+ * new starts use authenticated task-router byte offsets, never batch indices. */
78
+ export const SandboxProviderCommand = z.discriminatedUnion("kind", [
79
+ ModalLegacyProviderCommand,
80
+ ModalRouterProviderCommand,
81
+ ]);
26
82
  export type SandboxProviderCommand = z.infer<typeof SandboxProviderCommand>;
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+
3
+ /** Public, content-free selection. Never include native provider bindings. */
4
+ export const SandboxRecoverySelection = z
5
+ .object({
6
+ version: z.literal(1),
7
+ sessionId: z.string().uuid(),
8
+ sandboxGroupId: z.string().uuid(),
9
+ leaseId: z.string().uuid(),
10
+ routeEpoch: z.number().int().nonnegative(),
11
+ authorityEpoch: z.number().int().positive(),
12
+ leaseEpoch: z.number().int().nonnegative(),
13
+ workspaceGeneration: z.number().int().nonnegative(),
14
+ archiveGeneration: z.number().int().nonnegative(),
15
+ artifactId: z.string().uuid(),
16
+ revision: z.string().min(1).max(512),
17
+ capturedAt: z.string().datetime(),
18
+ })
19
+ .strict();
20
+ export type SandboxRecoverySelection = z.infer<typeof SandboxRecoverySelection>;
21
+
22
+ export const SandboxRecoveryProjection = z
23
+ .object({
24
+ version: z.literal(1),
25
+ status: z.enum([
26
+ "unsupported",
27
+ "blocked",
28
+ "eligible",
29
+ "consent_accepted",
30
+ "restoring",
31
+ "restored",
32
+ ]),
33
+ reason: z.string().max(128).nullable(),
34
+ checkpoint: SandboxRecoverySelection.nullable(),
35
+ operationId: z.string().uuid().nullable(),
36
+ })
37
+ .strict();
38
+ export type SandboxRecoveryProjection = z.infer<typeof SandboxRecoveryProjection>;
39
+
40
+ export const SandboxRecoveryRequest = z
41
+ .object({
42
+ operationId: z.string().uuid(),
43
+ acceptHistoricalCheckpoint: z.literal(true),
44
+ selection: SandboxRecoverySelection,
45
+ })
46
+ .strict();
47
+ export type SandboxRecoveryRequest = z.infer<typeof SandboxRecoveryRequest>;
48
+
49
+ export const SandboxRecoveryResponse = z
50
+ .object({
51
+ outcome: z.enum(["accepted", "replayed"]),
52
+ operationId: z.string().uuid(),
53
+ recovery: SandboxRecoveryProjection,
54
+ })
55
+ .strict();
56
+ export type SandboxRecoveryResponse = z.infer<typeof SandboxRecoveryResponse>;
57
+
58
+ /** Re-injected from durable receipts on every attempt, not lossy transcript state. */
59
+ export function sandboxRecoveryDiscontinuity(selection: SandboxRecoverySelection): string {
60
+ return `Filesystem discontinuity: the human explicitly consented to restoring this session's workspace from the checkpoint captured at ${selection.capturedAt} (archive generation ${selection.archiveGeneration}, pre-recovery workspace generation ${selection.workspaceGeneration}). Newer filesystem changes are unavailable. The generation gap is not a count of lost files or edits. Conversation and tool receipts remain historical evidence, not proof that their files still exist. External effects are not undone. Verify the current filesystem before relying on previous work. Never automatically replay prior commands or operations with unknown outcomes to rebuild missing files. Consent alone is not proof that restoration succeeded.`;
61
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+
3
+ export const SessionGoalReportRequirement = z
4
+ .object({
5
+ id: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/),
6
+ title: z
7
+ .string()
8
+ .trim()
9
+ .min(1)
10
+ .refine(
11
+ (value) => new TextEncoder().encode(value).byteLength <= 512,
12
+ "Report title exceeds 512 UTF-8 bytes",
13
+ ),
14
+ })
15
+ .strict();
16
+ export type SessionGoalReportRequirement = z.infer<typeof SessionGoalReportRequirement>;
17
+
18
+ export const SessionGoalReportRequirements = z
19
+ .array(SessionGoalReportRequirement)
20
+ .max(16)
21
+ .superRefine((requirements, context) => {
22
+ if (new Set(requirements.map((item) => item.id)).size !== requirements.length) {
23
+ context.addIssue({ code: "custom", message: "Duplicate report requirement IDs" });
24
+ }
25
+ });
26
+
27
+ export const SessionGoalReportDelivery = z
28
+ .object({
29
+ requirementId: SessionGoalReportRequirement.shape.id,
30
+ artifactId: z
31
+ .string()
32
+ .regex(/^[0-9a-f]{32}$/)
33
+ .refine((value) => !/^0+$/.test(value)),
34
+ inspectionReceiptId: z.string().uuid(),
35
+ })
36
+ .strict();
37
+ export type SessionGoalReportDelivery = z.infer<typeof SessionGoalReportDelivery>;
38
+ export const SessionGoalReportDeliveries = z
39
+ .array(SessionGoalReportDelivery)
40
+ .max(16)
41
+ .superRefine((deliveries, context) => {
42
+ if (new Set(deliveries.map((item) => item.requirementId)).size !== deliveries.length) {
43
+ context.addIssue({ code: "custom", message: "Duplicate report delivery requirement IDs" });
44
+ }
45
+ });
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+
3
+ /** Literal Unicode case-insensitive search, without trimming or normalization. */
4
+ export const SessionMessageSearchRequest = z
5
+ .object({
6
+ query: z.string().min(1).max(200),
7
+ sessionId: z.string().uuid().optional(),
8
+ groupBy: z.literal("session").optional(),
9
+ archiveStatus: z.enum(["active", "archived", "all"]).optional(),
10
+ limit: z.number().int().min(1).max(50).optional(),
11
+ cursor: z.string().min(1).max(4096).optional(),
12
+ })
13
+ .strict()
14
+ .refine((request) => !request.groupBy || !request.sessionId, {
15
+ message: "groupBy=session is only supported for workspace search without sessionId",
16
+ path: ["groupBy"],
17
+ });
18
+ export type SessionMessageSearchRequest = z.infer<typeof SessionMessageSearchRequest>;
19
+
20
+ export const SessionMessageSearchMatch = z.object({
21
+ sessionId: z.string().uuid(),
22
+ sessionTitle: z.string().nullable(),
23
+ eventId: z.string().uuid(),
24
+ sequence: z.number().int().positive(),
25
+ turnId: z.string().uuid().nullable(),
26
+ role: z.enum(["user", "assistant"]),
27
+ messageId: z.string().nullable(),
28
+ /** All offsets are zero-based UTF-16 code units; ends are exclusive. */
29
+ messageMatchOffset: z.number().int().nonnegative(),
30
+ snippet: z.object({
31
+ text: z.string(),
32
+ matchStart: z.number().int().nonnegative(),
33
+ matchEnd: z.number().int().nonnegative(),
34
+ }),
35
+ });
36
+ export type SessionMessageSearchMatch = z.infer<typeof SessionMessageSearchMatch>;
37
+
38
+ /** A bounded scan page, not necessarily a full page of matches. Continue even
39
+ * after an empty page while hasMore is true. Counts describe this live traversal,
40
+ * not a frozen snapshot; restart to reflect concurrent changes. All non-overlapping
41
+ * literal occurrences in user messages and completed assistant messages.
42
+ * With groupBy=session, matches and both matched counts describe one first-hit
43
+ * representative per matching session, not full message/occurrence totals.
44
+ */
45
+ export const SessionMessageSearchResponse = z.object({
46
+ matches: z.array(SessionMessageSearchMatch).max(50),
47
+ nextCursor: z.string().nullable(),
48
+ hasMore: z.boolean(),
49
+ scannedMessages: z.number().int().nonnegative(),
50
+ matchedMessageCount: z.number().int().nonnegative(),
51
+ matchedOccurrenceCount: z.number().int().nonnegative(),
52
+ countIsExact: z.boolean(),
53
+ });
54
+ export type SessionMessageSearchResponse = z.infer<typeof SessionMessageSearchResponse>;
@@ -0,0 +1,50 @@
1
+ /** Durable, model-visible catalog context. Text markers survive SDK/provider replay. */
2
+ export const SKILL_CATALOG_CONTEXT_PREFIX = "<opengeni_skill_catalog>\n";
3
+ export const SKILL_CATALOG_CONTEXT_SUFFIX = "\n</opengeni_skill_catalog>";
4
+ const GUIDANCE =
5
+ "This is the current configured Skill index and replaces earlier configured Skill indexes. " +
6
+ "Entries describe discoverability, not permission or full instructions. " +
7
+ "Use skill_read for instructions and skill_search for current discovery; tool authorization remains authoritative.\n\n";
8
+
9
+ export function skillCatalogContextItem(catalog: string): Record<string, unknown> {
10
+ return {
11
+ type: "message",
12
+ role: "developer",
13
+ content: SKILL_CATALOG_CONTEXT_PREFIX + GUIDANCE + catalog + SKILL_CATALOG_CONTEXT_SUFFIX,
14
+ };
15
+ }
16
+
17
+ /** Accept the Chat Completions projection and SDK text-part normalization too. */
18
+ export function readSkillCatalogContext(item: Record<string, unknown>): string | null {
19
+ if (item.type === "unknown" && item.providerData && typeof item.providerData === "object") {
20
+ const data = item.providerData as Record<string, unknown>;
21
+ if (data.type === "message") return readSkillCatalogContext(data);
22
+ }
23
+ if (item.type !== "message" || (item.role !== "developer" && item.role !== "system")) return null;
24
+ const content =
25
+ typeof item.content === "string"
26
+ ? item.content
27
+ : Array.isArray(item.content) &&
28
+ item.content.every(
29
+ (part) =>
30
+ part &&
31
+ (part.type === "input_text" || part.type === "output_text" || part.type === "text") &&
32
+ typeof part.text === "string",
33
+ )
34
+ ? item.content.map((part) => part.text).join("")
35
+ : null;
36
+ const prefix = SKILL_CATALOG_CONTEXT_PREFIX + GUIDANCE;
37
+ return content?.startsWith(prefix) && content.endsWith(SKILL_CATALOG_CONTEXT_SUFFIX)
38
+ ? content.slice(prefix.length, -SKILL_CATALOG_CONTEXT_SUFFIX.length)
39
+ : null;
40
+ }
41
+
42
+ export function latestSkillCatalogContext(
43
+ items: readonly Record<string, unknown>[],
44
+ ): Record<string, unknown> | undefined {
45
+ for (let index = items.length - 1; index >= 0; index--) {
46
+ const catalog = readSkillCatalogContext(items[index]!);
47
+ if (catalog !== null) return skillCatalogContextItem(catalog);
48
+ }
49
+ return undefined;
50
+ }
package/src/skills.ts CHANGED
@@ -38,6 +38,7 @@ export type SkillSaveInput = SkillWriteContext & {
38
38
  reason: string;
39
39
  };
40
40
  export type SkillRevisionInput = SkillWriteContext & {
41
+ removalOperationId?: string | undefined;
41
42
  operationId: string;
42
43
  skillId: string;
43
44
  revisionId: string;
@@ -45,12 +46,20 @@ export type SkillRevisionInput = SkillWriteContext & {
45
46
  expectedScopeVersion: number;
46
47
  reason: string;
47
48
  };
49
+ export type SkillRemoveInput = SkillWriteContext & {
50
+ operationId: string;
51
+ skillId: string;
52
+ expectedRevisionId: string | null;
53
+ expectedScopeVersion: number;
54
+ reason: string;
55
+ };
48
56
  export type SkillInstallInput = SkillWriteContext & {
49
57
  operationId: string;
50
58
  skillFacetId: string;
51
59
  reason: string;
52
60
  };
53
61
  export const SkillReviewReference = z.object({
62
+ removalOperationId: z.uuid().optional(),
54
63
  sourceOperationId: z.uuid(),
55
64
  skillId: z.uuid(),
56
65
  revisionId: z.uuid(),
@@ -59,18 +68,22 @@ export const SkillReviewReference = z.object({
59
68
  });
60
69
  export type SkillReviewReference = z.infer<typeof SkillReviewReference>;
61
70
  export function skillReviewHumanInput(skillReview: SkillReviewReference) {
71
+ const removing = Boolean(skillReview.removalOperationId);
62
72
  return {
63
73
  questions: [
64
74
  {
65
75
  id: `skill:${skillReview.revisionId}`,
66
76
  kind: "single_select" as const,
67
- label: "Save this Skill?",
68
- prompt: "Save this exact Skill revision for this workspace?",
69
- helpText:
70
- "Review the complete files before saving. Saving activates this revision immediately.",
77
+ label: removing ? "Permanently delete this Skill?" : "Save this Skill?",
78
+ prompt: removing
79
+ ? "Permanently delete this Skill and all its stored revisions?"
80
+ : "Save this exact Skill revision for this workspace?",
81
+ helpText: removing
82
+ ? "This cannot be undone. All stored Skill revisions will be deleted; conversations remain unchanged."
83
+ : "Review the complete files before saving. Saving activates this revision immediately.",
71
84
  options: [
72
- { id: "save", label: "Save" },
73
- { id: "skip", label: "Don't save" },
85
+ { id: "save", label: removing ? "Permanently delete" : "Save" },
86
+ { id: "skip", label: removing ? "Keep Skill" : "Don't save" },
74
87
  ],
75
88
  required: true,
76
89
  allowOther: false,
@@ -80,6 +93,7 @@ export function skillReviewHumanInput(skillReview: SkillReviewReference) {
80
93
  };
81
94
  }
82
95
  export const SkillWriteReceipt = z.object({
96
+ removed: z.boolean().optional(),
83
97
  operationId: z.uuid(),
84
98
  skillId: z.uuid(),
85
99
  revisionId: z.uuid(),
@@ -104,6 +118,7 @@ export const SkillSourceReleaseReceipt = z.object({
104
118
  });
105
119
  export type SkillSourceReleaseReceipt = z.infer<typeof SkillSourceReleaseReceipt>;
106
120
  export type SkillRecord = {
121
+ removalOperationId?: string | null;
107
122
  activationMode: "workspace_managed" | "session_selected";
108
123
  pendingRevisionIds: string[];
109
124
  id: string;