@tangle-network/agent-interface 0.45.0 → 0.46.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 (84) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-candidate-profile-schema.d.ts +2 -2
  3. package/dist/agent-candidate-promotion-schema.d.ts +22 -22
  4. package/dist/agent-candidate-schema-common.js +1 -1
  5. package/dist/agent-candidate-schema.d.ts +2 -2
  6. package/dist/agent-execution-preparation.d.ts +26 -26
  7. package/dist/agent-profile.d.ts +10 -3
  8. package/dist/agent-profile.js +45 -22
  9. package/dist/backend-message.d.ts +14 -0
  10. package/dist/backend-message.js +1 -0
  11. package/dist/contract-limits.d.ts +26 -0
  12. package/dist/contract-limits.js +175 -0
  13. package/dist/environment-exact-process.d.ts +161 -0
  14. package/dist/environment-exact-process.js +1 -0
  15. package/dist/environment-profile-capabilities.d.ts +26 -0
  16. package/dist/environment-profile-capabilities.js +34 -0
  17. package/dist/environment-provider.d.ts +3 -730
  18. package/dist/environment-provider.js +3 -245
  19. package/dist/environment-requests.d.ts +70 -0
  20. package/dist/environment-requests.js +1 -0
  21. package/dist/environment-runtime.d.ts +834 -0
  22. package/dist/environment-runtime.js +228 -0
  23. package/dist/execution-types.d.ts +47 -0
  24. package/dist/execution-types.js +1 -0
  25. package/dist/host-services.d.ts +91 -0
  26. package/dist/host-services.js +1 -0
  27. package/dist/index.d.ts +10 -617
  28. package/dist/index.js +10 -125
  29. package/dist/interaction-answer-validation.d.ts +13 -0
  30. package/dist/interaction-answer-validation.js +149 -0
  31. package/dist/interaction-data.d.ts +22 -0
  32. package/dist/interaction-data.js +46 -0
  33. package/dist/interaction-envelope.d.ts +258 -0
  34. package/dist/interaction-envelope.js +245 -0
  35. package/dist/interaction-fields.d.ts +130 -0
  36. package/dist/interaction-fields.js +227 -0
  37. package/dist/interaction-permissions.d.ts +28 -0
  38. package/dist/interaction-permissions.js +57 -0
  39. package/dist/interaction-resolution-validation.d.ts +9 -0
  40. package/dist/interaction-resolution-validation.js +50 -0
  41. package/dist/interaction-response-validation.d.ts +17 -0
  42. package/dist/interaction-response-validation.js +102 -0
  43. package/dist/interaction.d.ts +6 -397
  44. package/dist/interaction.js +6 -603
  45. package/dist/mcp.d.ts +38 -0
  46. package/dist/mcp.js +1 -0
  47. package/dist/parts.d.ts +104 -0
  48. package/dist/parts.js +55 -0
  49. package/dist/portable-context-base.d.ts +82 -0
  50. package/dist/portable-context-base.js +63 -0
  51. package/dist/portable-context-continuation.d.ts +168 -0
  52. package/dist/portable-context-continuation.js +194 -0
  53. package/dist/portable-context-plan-request.d.ts +86 -0
  54. package/dist/portable-context-plan-request.js +102 -0
  55. package/dist/portable-context-plan.d.ts +229 -0
  56. package/dist/portable-context-plan.js +241 -0
  57. package/dist/portable-context-shared.d.ts +36 -0
  58. package/dist/portable-context-shared.js +46 -0
  59. package/dist/portable-context-transfer.d.ts +319 -0
  60. package/dist/portable-context-transfer.js +206 -0
  61. package/dist/portable-context.d.ts +5 -753
  62. package/dist/portable-context.js +5 -754
  63. package/dist/profile-diff.js +56 -47
  64. package/dist/provider-adapter.d.ts +45 -0
  65. package/dist/provider-adapter.js +1 -0
  66. package/dist/provider-config.d.ts +58 -0
  67. package/dist/provider-config.js +42 -0
  68. package/dist/runtime-control.d.ts +87 -10
  69. package/dist/runtime-control.js +159 -38
  70. package/dist/stream-events.d.ts +55 -0
  71. package/dist/stream-events.js +1 -0
  72. package/dist/workspace-branching-shared.d.ts +7 -0
  73. package/dist/workspace-branching-shared.js +20 -0
  74. package/dist/workspace-branching.d.ts +4 -254
  75. package/dist/workspace-branching.js +4 -400
  76. package/dist/workspace-checkpoint.d.ts +99 -0
  77. package/dist/workspace-checkpoint.js +169 -0
  78. package/dist/workspace-cleanup.d.ts +85 -0
  79. package/dist/workspace-cleanup.js +156 -0
  80. package/dist/workspace-confidentiality.d.ts +59 -0
  81. package/dist/workspace-confidentiality.js +123 -0
  82. package/dist/workspace-fork.d.ts +182 -0
  83. package/dist/workspace-fork.js +267 -0
  84. package/package.json +1 -1
