@tangle-network/agent-interface 0.44.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.
- package/README.md +1 -1
- package/dist/agent-candidate-code-schema.d.ts +1 -0
- package/dist/agent-candidate-execution-plan-schema.d.ts +8 -6
- package/dist/agent-candidate-profile-schema.d.ts +3 -2
- package/dist/agent-candidate-promotion-schema.d.ts +59 -42
- package/dist/agent-candidate-receipt-schema.d.ts +5 -4
- package/dist/agent-candidate-schema-common.js +1 -1
- package/dist/agent-candidate-schema.d.ts +4 -2
- package/dist/agent-execution-preparation.d.ts +27 -26
- package/dist/agent-profile.d.ts +10 -3
- package/dist/agent-profile.js +45 -22
- package/dist/backend-message.d.ts +14 -0
- package/dist/backend-message.js +1 -0
- package/dist/contract-limits.d.ts +26 -0
- package/dist/contract-limits.js +175 -0
- package/dist/environment-exact-process.d.ts +161 -0
- package/dist/environment-exact-process.js +1 -0
- package/dist/environment-profile-capabilities.d.ts +26 -0
- package/dist/environment-profile-capabilities.js +34 -0
- package/dist/environment-provider.d.ts +3 -730
- package/dist/environment-provider.js +3 -245
- package/dist/environment-requests.d.ts +70 -0
- package/dist/environment-requests.js +1 -0
- package/dist/environment-runtime.d.ts +834 -0
- package/dist/environment-runtime.js +228 -0
- package/dist/execution-types.d.ts +47 -0
- package/dist/execution-types.js +1 -0
- package/dist/harness-capabilities.js +5 -0
- package/dist/harness.d.ts +6 -1
- package/dist/harness.js +1 -0
- package/dist/host-services.d.ts +91 -0
- package/dist/host-services.js +1 -0
- package/dist/index.d.ts +10 -617
- package/dist/index.js +10 -125
- package/dist/interaction-answer-validation.d.ts +13 -0
- package/dist/interaction-answer-validation.js +149 -0
- package/dist/interaction-data.d.ts +22 -0
- package/dist/interaction-data.js +46 -0
- package/dist/interaction-envelope.d.ts +258 -0
- package/dist/interaction-envelope.js +245 -0
- package/dist/interaction-fields.d.ts +130 -0
- package/dist/interaction-fields.js +227 -0
- package/dist/interaction-permissions.d.ts +28 -0
- package/dist/interaction-permissions.js +57 -0
- package/dist/interaction-resolution-validation.d.ts +9 -0
- package/dist/interaction-resolution-validation.js +50 -0
- package/dist/interaction-response-validation.d.ts +17 -0
- package/dist/interaction-response-validation.js +102 -0
- package/dist/interaction.d.ts +6 -397
- package/dist/interaction.js +6 -603
- package/dist/mcp.d.ts +38 -0
- package/dist/mcp.js +1 -0
- package/dist/parts.d.ts +104 -0
- package/dist/parts.js +55 -0
- package/dist/portable-context-base.d.ts +82 -0
- package/dist/portable-context-base.js +63 -0
- package/dist/portable-context-continuation.d.ts +168 -0
- package/dist/portable-context-continuation.js +194 -0
- package/dist/portable-context-plan-request.d.ts +86 -0
- package/dist/portable-context-plan-request.js +102 -0
- package/dist/portable-context-plan.d.ts +229 -0
- package/dist/portable-context-plan.js +241 -0
- package/dist/portable-context-shared.d.ts +36 -0
- package/dist/portable-context-shared.js +46 -0
- package/dist/portable-context-transfer.d.ts +319 -0
- package/dist/portable-context-transfer.js +206 -0
- package/dist/portable-context.d.ts +5 -753
- package/dist/portable-context.js +5 -754
- package/dist/profile-diff.js +56 -47
- package/dist/profile-schema.d.ts +1 -0
- package/dist/provider-adapter.d.ts +45 -0
- package/dist/provider-adapter.js +1 -0
- package/dist/provider-config.d.ts +58 -0
- package/dist/provider-config.js +42 -0
- package/dist/runtime-control.d.ts +87 -10
- package/dist/runtime-control.js +159 -38
- package/dist/stream-events.d.ts +55 -0
- package/dist/stream-events.js +1 -0
- package/dist/workspace-branching-shared.d.ts +7 -0
- package/dist/workspace-branching-shared.js +20 -0
- package/dist/workspace-branching.d.ts +4 -254
- package/dist/workspace-branching.js +4 -400
- package/dist/workspace-checkpoint.d.ts +99 -0
- package/dist/workspace-checkpoint.js +169 -0
- package/dist/workspace-cleanup.d.ts +85 -0
- package/dist/workspace-cleanup.js +156 -0
- package/dist/workspace-confidentiality.d.ts +59 -0
- package/dist/workspace-confidentiality.js +123 -0
- package/dist/workspace-fork.d.ts +182 -0
- package/dist/workspace-fork.js +267 -0
- package/package.json +1 -1
package/dist/interaction.js
CHANGED
|
@@ -1,603 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* same `id`. This subsumes the original question/answer pair and extends it to
|
|
8
|
-
* permissions, plans, and provider-specific asks.
|
|
9
|
-
*
|
|
10
|
-
* Design contract:
|
|
11
|
-
* - The envelope is stable; `kind` is an OPEN label, so new ask types need no
|
|
12
|
-
* change to this contract. Well-known kinds (see `InteractionKind`) get
|
|
13
|
-
* richer rendering and platform handling; unknown kinds render generically
|
|
14
|
-
* from `answerSpec` and still work end-to-end.
|
|
15
|
-
* - `answerSpec` is a small closed set of flat field types, so any consumer can
|
|
16
|
-
* render a form and validate a response without a general schema engine. This
|
|
17
|
-
* mirrors MCP elicitation so MCP-originated asks map onto this 1:1.
|
|
18
|
-
* - `default` + `timeoutMs`/`onTimeout` make unattended resolution explicit and
|
|
19
|
-
* auditable, replacing blanket permission-bypass flags.
|
|
20
|
-
*/
|
|
21
|
-
import { z } from "zod";
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Answer specification — describes the shape of a valid answer.
|
|
24
|
-
// =============================================================================
|
|
25
|
-
const FieldBase = {
|
|
26
|
-
/** Stable key the answer is returned under in `InteractionResponse.data`. */
|
|
27
|
-
name: z.string().min(1),
|
|
28
|
-
/** Human-readable label for the form control. */
|
|
29
|
-
label: z.string().min(1),
|
|
30
|
-
/** Whether the answer must supply this field to be `accepted`. */
|
|
31
|
-
required: z.boolean().optional(),
|
|
32
|
-
};
|
|
33
|
-
export const InteractionFieldSchema = z
|
|
34
|
-
.discriminatedUnion("type", [
|
|
35
|
-
z.strictObject({
|
|
36
|
-
...FieldBase,
|
|
37
|
-
type: z.literal("text"),
|
|
38
|
-
multiline: z.boolean().optional(),
|
|
39
|
-
placeholder: z.string().optional(),
|
|
40
|
-
/** Maximum answer length chosen by the request author. Omission means no contract limit. */
|
|
41
|
-
maxLength: z.number().int().positive().optional(),
|
|
42
|
-
default: z.string().optional(),
|
|
43
|
-
}),
|
|
44
|
-
z.strictObject({
|
|
45
|
-
...FieldBase,
|
|
46
|
-
type: z.literal("number"),
|
|
47
|
-
min: z.number().finite().optional(),
|
|
48
|
-
max: z.number().finite().optional(),
|
|
49
|
-
default: z.number().finite().optional(),
|
|
50
|
-
}),
|
|
51
|
-
z.strictObject({
|
|
52
|
-
...FieldBase,
|
|
53
|
-
type: z.literal("boolean"),
|
|
54
|
-
default: z.boolean().optional(),
|
|
55
|
-
}),
|
|
56
|
-
z.strictObject({
|
|
57
|
-
...FieldBase,
|
|
58
|
-
type: z.literal("select"),
|
|
59
|
-
options: z
|
|
60
|
-
.array(z.strictObject({
|
|
61
|
-
value: z.string().min(1),
|
|
62
|
-
label: z.string().min(1),
|
|
63
|
-
description: z.string().optional(),
|
|
64
|
-
}))
|
|
65
|
-
.min(1),
|
|
66
|
-
/** When true the user may pick more than one option. */
|
|
67
|
-
multi: z.boolean().optional(),
|
|
68
|
-
/**
|
|
69
|
-
* When true the answer may contain write-in values outside `options`
|
|
70
|
-
* (e.g. a rendered "Other…" choice carrying the user's own text).
|
|
71
|
-
* Write-ins must still be non-empty strings.
|
|
72
|
-
*/
|
|
73
|
-
allowCustom: z.boolean().optional(),
|
|
74
|
-
default: z.array(z.string().min(1)).optional(),
|
|
75
|
-
}),
|
|
76
|
-
/** Like `text` but the value is sensitive (token/key) and must be masked. */
|
|
77
|
-
z.strictObject({
|
|
78
|
-
...FieldBase,
|
|
79
|
-
type: z.literal("secret"),
|
|
80
|
-
placeholder: z.string().optional(),
|
|
81
|
-
/** Maximum answer length chosen by the request author. Omission means no contract limit. */
|
|
82
|
-
maxLength: z.number().int().positive().optional(),
|
|
83
|
-
}),
|
|
84
|
-
])
|
|
85
|
-
.superRefine((field, context) => {
|
|
86
|
-
if (field.type === "text" &&
|
|
87
|
-
field.default !== undefined &&
|
|
88
|
-
field.maxLength !== undefined &&
|
|
89
|
-
field.default.length > field.maxLength) {
|
|
90
|
-
context.addIssue({
|
|
91
|
-
code: "custom",
|
|
92
|
-
path: ["default"],
|
|
93
|
-
message: "text field default exceeds maxLength",
|
|
94
|
-
});
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (field.type === "number") {
|
|
98
|
-
if (field.min !== undefined &&
|
|
99
|
-
field.max !== undefined &&
|
|
100
|
-
field.min > field.max) {
|
|
101
|
-
context.addIssue({
|
|
102
|
-
code: "custom",
|
|
103
|
-
path: ["max"],
|
|
104
|
-
message: "number field max must be greater than or equal to min",
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
if (field.default !== undefined &&
|
|
108
|
-
field.min !== undefined &&
|
|
109
|
-
field.default < field.min) {
|
|
110
|
-
context.addIssue({
|
|
111
|
-
code: "custom",
|
|
112
|
-
path: ["default"],
|
|
113
|
-
message: "number field default must be greater than or equal to min",
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
if (field.default !== undefined &&
|
|
117
|
-
field.max !== undefined &&
|
|
118
|
-
field.default > field.max) {
|
|
119
|
-
context.addIssue({
|
|
120
|
-
code: "custom",
|
|
121
|
-
path: ["default"],
|
|
122
|
-
message: "number field default must be less than or equal to max",
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (field.type !== "select")
|
|
128
|
-
return;
|
|
129
|
-
const optionValues = field.options.map((option) => option.value);
|
|
130
|
-
if (new Set(optionValues).size !== optionValues.length) {
|
|
131
|
-
context.addIssue({
|
|
132
|
-
code: "custom",
|
|
133
|
-
path: ["options"],
|
|
134
|
-
message: "select option values must be unique",
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
if (field.default === undefined)
|
|
138
|
-
return;
|
|
139
|
-
if (new Set(field.default).size !== field.default.length) {
|
|
140
|
-
context.addIssue({
|
|
141
|
-
code: "custom",
|
|
142
|
-
path: ["default"],
|
|
143
|
-
message: "select default values must be unique",
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
if (!field.multi && field.default.length > 1) {
|
|
147
|
-
context.addIssue({
|
|
148
|
-
code: "custom",
|
|
149
|
-
path: ["default"],
|
|
150
|
-
message: "single-select default may contain at most one value",
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
if (field.required && field.default.length === 0) {
|
|
154
|
-
context.addIssue({
|
|
155
|
-
code: "custom",
|
|
156
|
-
path: ["default"],
|
|
157
|
-
message: "required select default must contain a value",
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
const allowed = new Set(optionValues);
|
|
161
|
-
for (const value of field.default) {
|
|
162
|
-
if (allowed.has(value))
|
|
163
|
-
continue;
|
|
164
|
-
if (field.allowCustom === true && value.trim().length > 0)
|
|
165
|
-
continue;
|
|
166
|
-
context.addIssue({
|
|
167
|
-
code: "custom",
|
|
168
|
-
path: ["default"],
|
|
169
|
-
message: `select default contains unknown option "${value}"`,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
export const InteractionAnswerSpecSchema = z
|
|
174
|
-
.strictObject({
|
|
175
|
-
fields: z.array(InteractionFieldSchema),
|
|
176
|
-
})
|
|
177
|
-
.superRefine((spec, context) => {
|
|
178
|
-
const names = spec.fields.map((field) => field.name);
|
|
179
|
-
if (new Set(names).size !== names.length) {
|
|
180
|
-
context.addIssue({
|
|
181
|
-
code: "custom",
|
|
182
|
-
path: ["fields"],
|
|
183
|
-
message: "interaction field names must be unique",
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
export const InteractionFieldTypeSchema = z.enum([
|
|
188
|
-
"text",
|
|
189
|
-
"number",
|
|
190
|
-
"boolean",
|
|
191
|
-
"select",
|
|
192
|
-
"secret",
|
|
193
|
-
]);
|
|
194
|
-
/** Scope at which an accepted answer may be reused by an explicit policy. */
|
|
195
|
-
export const InteractionResponseScopeSchema = z.enum([
|
|
196
|
-
"interaction",
|
|
197
|
-
"session",
|
|
198
|
-
"persistent",
|
|
199
|
-
]);
|
|
200
|
-
/** Negotiated interaction behavior. Absence means interactions are unsupported. */
|
|
201
|
-
export const InteractionCapabilitiesSchema = z
|
|
202
|
-
.strictObject({
|
|
203
|
-
kinds: z.array(z.string().min(1)).min(1),
|
|
204
|
-
answerFieldTypes: z.array(InteractionFieldTypeSchema).min(1),
|
|
205
|
-
responseScopes: z.array(InteractionResponseScopeSchema).min(1),
|
|
206
|
-
secretAnswers: z.boolean(),
|
|
207
|
-
concurrentRequests: z.boolean(),
|
|
208
|
-
replay: z.boolean(),
|
|
209
|
-
responseIdempotency: z.boolean(),
|
|
210
|
-
})
|
|
211
|
-
.superRefine((capabilities, context) => {
|
|
212
|
-
const advertisesSecret = capabilities.answerFieldTypes.includes("secret");
|
|
213
|
-
if (advertisesSecret !== capabilities.secretAnswers) {
|
|
214
|
-
context.addIssue({
|
|
215
|
-
code: "custom",
|
|
216
|
-
path: ["secretAnswers"],
|
|
217
|
-
message: "secretAnswers must agree with the secret answer field capability",
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
if (new Set(capabilities.kinds).size !== capabilities.kinds.length) {
|
|
221
|
-
context.addIssue({
|
|
222
|
-
code: "custom",
|
|
223
|
-
path: ["kinds"],
|
|
224
|
-
message: "interaction kinds must be unique",
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
if (new Set(capabilities.answerFieldTypes).size !==
|
|
228
|
-
capabilities.answerFieldTypes.length) {
|
|
229
|
-
context.addIssue({
|
|
230
|
-
code: "custom",
|
|
231
|
-
path: ["answerFieldTypes"],
|
|
232
|
-
message: "interaction answer field types must be unique",
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
if (new Set(capabilities.responseScopes).size !==
|
|
236
|
-
capabilities.responseScopes.length) {
|
|
237
|
-
context.addIssue({
|
|
238
|
-
code: "custom",
|
|
239
|
-
path: ["responseScopes"],
|
|
240
|
-
message: "interaction response scopes must be unique",
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
// =============================================================================
|
|
245
|
-
// Subject — what the request is about (drives preview/permission UX).
|
|
246
|
-
// =============================================================================
|
|
247
|
-
export const InteractionSubjectSchema = z.discriminatedUnion("type", [
|
|
248
|
-
z.strictObject({
|
|
249
|
-
type: z.literal("tool"),
|
|
250
|
-
toolName: z.string(),
|
|
251
|
-
input: z.unknown().optional(),
|
|
252
|
-
}),
|
|
253
|
-
z.strictObject({ type: z.literal("command"), command: z.string() }),
|
|
254
|
-
z.strictObject({
|
|
255
|
-
type: z.literal("file"),
|
|
256
|
-
path: z.string(),
|
|
257
|
-
preview: z.string().optional(),
|
|
258
|
-
}),
|
|
259
|
-
z.strictObject({ type: z.literal("resource"), uri: z.string() }),
|
|
260
|
-
]);
|
|
261
|
-
// =============================================================================
|
|
262
|
-
// Outcome + resolution — the answer.
|
|
263
|
-
// =============================================================================
|
|
264
|
-
export const InteractionOutcomeSchema = z.enum(["accepted", "declined", "cancelled"]);
|
|
265
|
-
/** Field values keyed by `InteractionField.name`. Validated against `answerSpec`. */
|
|
266
|
-
export const InteractionDataSchema = z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]));
|
|
267
|
-
export const InteractionResolutionSchema = z.discriminatedUnion("outcome", [
|
|
268
|
-
z.strictObject({
|
|
269
|
-
outcome: z.literal("accepted"),
|
|
270
|
-
data: InteractionDataSchema.optional(),
|
|
271
|
-
}),
|
|
272
|
-
z.strictObject({
|
|
273
|
-
outcome: z.literal("declined"),
|
|
274
|
-
/** Accepted for wire compatibility; ignored by response validation. */
|
|
275
|
-
data: InteractionDataSchema.optional(),
|
|
276
|
-
}),
|
|
277
|
-
z.strictObject({
|
|
278
|
-
outcome: z.literal("cancelled"),
|
|
279
|
-
/** Accepted for wire compatibility; ignored by response validation. */
|
|
280
|
-
data: InteractionDataSchema.optional(),
|
|
281
|
-
}),
|
|
282
|
-
]);
|
|
283
|
-
// =============================================================================
|
|
284
|
-
// The request envelope.
|
|
285
|
-
// =============================================================================
|
|
286
|
-
export const InteractionRequestSchema = z
|
|
287
|
-
.strictObject({
|
|
288
|
-
/** Correlation id; unique within a session. The response carries the same id. */
|
|
289
|
-
id: z.string().min(1),
|
|
290
|
-
/**
|
|
291
|
-
* Open label for rendering, handling, and authorization. Well-known values:
|
|
292
|
-
* `question` | `permission` | `plan`. Vendor extensions SHOULD namespace,
|
|
293
|
-
* e.g. `x-pi.choose-extension`.
|
|
294
|
-
*/
|
|
295
|
-
kind: z.string().min(1),
|
|
296
|
-
/** Short human-readable prompt. */
|
|
297
|
-
title: z.string().min(1),
|
|
298
|
-
/** Optional longer context (markdown). */
|
|
299
|
-
body: z.string().optional(),
|
|
300
|
-
subject: InteractionSubjectSchema.optional(),
|
|
301
|
-
answerSpec: InteractionAnswerSpecSchema,
|
|
302
|
-
/** Omission is fail-closed and permits only this interaction. */
|
|
303
|
-
responseScopes: z.array(InteractionResponseScopeSchema).min(1).optional(),
|
|
304
|
-
/** Resolution applied when unattended or timed out — explicit, not a bypass flag. */
|
|
305
|
-
default: InteractionResolutionSchema.optional(),
|
|
306
|
-
/** Wait this long for a human before applying `onTimeout`. */
|
|
307
|
-
timeoutMs: z.number().int().positive().optional(),
|
|
308
|
-
/** On timeout: apply `default`, `fail` the turn, or keep `wait`ing. Default `wait`. */
|
|
309
|
-
onTimeout: z.enum(["default", "fail", "wait"]).optional(),
|
|
310
|
-
})
|
|
311
|
-
.superRefine((request, context) => {
|
|
312
|
-
if (request.responseScopes &&
|
|
313
|
-
new Set(request.responseScopes).size !== request.responseScopes.length) {
|
|
314
|
-
context.addIssue({
|
|
315
|
-
code: "custom",
|
|
316
|
-
path: ["responseScopes"],
|
|
317
|
-
message: "interaction response scopes must be unique",
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
if (request.onTimeout === "default" && request.default === undefined) {
|
|
321
|
-
context.addIssue({
|
|
322
|
-
code: "custom",
|
|
323
|
-
path: ["default"],
|
|
324
|
-
message: "onTimeout=default requires a default resolution",
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
if (request.default) {
|
|
328
|
-
for (const error of validateResolutionForRequest(request, request.default)) {
|
|
329
|
-
context.addIssue({ code: "custom", path: ["default"], message: error });
|
|
330
|
-
}
|
|
331
|
-
if (request.default.outcome === "accepted") {
|
|
332
|
-
const secretFields = new Set(request.answerSpec.fields
|
|
333
|
-
.filter((field) => field.type === "secret")
|
|
334
|
-
.map((field) => field.name));
|
|
335
|
-
for (const fieldName of Object.keys(request.default.data ?? {})) {
|
|
336
|
-
if (!secretFields.has(fieldName))
|
|
337
|
-
continue;
|
|
338
|
-
context.addIssue({
|
|
339
|
-
code: "custom",
|
|
340
|
-
path: ["default", "data", fieldName],
|
|
341
|
-
message: "secret answers cannot be embedded in interaction defaults",
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
export const InteractionResponseSchema = z.discriminatedUnion("outcome", [
|
|
348
|
-
z.strictObject({
|
|
349
|
-
id: z.string().min(1),
|
|
350
|
-
outcome: z.literal("accepted"),
|
|
351
|
-
data: InteractionDataSchema.optional(),
|
|
352
|
-
}),
|
|
353
|
-
z.strictObject({
|
|
354
|
-
id: z.string().min(1),
|
|
355
|
-
outcome: z.literal("declined"),
|
|
356
|
-
/** Accepted for compatibility with the pre-discriminated wire shape. */
|
|
357
|
-
data: InteractionDataSchema.optional(),
|
|
358
|
-
}),
|
|
359
|
-
z.strictObject({
|
|
360
|
-
id: z.string().min(1),
|
|
361
|
-
outcome: z.literal("cancelled"),
|
|
362
|
-
/** Accepted for compatibility with the pre-discriminated wire shape. */
|
|
363
|
-
data: InteractionDataSchema.optional(),
|
|
364
|
-
}),
|
|
365
|
-
]);
|
|
366
|
-
/** A request is unique only within this run and optional provider session. */
|
|
367
|
-
export const InteractionBindingSchema = z.strictObject({
|
|
368
|
-
runId: z.string().min(1),
|
|
369
|
-
environmentId: z.string().min(1),
|
|
370
|
-
sessionId: z.string().min(1).optional(),
|
|
371
|
-
interactionId: z.string().min(1),
|
|
372
|
-
});
|
|
373
|
-
/** Retryable command sent to an environment or retained session. */
|
|
374
|
-
export const InteractionResponseCommandSchema = z
|
|
375
|
-
.strictObject({
|
|
376
|
-
operationId: z.string().min(1),
|
|
377
|
-
binding: InteractionBindingSchema,
|
|
378
|
-
response: InteractionResponseSchema,
|
|
379
|
-
})
|
|
380
|
-
.superRefine((command, context) => {
|
|
381
|
-
if (command.binding.interactionId !== command.response.id) {
|
|
382
|
-
context.addIssue({
|
|
383
|
-
code: "custom",
|
|
384
|
-
path: ["response", "id"],
|
|
385
|
-
message: "response id must match the bound interaction id",
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
export const InteractionAcknowledgementStatusSchema = z.enum([
|
|
390
|
-
"accepted",
|
|
391
|
-
"already_resolved_same",
|
|
392
|
-
"already_resolved_different",
|
|
393
|
-
"expired",
|
|
394
|
-
"cancelled",
|
|
395
|
-
"unknown_interaction",
|
|
396
|
-
"unknown_run",
|
|
397
|
-
"binding_mismatch",
|
|
398
|
-
"invalid_response",
|
|
399
|
-
"transport_failure",
|
|
400
|
-
]);
|
|
401
|
-
/** Durable result of one interaction response operation. */
|
|
402
|
-
export const InteractionAcknowledgementSchema = z
|
|
403
|
-
.strictObject({
|
|
404
|
-
operationId: z.string().min(1),
|
|
405
|
-
binding: InteractionBindingSchema,
|
|
406
|
-
status: InteractionAcknowledgementStatusSchema,
|
|
407
|
-
message: z.string().min(1).optional(),
|
|
408
|
-
retryable: z.boolean().optional(),
|
|
409
|
-
})
|
|
410
|
-
.superRefine((acknowledgement, context) => {
|
|
411
|
-
if (["invalid_response", "transport_failure"].includes(acknowledgement.status) &&
|
|
412
|
-
acknowledgement.message === undefined) {
|
|
413
|
-
context.addIssue({
|
|
414
|
-
code: "custom",
|
|
415
|
-
path: ["message"],
|
|
416
|
-
message: `${acknowledgement.status} must include a message`,
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
if (acknowledgement.status === "transport_failure" &&
|
|
420
|
-
acknowledgement.retryable === undefined) {
|
|
421
|
-
context.addIssue({
|
|
422
|
-
code: "custom",
|
|
423
|
-
path: ["retryable"],
|
|
424
|
-
message: "transport_failure must state whether retry is safe",
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
// =============================================================================
|
|
429
|
-
// Well-known kinds + helpers.
|
|
430
|
-
// =============================================================================
|
|
431
|
-
export const InteractionKind = {
|
|
432
|
-
/** Agent asks the user to answer/choose. Answer = the chosen field values. */
|
|
433
|
-
Question: "question",
|
|
434
|
-
/** Agent requests approval to run a tool/command. Answer = a `PermissionGrant`. */
|
|
435
|
-
Permission: "permission",
|
|
436
|
-
/** Agent shares a plan/todo list for review/approval. */
|
|
437
|
-
Plan: "plan",
|
|
438
|
-
};
|
|
439
|
-
/** Field name carrying the grant on a `permission` interaction's response. */
|
|
440
|
-
export const PERMISSION_GRANT_FIELD = "grant";
|
|
441
|
-
/** Optional free-text field carrying the user's reason on a `permission` response. */
|
|
442
|
-
export const PERMISSION_FEEDBACK_FIELD = "feedback";
|
|
443
|
-
/** Graduated permission decision — the value of the `grant` field. */
|
|
444
|
-
export const PermissionGrantSchema = z.enum([
|
|
445
|
-
"allow_once",
|
|
446
|
-
"allow_session",
|
|
447
|
-
"allow_always",
|
|
448
|
-
"deny",
|
|
449
|
-
]);
|
|
450
|
-
/** Build a permission answer spec that cannot offer a broader reusable grant. */
|
|
451
|
-
export function permissionAnswerSpec(opts) {
|
|
452
|
-
const scopes = new Set(opts?.responseScopes ?? ["interaction"]);
|
|
453
|
-
const options = [
|
|
454
|
-
...(scopes.has("interaction")
|
|
455
|
-
? [{ value: "allow_once", label: "Allow once" }]
|
|
456
|
-
: []),
|
|
457
|
-
...(scopes.has("session")
|
|
458
|
-
? [{ value: "allow_session", label: "Allow for this session" }]
|
|
459
|
-
: []),
|
|
460
|
-
...(scopes.has("persistent")
|
|
461
|
-
? [{ value: "allow_always", label: "Always allow" }]
|
|
462
|
-
: []),
|
|
463
|
-
{ value: "deny", label: "Deny" },
|
|
464
|
-
];
|
|
465
|
-
const fields = [
|
|
466
|
-
{
|
|
467
|
-
type: "select",
|
|
468
|
-
name: PERMISSION_GRANT_FIELD,
|
|
469
|
-
label: "Decision",
|
|
470
|
-
required: true,
|
|
471
|
-
options,
|
|
472
|
-
},
|
|
473
|
-
];
|
|
474
|
-
if (opts?.allowFeedback !== false) {
|
|
475
|
-
fields.push({
|
|
476
|
-
type: "text",
|
|
477
|
-
name: PERMISSION_FEEDBACK_FIELD,
|
|
478
|
-
label: "Feedback (optional)",
|
|
479
|
-
multiline: true,
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
return { fields };
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Validate an accepted answer against its spec. Used by the broker before a
|
|
486
|
-
* response reaches the adapter, so malformed answers are rejected centrally.
|
|
487
|
-
*/
|
|
488
|
-
export function validateInteractionAnswer(spec, data) {
|
|
489
|
-
const errors = [];
|
|
490
|
-
const d = data ?? {};
|
|
491
|
-
const knownFields = new Set(spec.fields.map((field) => field.name));
|
|
492
|
-
for (const fieldName of Object.keys(d)) {
|
|
493
|
-
if (!knownFields.has(fieldName)) {
|
|
494
|
-
errors.push(`unknown field "${fieldName}"`);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
for (const field of spec.fields) {
|
|
498
|
-
const v = d[field.name];
|
|
499
|
-
const present = v !== undefined && v !== null && !(typeof v === "string" && v === "");
|
|
500
|
-
if (!present) {
|
|
501
|
-
if (field.required)
|
|
502
|
-
errors.push(`missing required field "${field.name}"`);
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
switch (field.type) {
|
|
506
|
-
case "text":
|
|
507
|
-
case "secret": {
|
|
508
|
-
if (typeof v !== "string") {
|
|
509
|
-
errors.push(`field "${field.name}" must be a string`);
|
|
510
|
-
}
|
|
511
|
-
else if (field.maxLength !== undefined && v.length > field.maxLength) {
|
|
512
|
-
errors.push(`field "${field.name}" exceeds maxLength ${field.maxLength}`);
|
|
513
|
-
}
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
case "number":
|
|
517
|
-
if (typeof v !== "number" || !Number.isFinite(v)) {
|
|
518
|
-
errors.push(`field "${field.name}" must be a finite number`);
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
if (field.min !== undefined && v < field.min)
|
|
522
|
-
errors.push(`field "${field.name}" below min ${field.min}`);
|
|
523
|
-
if (field.max !== undefined && v > field.max)
|
|
524
|
-
errors.push(`field "${field.name}" above max ${field.max}`);
|
|
525
|
-
}
|
|
526
|
-
break;
|
|
527
|
-
case "boolean":
|
|
528
|
-
if (typeof v !== "boolean")
|
|
529
|
-
errors.push(`field "${field.name}" must be a boolean`);
|
|
530
|
-
break;
|
|
531
|
-
case "select": {
|
|
532
|
-
if (!Array.isArray(v)) {
|
|
533
|
-
errors.push(`field "${field.name}" must be an array of option values`);
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
if (!field.multi && v.length > 1)
|
|
537
|
-
errors.push(`field "${field.name}" accepts a single value`);
|
|
538
|
-
if (field.required && v.length === 0)
|
|
539
|
-
errors.push(`field "${field.name}" requires a selection`);
|
|
540
|
-
const allowed = new Set(field.options.map((o) => o.value));
|
|
541
|
-
for (const choice of v) {
|
|
542
|
-
if (allowed.has(choice))
|
|
543
|
-
continue;
|
|
544
|
-
if (field.allowCustom === true) {
|
|
545
|
-
// Write-ins are open but stay fail-closed on shape: string, non-blank.
|
|
546
|
-
if (typeof choice !== "string" || choice.trim() === "") {
|
|
547
|
-
errors.push(`field "${field.name}" has blank write-in value`);
|
|
548
|
-
}
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
errors.push(`field "${field.name}" has invalid option "${choice}"`);
|
|
552
|
-
}
|
|
553
|
-
break;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
558
|
-
}
|
|
559
|
-
/** Validate one response against the exact outstanding request. */
|
|
560
|
-
export function validateInteractionResponse(request, response) {
|
|
561
|
-
const parsed = InteractionResponseSchema.safeParse(response);
|
|
562
|
-
if (!parsed.success) {
|
|
563
|
-
return {
|
|
564
|
-
ok: false,
|
|
565
|
-
errors: parsed.error.issues.map((issue) => issue.message),
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
const errors = request.id === parsed.data.id
|
|
569
|
-
? validateResolutionForRequest(request, parsed.data)
|
|
570
|
-
: ["response id does not match the outstanding interaction"];
|
|
571
|
-
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
572
|
-
}
|
|
573
|
-
function validateResolutionForRequest(request, resolution) {
|
|
574
|
-
if (resolution.outcome !== "accepted")
|
|
575
|
-
return [];
|
|
576
|
-
const validation = validateInteractionAnswer(request.answerSpec, resolution.data);
|
|
577
|
-
const errors = validation.ok ? [] : [...validation.errors];
|
|
578
|
-
if (request.kind !== InteractionKind.Permission)
|
|
579
|
-
return errors;
|
|
580
|
-
const grant = resolution.data?.[PERMISSION_GRANT_FIELD];
|
|
581
|
-
if (!Array.isArray(grant) || grant.length !== 1) {
|
|
582
|
-
errors.push('permission response must select exactly one "grant" value');
|
|
583
|
-
return errors;
|
|
584
|
-
}
|
|
585
|
-
const parsedGrant = PermissionGrantSchema.safeParse(grant[0]);
|
|
586
|
-
if (!parsedGrant.success) {
|
|
587
|
-
errors.push(`permission response has invalid grant "${String(grant[0])}"`);
|
|
588
|
-
return errors;
|
|
589
|
-
}
|
|
590
|
-
if (parsedGrant.data === "deny")
|
|
591
|
-
return errors;
|
|
592
|
-
const requiredScope = {
|
|
593
|
-
allow_once: "interaction",
|
|
594
|
-
allow_session: "session",
|
|
595
|
-
allow_always: "persistent",
|
|
596
|
-
deny: "interaction",
|
|
597
|
-
};
|
|
598
|
-
const permitted = new Set(request.responseScopes ?? ["interaction"]);
|
|
599
|
-
if (!permitted.has(requiredScope[parsedGrant.data])) {
|
|
600
|
-
errors.push(`permission grant "${parsedGrant.data}" exceeds the request's response scopes`);
|
|
601
|
-
}
|
|
602
|
-
return errors;
|
|
603
|
-
}
|
|
1
|
+
export * from "./interaction-fields.js";
|
|
2
|
+
export * from "./interaction-data.js";
|
|
3
|
+
export * from "./interaction-envelope.js";
|
|
4
|
+
export * from "./interaction-permissions.js";
|
|
5
|
+
export * from "./interaction-answer-validation.js";
|
|
6
|
+
export * from "./interaction-response-validation.js";
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type LocalMcpConfig = {
|
|
2
|
+
type: "local" | "stdio";
|
|
3
|
+
command: string | string[];
|
|
4
|
+
args?: string[];
|
|
5
|
+
env?: Record<string, string>;
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
};
|
|
9
|
+
export type RemoteMcpConfig = {
|
|
10
|
+
type: "remote" | "http";
|
|
11
|
+
url: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
};
|
|
16
|
+
export type McpConfig = LocalMcpConfig | RemoteMcpConfig;
|
|
17
|
+
export type McpServerStatus = {
|
|
18
|
+
name: string;
|
|
19
|
+
status: "connected" | "disconnected" | "error" | "unknown";
|
|
20
|
+
type?: "local" | "remote" | "stdio" | "http";
|
|
21
|
+
error?: string;
|
|
22
|
+
};
|
|
23
|
+
export type McpStatusResponse = {
|
|
24
|
+
servers: Record<string, McpServerStatus>;
|
|
25
|
+
};
|
|
26
|
+
export type BackendListOptions = {
|
|
27
|
+
limit?: number;
|
|
28
|
+
cursor?: string;
|
|
29
|
+
};
|
|
30
|
+
export type BackendListResult<TItem> = {
|
|
31
|
+
items: TItem[];
|
|
32
|
+
nextCursor?: string;
|
|
33
|
+
};
|
|
34
|
+
export type BackendArtifact = {
|
|
35
|
+
path: string;
|
|
36
|
+
sizeBytes?: number;
|
|
37
|
+
updatedAt?: string;
|
|
38
|
+
};
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|