@@ -0,0 +1,245 @@
1
+ import { z } from "zod";
2
+ import { canonicalCandidateDigest, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
3
+ import { boundedIdentifierSchema, boundedJsonSchema, boundedStringSchema, CONTRACT_MAX_ARRAY_LENGTH, } from "./contract-limits.js";
4
+ import { InteractionDataSchema, InteractionResolutionSchema, } from "./interaction-data.js";
5
+ import { InteractionAnswerSpecSchema, InteractionResponseScopeSchema, } from "./interaction-fields.js";
6
+ import { validateResolutionForRequest } from "./interaction-resolution-validation.js";
7
+ export { InteractionDataSchema, InteractionResolutionSchema, InteractionSecretReferenceSchema, } from "./interaction-data.js";
8
+ const interactionOutcomeValues = ["accepted", "declined", "cancelled"];
9
+ // =============================================================================
10
+ // Subject — what the request is about (drives preview/permission UX).
11
+ // =============================================================================
12
+ export const InteractionSubjectSchema = z.discriminatedUnion("type", [
13
+ z.strictObject({
14
+ type: z.literal("tool"),
15
+ toolName: boundedIdentifierSchema,
16
+ input: boundedJsonSchema.optional(),
17
+ }),
18
+ z.strictObject({ type: z.literal("command"), command: boundedStringSchema }),
19
+ z.strictObject({
20
+ type: z.literal("file"),
21
+ path: boundedStringSchema,
22
+ preview: boundedStringSchema.optional(),
23
+ }),
24
+ z.strictObject({ type: z.literal("resource"), uri: boundedStringSchema }),
25
+ ]);
26
+ export const InteractionOutcomeSchema = z.enum(interactionOutcomeValues);
27
+ export const InteractionRequestBindingSchema = z.strictObject({
28
+ runId: boundedIdentifierSchema,
29
+ provider: boundedIdentifierSchema,
30
+ environmentId: boundedIdentifierSchema,
31
+ sessionId: boundedIdentifierSchema,
32
+ executionId: boundedIdentifierSchema,
33
+ interactionId: boundedIdentifierSchema,
34
+ });
35
+ /** Binding carried by a response command and acknowledgement. */
36
+ export const InteractionBindingSchema = z.strictObject({
37
+ ...InteractionRequestBindingSchema.shape,
38
+ requestDigest: sha256DigestSchema,
39
+ });
40
+ const InteractionRequestMaterialSchema = z.strictObject({
41
+ id: boundedIdentifierSchema,
42
+ kind: boundedIdentifierSchema,
43
+ title: boundedStringSchema.min(1),
44
+ body: boundedStringSchema.optional(),
45
+ subject: InteractionSubjectSchema.optional(),
46
+ answerSpec: InteractionAnswerSpecSchema,
47
+ responseScopes: z
48
+ .array(InteractionResponseScopeSchema)
49
+ .min(1)
50
+ .max(CONTRACT_MAX_ARRAY_LENGTH)
51
+ .optional(),
52
+ allowedOutcomes: z
53
+ .array(InteractionOutcomeSchema)
54
+ .min(1)
55
+ .max(interactionOutcomeValues.length)
56
+ .optional(),
57
+ default: InteractionResolutionSchema.optional(),
58
+ timeoutMs: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
59
+ onTimeout: z.enum(["default", "fail", "wait"]).optional(),
60
+ binding: InteractionRequestBindingSchema,
61
+ });
62
+ export function interactionRequestDigest(request) {
63
+ const parsed = InteractionRequestMaterialSchema.parse(request);
64
+ rejectSecretDefaultValues(parsed);
65
+ return canonicalCandidateDigest(parsed);
66
+ }
67
+ function rejectSecretDefaultValues(request) {
68
+ const secretFields = new Set(request.answerSpec.fields
69
+ .filter((field) => field.type === "secret")
70
+ .map((field) => field.name));
71
+ for (const fieldName of Object.keys(request.default?.data ?? {})) {
72
+ if (secretFields.has(fieldName)) {
73
+ throw new Error("secret answers cannot be embedded in interaction defaults");
74
+ }
75
+ }
76
+ }
77
+ // =============================================================================
78
+ // The request envelope.
79
+ // =============================================================================
80
+ export const InteractionRequestSchema = z
81
+ .strictObject({
82
+ ...InteractionRequestMaterialSchema.shape,
83
+ requestDigest: sha256DigestSchema,
84
+ })
85
+ .superRefine((request, context) => {
86
+ if (request.binding.interactionId !== request.id) {
87
+ context.addIssue({
88
+ code: "custom",
89
+ path: ["binding", "interactionId"],
90
+ message: "interaction binding must name the request id",
91
+ });
92
+ }
93
+ if (request.responseScopes &&
94
+ new Set(request.responseScopes).size !== request.responseScopes.length) {
95
+ context.addIssue({
96
+ code: "custom",
97
+ path: ["responseScopes"],
98
+ message: "interaction response scopes must be unique",
99
+ });
100
+ }
101
+ if (request.allowedOutcomes &&
102
+ new Set(request.allowedOutcomes).size !== request.allowedOutcomes.length) {
103
+ context.addIssue({
104
+ code: "custom",
105
+ path: ["allowedOutcomes"],
106
+ message: "interaction outcomes must be unique",
107
+ });
108
+ }
109
+ if (request.onTimeout === "default" && request.default === undefined) {
110
+ context.addIssue({
111
+ code: "custom",
112
+ path: ["default"],
113
+ message: "onTimeout=default requires a default resolution",
114
+ });
115
+ }
116
+ if (request.default &&
117
+ request.allowedOutcomes &&
118
+ !request.allowedOutcomes.includes(request.default.outcome)) {
119
+ context.addIssue({
120
+ code: "custom",
121
+ path: ["default", "outcome"],
122
+ message: "default outcome is not allowed by the request",
123
+ });
124
+ }
125
+ if (request.default) {
126
+ for (const error of validateResolutionForRequest(request, request.default)) {
127
+ context.addIssue({ code: "custom", path: ["default"], message: error });
128
+ }
129
+ const secretFields = new Set(request.answerSpec.fields
130
+ .filter((field) => field.type === "secret")
131
+ .map((field) => field.name));
132
+ for (const fieldName of Object.keys(request.default.data ?? {})) {
133
+ if (!secretFields.has(fieldName))
134
+ continue;
135
+ context.addIssue({
136
+ code: "custom",
137
+ path: ["default", "data", fieldName],
138
+ message: "secret answers cannot be embedded in interaction defaults",
139
+ });
140
+ }
141
+ }
142
+ const { requestDigest: _digest, ...material } = request;
143
+ const hasSecretDefault = Object.keys(request.default?.data ?? {}).some((fieldName) => request.answerSpec.fields.some((field) => field.type === "secret" && field.name === fieldName));
144
+ if (!hasSecretDefault) {
145
+ if (request.requestDigest !== interactionRequestDigest(material)) {
146
+ context.addIssue({
147
+ code: "custom",
148
+ path: ["requestDigest"],
149
+ message: "interaction request digest does not match its content",
150
+ });
151
+ }
152
+ }
153
+ });
154
+ export const InteractionResponseSchema = z.discriminatedUnion("outcome", [
155
+ z.strictObject({
156
+ id: boundedIdentifierSchema,
157
+ outcome: z.literal("accepted"),
158
+ data: InteractionDataSchema.optional(),
159
+ }),
160
+ z.strictObject({
161
+ id: boundedIdentifierSchema,
162
+ outcome: z.literal("declined"),
163
+ data: InteractionDataSchema.optional(),
164
+ }),
165
+ z.strictObject({
166
+ id: boundedIdentifierSchema,
167
+ outcome: z.literal("cancelled"),
168
+ data: InteractionDataSchema.optional(),
169
+ }),
170
+ ]);
171
+ /** Digest the exact binding and response carried by a retryable command. */
172
+ export function interactionResponseCommandDigest(material) {
173
+ const binding = InteractionBindingSchema.parse(material.binding);
174
+ const response = InteractionResponseSchema.parse(material.response);
175
+ return canonicalCandidateDigest({ binding, response });
176
+ }
177
+ /** Retryable command sent to an environment or retained session. */
178
+ export const InteractionResponseCommandSchema = z
179
+ .strictObject({
180
+ operationId: boundedIdentifierSchema,
181
+ binding: InteractionBindingSchema,
182
+ commandDigest: sha256DigestSchema,
183
+ response: InteractionResponseSchema,
184
+ })
185
+ .superRefine((command, context) => {
186
+ if (command.binding.interactionId !== command.response.id) {
187
+ context.addIssue({
188
+ code: "custom",
189
+ path: ["response", "id"],
190
+ message: "response id must match the bound interaction id",
191
+ });
192
+ }
193
+ if (command.commandDigest !==
194
+ interactionResponseCommandDigest({
195
+ binding: command.binding,
196
+ response: command.response,
197
+ })) {
198
+ context.addIssue({
199
+ code: "custom",
200
+ path: ["commandDigest"],
201
+ message: "interaction response command digest does not match its content",
202
+ });
203
+ }
204
+ });
205
+ export const InteractionAcknowledgementStatusSchema = z.enum([
206
+ "accepted",
207
+ "already_resolved_same",
208
+ "already_resolved_different",
209
+ "expired",
210
+ "cancelled",
211
+ "unknown_interaction",
212
+ "unknown_run",
213
+ "binding_mismatch",
214
+ "invalid_response",
215
+ "transport_failure",
216
+ ]);
217
+ /** Durable result of one interaction response operation. */
218
+ export const InteractionAcknowledgementSchema = z
219
+ .strictObject({
220
+ operationId: boundedIdentifierSchema,
221
+ binding: InteractionBindingSchema,
222
+ commandDigest: sha256DigestSchema,
223
+ status: InteractionAcknowledgementStatusSchema,
224
+ message: boundedStringSchema.min(1).optional(),
225
+ retryable: z.boolean().optional(),
226
+ })
227
+ .superRefine((acknowledgement, context) => {
228
+ if (["invalid_response", "transport_failure"].includes(acknowledgement.status) &&
229
+ acknowledgement.message === undefined) {
230
+ context.addIssue({
231
+ code: "custom",
232
+ path: ["message"],
233
+ message: `${acknowledgement.status} must include a message`,
234
+ });
235
+ }
236
+ if (acknowledgement.status === "transport_failure" &&
237
+ acknowledgement.retryable === undefined) {
238
+ context.addIssue({
239
+ code: "custom",
240
+ path: ["retryable"],
241
+ message: "transport_failure must state whether retry is safe",
242
+ });
243
+ }
244
+ });
245
+ void InteractionOutcomeSchema;
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ export declare const InteractionFieldNameSchema: z.ZodString;
3
+ export declare const InteractionFieldSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
+ type: z.ZodLiteral<"text">;
5
+ multiline: z.ZodOptional<z.ZodBoolean>;
6
+ placeholder: z.ZodOptional<z.ZodString>;
7
+ maxLength: z.ZodOptional<z.ZodNumber>;
8
+ default: z.ZodOptional<z.ZodString>;
9
+ name: z.ZodString;
10
+ label: z.ZodString;
11
+ required: z.ZodOptional<z.ZodBoolean>;
12
+ }, z.core.$strict>, z.ZodObject<{
13
+ type: z.ZodLiteral<"number">;
14
+ min: z.ZodOptional<z.ZodNumber>;
15
+ max: z.ZodOptional<z.ZodNumber>;
16
+ default: z.ZodOptional<z.ZodNumber>;
17
+ name: z.ZodString;
18
+ label: z.ZodString;
19
+ required: z.ZodOptional<z.ZodBoolean>;
20
+ }, z.core.$strict>, z.ZodObject<{
21
+ type: z.ZodLiteral<"boolean">;
22
+ default: z.ZodOptional<z.ZodBoolean>;
23
+ name: z.ZodString;
24
+ label: z.ZodString;
25
+ required: z.ZodOptional<z.ZodBoolean>;
26
+ }, z.core.$strict>, z.ZodObject<{
27
+ type: z.ZodLiteral<"select">;
28
+ options: z.ZodArray<z.ZodObject<{
29
+ value: z.ZodString;
30
+ label: z.ZodString;
31
+ description: z.ZodOptional<z.ZodString>;
32
+ }, z.core.$strict>>;
33
+ multi: z.ZodOptional<z.ZodBoolean>;
34
+ allowCustom: z.ZodOptional<z.ZodBoolean>;
35
+ default: z.ZodOptional<z.ZodArray<z.ZodString>>;
36
+ name: z.ZodString;
37
+ label: z.ZodString;
38
+ required: z.ZodOptional<z.ZodBoolean>;
39
+ }, z.core.$strict>, z.ZodObject<{
40
+ type: z.ZodLiteral<"secret">;
41
+ placeholder: z.ZodOptional<z.ZodString>;
42
+ maxLength: z.ZodOptional<z.ZodNumber>;
43
+ name: z.ZodString;
44
+ label: z.ZodString;
45
+ required: z.ZodOptional<z.ZodBoolean>;
46
+ }, z.core.$strict>], "type">;
47
+ export type InteractionField = z.infer<typeof InteractionFieldSchema>;
48
+ export declare const InteractionAnswerSpecSchema: z.ZodObject<{
49
+ fields: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
50
+ type: z.ZodLiteral<"text">;
51
+ multiline: z.ZodOptional<z.ZodBoolean>;
52
+ placeholder: z.ZodOptional<z.ZodString>;
53
+ maxLength: z.ZodOptional<z.ZodNumber>;
54
+ default: z.ZodOptional<z.ZodString>;
55
+ name: z.ZodString;
56
+ label: z.ZodString;
57
+ required: z.ZodOptional<z.ZodBoolean>;
58
+ }, z.core.$strict>, z.ZodObject<{
59
+ type: z.ZodLiteral<"number">;
60
+ min: z.ZodOptional<z.ZodNumber>;
61
+ max: z.ZodOptional<z.ZodNumber>;
62
+ default: z.ZodOptional<z.ZodNumber>;
63
+ name: z.ZodString;
64
+ label: z.ZodString;
65
+ required: z.ZodOptional<z.ZodBoolean>;
66
+ }, z.core.$strict>, z.ZodObject<{
67
+ type: z.ZodLiteral<"boolean">;
68
+ default: z.ZodOptional<z.ZodBoolean>;
69
+ name: z.ZodString;
70
+ label: z.ZodString;
71
+ required: z.ZodOptional<z.ZodBoolean>;
72
+ }, z.core.$strict>, z.ZodObject<{
73
+ type: z.ZodLiteral<"select">;
74
+ options: z.ZodArray<z.ZodObject<{
75
+ value: z.ZodString;
76
+ label: z.ZodString;
77
+ description: z.ZodOptional<z.ZodString>;
78
+ }, z.core.$strict>>;
79
+ multi: z.ZodOptional<z.ZodBoolean>;
80
+ allowCustom: z.ZodOptional<z.ZodBoolean>;
81
+ default: z.ZodOptional<z.ZodArray<z.ZodString>>;
82
+ name: z.ZodString;
83
+ label: z.ZodString;
84
+ required: z.ZodOptional<z.ZodBoolean>;
85
+ }, z.core.$strict>, z.ZodObject<{
86
+ type: z.ZodLiteral<"secret">;
87
+ placeholder: z.ZodOptional<z.ZodString>;
88
+ maxLength: z.ZodOptional<z.ZodNumber>;
89
+ name: z.ZodString;
90
+ label: z.ZodString;
91
+ required: z.ZodOptional<z.ZodBoolean>;
92
+ }, z.core.$strict>], "type">>;
93
+ }, z.core.$strict>;
94
+ export type InteractionAnswerSpec = z.infer<typeof InteractionAnswerSpecSchema>;
95
+ export declare const InteractionFieldTypeSchema: z.ZodEnum<{
96
+ number: "number";
97
+ boolean: "boolean";
98
+ text: "text";
99
+ select: "select";
100
+ secret: "secret";
101
+ }>;
102
+ export type InteractionFieldType = z.infer<typeof InteractionFieldTypeSchema>;
103
+ /** Scope at which an accepted answer may be reused by an explicit policy. */
104
+ export declare const InteractionResponseScopeSchema: z.ZodEnum<{
105
+ interaction: "interaction";
106
+ session: "session";
107
+ persistent: "persistent";
108
+ }>;
109
+ export type InteractionResponseScope = z.infer<typeof InteractionResponseScopeSchema>;
110
+ /** Negotiated interaction behavior. Absence means interactions are unsupported. */
111
+ export declare const InteractionCapabilitiesSchema: z.ZodObject<{
112
+ kinds: z.ZodArray<z.ZodString>;
113
+ answerFieldTypes: z.ZodArray<z.ZodEnum<{
114
+ number: "number";
115
+ boolean: "boolean";
116
+ text: "text";
117
+ select: "select";
118
+ secret: "secret";
119
+ }>>;
120
+ responseScopes: z.ZodArray<z.ZodEnum<{
121
+ interaction: "interaction";
122
+ session: "session";
123
+ persistent: "persistent";
124
+ }>>;
125
+ secretAnswers: z.ZodBoolean;
126
+ concurrentRequests: z.ZodBoolean;
127
+ replay: z.ZodBoolean;
128
+ responseIdempotency: z.ZodBoolean;
129
+ }, z.core.$strict>;
130
+ export type InteractionCapabilities = z.infer<typeof InteractionCapabilitiesSchema>;
@@ -0,0 +1,227 @@
1
+ import { z } from "zod";
2
+ import { boundedIdentifierSchema, boundedStringSchema, CONTRACT_MAX_ARRAY_LENGTH, CONTRACT_MAX_STRING_LENGTH, } from "./contract-limits.js";
3
+ const forbiddenFieldNames = new Set(["__proto__", "constructor", "prototype"]);
4
+ export const InteractionFieldNameSchema = boundedIdentifierSchema.refine((value) => !forbiddenFieldNames.has(value), "interaction field name is reserved");
5
+ // =============================================================================
6
+ // Answer specification — describes the shape of a valid answer.
7
+ // =============================================================================
8
+ const FieldBase = {
9
+ /** Stable key the answer is returned under in `InteractionResponse.data`. */
10
+ name: InteractionFieldNameSchema,
11
+ /** Human-readable label for the form control. */
12
+ label: boundedStringSchema.min(1),
13
+ /** Whether the answer must supply this field to be `accepted`. */
14
+ required: z.boolean().optional(),
15
+ };
16
+ export const InteractionFieldSchema = z
17
+ .discriminatedUnion("type", [
18
+ z.strictObject({
19
+ ...FieldBase,
20
+ type: z.literal("text"),
21
+ multiline: z.boolean().optional(),
22
+ placeholder: boundedStringSchema.optional(),
23
+ /** Maximum answer length chosen by the request author. */
24
+ maxLength: z.number().int().positive().max(CONTRACT_MAX_STRING_LENGTH).optional(),
25
+ default: boundedStringSchema.optional(),
26
+ }),
27
+ z.strictObject({
28
+ ...FieldBase,
29
+ type: z.literal("number"),
30
+ min: z.number().finite().optional(),
31
+ max: z.number().finite().optional(),
32
+ default: z.number().finite().optional(),
33
+ }),
34
+ z.strictObject({
35
+ ...FieldBase,
36
+ type: z.literal("boolean"),
37
+ default: z.boolean().optional(),
38
+ }),
39
+ z.strictObject({
40
+ ...FieldBase,
41
+ type: z.literal("select"),
42
+ options: z
43
+ .array(z.strictObject({
44
+ value: boundedIdentifierSchema,
45
+ label: boundedStringSchema.min(1),
46
+ description: boundedStringSchema.optional(),
47
+ }))
48
+ .min(1)
49
+ .max(CONTRACT_MAX_ARRAY_LENGTH),
50
+ /** When true the user may pick more than one option. */
51
+ multi: z.boolean().optional(),
52
+ /**
53
+ * When true the answer may contain write-in values outside `options`
54
+ * (e.g. a rendered "Other…" choice carrying the user's own text).
55
+ * Write-ins must still be non-empty strings.
56
+ */
57
+ allowCustom: z.boolean().optional(),
58
+ default: z.array(boundedIdentifierSchema).max(CONTRACT_MAX_ARRAY_LENGTH).optional(),
59
+ }),
60
+ /** Like `text` but the value is sensitive (token/key) and must be masked. */
61
+ z.strictObject({
62
+ ...FieldBase,
63
+ type: z.literal("secret"),
64
+ placeholder: boundedStringSchema.optional(),
65
+ /** Maximum answer length chosen by the request author. */
66
+ maxLength: z.number().int().positive().max(CONTRACT_MAX_STRING_LENGTH).optional(),
67
+ }),
68
+ ])
69
+ .superRefine((field, context) => {
70
+ if (field.type === "text" &&
71
+ field.default !== undefined &&
72
+ field.maxLength !== undefined &&
73
+ field.default.length > field.maxLength) {
74
+ context.addIssue({
75
+ code: "custom",
76
+ path: ["default"],
77
+ message: "text field default exceeds maxLength",
78
+ });
79
+ return;
80
+ }
81
+ if (field.type === "number") {
82
+ if (field.min !== undefined &&
83
+ field.max !== undefined &&
84
+ field.min > field.max) {
85
+ context.addIssue({
86
+ code: "custom",
87
+ path: ["max"],
88
+ message: "number field max must be greater than or equal to min",
89
+ });
90
+ }
91
+ if (field.default !== undefined &&
92
+ field.min !== undefined &&
93
+ field.default < field.min) {
94
+ context.addIssue({
95
+ code: "custom",
96
+ path: ["default"],
97
+ message: "number field default must be greater than or equal to min",
98
+ });
99
+ }
100
+ if (field.default !== undefined &&
101
+ field.max !== undefined &&
102
+ field.default > field.max) {
103
+ context.addIssue({
104
+ code: "custom",
105
+ path: ["default"],
106
+ message: "number field default must be less than or equal to max",
107
+ });
108
+ }
109
+ return;
110
+ }
111
+ if (field.type !== "select")
112
+ return;
113
+ const optionValues = field.options.map((option) => option.value);
114
+ if (new Set(optionValues).size !== optionValues.length) {
115
+ context.addIssue({
116
+ code: "custom",
117
+ path: ["options"],
118
+ message: "select option values must be unique",
119
+ });
120
+ }
121
+ if (field.default === undefined)
122
+ return;
123
+ if (new Set(field.default).size !== field.default.length) {
124
+ context.addIssue({
125
+ code: "custom",
126
+ path: ["default"],
127
+ message: "select default values must be unique",
128
+ });
129
+ }
130
+ if (!field.multi && field.default.length > 1) {
131
+ context.addIssue({
132
+ code: "custom",
133
+ path: ["default"],
134
+ message: "single-select default may contain at most one value",
135
+ });
136
+ }
137
+ if (field.required && field.default.length === 0) {
138
+ context.addIssue({
139
+ code: "custom",
140
+ path: ["default"],
141
+ message: "required select default must contain a value",
142
+ });
143
+ }
144
+ const allowed = new Set(optionValues);
145
+ for (const value of field.default) {
146
+ if (allowed.has(value))
147
+ continue;
148
+ if (field.allowCustom === true && value.trim().length > 0)
149
+ continue;
150
+ context.addIssue({
151
+ code: "custom",
152
+ path: ["default"],
153
+ message: `select default contains unknown option "${value}"`,
154
+ });
155
+ }
156
+ });
157
+ export const InteractionAnswerSpecSchema = z
158
+ .strictObject({
159
+ fields: z.array(InteractionFieldSchema).max(CONTRACT_MAX_ARRAY_LENGTH),
160
+ })
161
+ .superRefine((spec, context) => {
162
+ const names = spec.fields.map((field) => field.name);
163
+ if (new Set(names).size !== names.length) {
164
+ context.addIssue({
165
+ code: "custom",
166
+ path: ["fields"],
167
+ message: "interaction field names must be unique",
168
+ });
169
+ }
170
+ });
171
+ export const InteractionFieldTypeSchema = z.enum([
172
+ "text",
173
+ "number",
174
+ "boolean",
175
+ "select",
176
+ "secret",
177
+ ]);
178
+ /** Scope at which an accepted answer may be reused by an explicit policy. */
179
+ export const InteractionResponseScopeSchema = z.enum([
180
+ "interaction",
181
+ "session",
182
+ "persistent",
183
+ ]);
184
+ /** Negotiated interaction behavior. Absence means interactions are unsupported. */
185
+ export const InteractionCapabilitiesSchema = z
186
+ .strictObject({
187
+ kinds: z.array(boundedIdentifierSchema).min(1).max(CONTRACT_MAX_ARRAY_LENGTH),
188
+ answerFieldTypes: z.array(InteractionFieldTypeSchema).min(1).max(CONTRACT_MAX_ARRAY_LENGTH),
189
+ responseScopes: z.array(InteractionResponseScopeSchema).min(1).max(CONTRACT_MAX_ARRAY_LENGTH),
190
+ secretAnswers: z.boolean(),
191
+ concurrentRequests: z.boolean(),
192
+ replay: z.boolean(),
193
+ responseIdempotency: z.boolean(),
194
+ })
195
+ .superRefine((capabilities, context) => {
196
+ const advertisesSecret = capabilities.answerFieldTypes.includes("secret");
197
+ if (advertisesSecret !== capabilities.secretAnswers) {
198
+ context.addIssue({
199
+ code: "custom",
200
+ path: ["secretAnswers"],
201
+ message: "secretAnswers must agree with the secret answer field capability",
202
+ });
203
+ }
204
+ if (new Set(capabilities.kinds).size !== capabilities.kinds.length) {
205
+ context.addIssue({
206
+ code: "custom",
207
+ path: ["kinds"],
208
+ message: "interaction kinds must be unique",
209
+ });
210
+ }
211
+ if (new Set(capabilities.answerFieldTypes).size !==
212
+ capabilities.answerFieldTypes.length) {
213
+ context.addIssue({
214
+ code: "custom",
215
+ path: ["answerFieldTypes"],
216
+ message: "interaction answer field types must be unique",
217
+ });
218
+ }
219
+ if (new Set(capabilities.responseScopes).size !==
220
+ capabilities.responseScopes.length) {
221
+ context.addIssue({
222
+ code: "custom",
223
+ path: ["responseScopes"],
224
+ message: "interaction response scopes must be unique",
225
+ });
226
+ }
227
+ });
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ import type { InteractionAnswerSpec, InteractionResponseScope } from "./interaction-fields.js";
3
+ export declare const InteractionKind: {
4
+ /** Agent asks the user to answer/choose. Answer = the chosen field values. */
5
+ readonly Question: "question";
6
+ /** Agent requests approval to run a tool/command. Answer = a `PermissionGrant`. */
7
+ readonly Permission: "permission";
8
+ /** Agent shares a plan/todo list for review/approval. */
9
+ readonly Plan: "plan";
10
+ };
11
+ export type WellKnownInteractionKind = (typeof InteractionKind)[keyof typeof InteractionKind];
12
+ /** Field name carrying the grant on a `permission` interaction's response. */
13
+ export declare const PERMISSION_GRANT_FIELD = "grant";
14
+ /** Optional free-text field carrying the user's reason on a `permission` response. */
15
+ export declare const PERMISSION_FEEDBACK_FIELD = "feedback";
16
+ /** Graduated permission decision — the value of the `grant` field. */
17
+ export declare const PermissionGrantSchema: z.ZodEnum<{
18
+ deny: "deny";
19
+ allow_once: "allow_once";
20
+ allow_session: "allow_session";
21
+ allow_always: "allow_always";
22
+ }>;
23
+ export type PermissionGrant = z.infer<typeof PermissionGrantSchema>;
24
+ /** Build a permission answer spec that cannot offer a broader reusable grant. */
25
+ export declare function permissionAnswerSpec(opts?: {
26
+ allowFeedback?: boolean;
27
+ responseScopes?: readonly InteractionResponseScope[];
28
+ }): InteractionAnswerSpec;
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ // =============================================================================
3
+ // Well-known kinds + helpers.
4
+ // =============================================================================
5
+ export const InteractionKind = {
6
+ /** Agent asks the user to answer/choose. Answer = the chosen field values. */
7
+ Question: "question",
8
+ /** Agent requests approval to run a tool/command. Answer = a `PermissionGrant`. */
9
+ Permission: "permission",
10
+ /** Agent shares a plan/todo list for review/approval. */
11
+ Plan: "plan",
12
+ };
13
+ /** Field name carrying the grant on a `permission` interaction's response. */
14
+ export const PERMISSION_GRANT_FIELD = "grant";
15
+ /** Optional free-text field carrying the user's reason on a `permission` response. */
16
+ export const PERMISSION_FEEDBACK_FIELD = "feedback";
17
+ /** Graduated permission decision — the value of the `grant` field. */
18
+ export const PermissionGrantSchema = z.enum([
19
+ "allow_once",
20
+ "allow_session",
21
+ "allow_always",
22
+ "deny",
23
+ ]);
24
+ /** Build a permission answer spec that cannot offer a broader reusable grant. */
25
+ export function permissionAnswerSpec(opts) {
26
+ const scopes = new Set(opts?.responseScopes ?? ["interaction"]);
27
+ const options = [
28
+ ...(scopes.has("interaction")
29
+ ? [{ value: "allow_once", label: "Allow once" }]
30
+ : []),
31
+ ...(scopes.has("session")
32
+ ? [{ value: "allow_session", label: "Allow for this session" }]
33
+ : []),
34
+ ...(scopes.has("persistent")
35
+ ? [{ value: "allow_always", label: "Always allow" }]
36
+ : []),
37
+ { value: "deny", label: "Deny" },
38
+ ];
39
+ const fields = [
40
+ {
41
+ type: "select",
42
+ name: PERMISSION_GRANT_FIELD,
43
+ label: "Decision",
44
+ required: true,
45
+ options,
46
+ },
47
+ ];
48
+ if (opts?.allowFeedback !== false) {
49
+ fields.push({
50
+ type: "text",
51
+ name: PERMISSION_FEEDBACK_FIELD,
52
+ label: "Feedback (optional)",
53
+ multiline: true,
54
+ });
55
+ }
56
+ return { fields };
57
+ }