@crowdedkingdoms/crowdyjs 11.1.0 → 12.1.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/MIGRATION.md +152 -0
- package/README.md +262 -0
- package/dist/crowdy-agent/browser-dispatcher.d.ts +41 -0
- package/dist/crowdy-agent/browser-dispatcher.d.ts.map +1 -0
- package/dist/crowdy-agent/browser-dispatcher.js +264 -0
- package/dist/crowdy-agent/controller.d.ts +144 -0
- package/dist/crowdy-agent/controller.d.ts.map +1 -0
- package/dist/crowdy-agent/controller.js +1239 -0
- package/dist/crowdy-agent/errors.d.ts +37 -0
- package/dist/crowdy-agent/errors.d.ts.map +1 -0
- package/dist/crowdy-agent/errors.js +107 -0
- package/dist/crowdy-agent/graphql-transport.d.ts +154 -0
- package/dist/crowdy-agent/graphql-transport.d.ts.map +1 -0
- package/dist/crowdy-agent/graphql-transport.js +854 -0
- package/dist/crowdy-agent/index.d.ts +16 -0
- package/dist/crowdy-agent/index.d.ts.map +1 -0
- package/dist/crowdy-agent/index.js +15 -0
- package/dist/crowdy-agent/registry.d.ts +29 -0
- package/dist/crowdy-agent/registry.d.ts.map +1 -0
- package/dist/crowdy-agent/registry.js +281 -0
- package/dist/crowdy-agent/schema.d.ts +72 -0
- package/dist/crowdy-agent/schema.d.ts.map +1 -0
- package/dist/crowdy-agent/schema.js +467 -0
- package/dist/crowdy-agent/studio-tools.d.ts +15 -0
- package/dist/crowdy-agent/studio-tools.d.ts.map +1 -0
- package/dist/crowdy-agent/studio-tools.js +280 -0
- package/dist/crowdy-agent/tool-descriptors.d.ts +4 -0
- package/dist/crowdy-agent/tool-descriptors.d.ts.map +1 -0
- package/dist/crowdy-agent/tool-descriptors.js +1049 -0
- package/dist/crowdy-agent/transport.d.ts +152 -0
- package/dist/crowdy-agent/transport.d.ts.map +1 -0
- package/dist/crowdy-agent/transport.js +27 -0
- package/dist/crowdy-agent/types.d.ts +328 -0
- package/dist/crowdy-agent/types.d.ts.map +1 -0
- package/dist/crowdy-agent/types.js +37 -0
- package/dist/crowdy-client.d.ts +3 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +6 -0
- package/dist/crowdy-studio/agent-dom-shell.d.ts +68 -0
- package/dist/crowdy-studio/agent-dom-shell.d.ts.map +1 -0
- package/dist/crowdy-studio/agent-dom-shell.js +418 -0
- package/dist/crowdy-studio/controller.d.ts +74 -4
- package/dist/crowdy-studio/controller.d.ts.map +1 -1
- package/dist/crowdy-studio/controller.js +450 -17
- package/dist/crowdy-studio/dom-shell.d.ts +50 -4
- package/dist/crowdy-studio/dom-shell.d.ts.map +1 -1
- package/dist/crowdy-studio/dom-shell.js +554 -123
- package/dist/crowdy-studio/embed/dock.d.ts +56 -0
- package/dist/crowdy-studio/embed/dock.d.ts.map +1 -0
- package/dist/crowdy-studio/embed/dock.js +231 -0
- package/dist/crowdy-studio/embed/embed-styles.d.ts +12 -0
- package/dist/crowdy-studio/embed/embed-styles.d.ts.map +1 -0
- package/dist/crowdy-studio/embed/embed-styles.js +549 -0
- package/dist/crowdy-studio/embed/hud-layer.d.ts +40 -0
- package/dist/crowdy-studio/embed/hud-layer.d.ts.map +1 -0
- package/dist/crowdy-studio/embed/hud-layer.js +100 -0
- package/dist/crowdy-studio/embed/index.d.ts +5 -0
- package/dist/crowdy-studio/embed/index.d.ts.map +1 -0
- package/dist/crowdy-studio/embed/index.js +4 -0
- package/dist/crowdy-studio/embed/panel.d.ts +162 -0
- package/dist/crowdy-studio/embed/panel.d.ts.map +1 -0
- package/dist/crowdy-studio/embed/panel.js +642 -0
- package/dist/crowdy-studio/index.d.ts +9 -3
- package/dist/crowdy-studio/index.d.ts.map +1 -1
- package/dist/crowdy-studio/index.js +6 -0
- package/dist/crowdy-studio/layout.d.ts +50 -0
- package/dist/crowdy-studio/layout.d.ts.map +1 -0
- package/dist/crowdy-studio/layout.js +155 -0
- package/dist/crowdy-studio/models.d.ts +67 -0
- package/dist/crowdy-studio/models.d.ts.map +1 -1
- package/dist/crowdy-studio/mount.d.ts +15 -0
- package/dist/crowdy-studio/mount.d.ts.map +1 -1
- package/dist/crowdy-studio/mount.js +132 -3
- package/dist/crowdy-studio/splitter.d.ts +33 -0
- package/dist/crowdy-studio/splitter.d.ts.map +1 -0
- package/dist/crowdy-studio/splitter.js +133 -0
- package/dist/crowdy-studio/styles.d.ts +1 -1
- package/dist/crowdy-studio/styles.d.ts.map +1 -1
- package/dist/crowdy-studio/styles.js +128 -11
- package/dist/generated/graphql.d.ts +3753 -1
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +296 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/player-host/control-banner.d.ts +40 -0
- package/dist/player-host/control-banner.d.ts.map +1 -0
- package/dist/player-host/control-banner.js +252 -0
- package/dist/player-host/control-gate.d.ts +77 -0
- package/dist/player-host/control-gate.d.ts.map +1 -0
- package/dist/player-host/control-gate.js +197 -0
- package/dist/player-host/index.d.ts +7 -0
- package/dist/player-host/index.d.ts.map +1 -0
- package/dist/player-host/index.js +5 -0
- package/dist/player-host/lease-manager.d.ts +66 -0
- package/dist/player-host/lease-manager.d.ts.map +1 -0
- package/dist/player-host/lease-manager.js +428 -0
- package/dist/player-host/schemas.d.ts +9 -0
- package/dist/player-host/schemas.d.ts.map +1 -0
- package/dist/player-host/schemas.js +347 -0
- package/dist/player-host/tools.d.ts +13 -0
- package/dist/player-host/tools.d.ts.map +1 -0
- package/dist/player-host/tools.js +79 -0
- package/dist/player-host/types.d.ts +203 -0
- package/dist/player-host/types.d.ts.map +1 -0
- package/dist/player-host/types.js +1 -0
- package/package.json +17 -2
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { CrowdyAgentError } from './errors.js';
|
|
2
|
+
const DEFAULT_MAX_DEPTH = 12;
|
|
3
|
+
const DEFAULT_MAX_NODES = 4096;
|
|
4
|
+
const DEFAULT_MAX_BYTES = 1048576;
|
|
5
|
+
const DECIMAL_PATTERN = /^(0|-[1-9][0-9]*|[1-9][0-9]*)$/u;
|
|
6
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
7
|
+
const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/u;
|
|
8
|
+
/**
|
|
9
|
+
* Fields the model must never supply. They are injected from authenticated
|
|
10
|
+
* session, dispatch, approval, and lease context.
|
|
11
|
+
*/
|
|
12
|
+
export const FORBIDDEN_AGENT_AUTHORITY_FIELDS = Object.freeze([
|
|
13
|
+
'userId',
|
|
14
|
+
'ownerUserId',
|
|
15
|
+
'appId',
|
|
16
|
+
'projectId',
|
|
17
|
+
'gridId',
|
|
18
|
+
'sessionId',
|
|
19
|
+
'runId',
|
|
20
|
+
'toolCallId',
|
|
21
|
+
'clientEpoch',
|
|
22
|
+
'leaseId',
|
|
23
|
+
'lease',
|
|
24
|
+
'approval',
|
|
25
|
+
'approvalGrant',
|
|
26
|
+
'argumentHash',
|
|
27
|
+
'descriptorDigest',
|
|
28
|
+
'idempotencyKey',
|
|
29
|
+
'deadline',
|
|
30
|
+
'token',
|
|
31
|
+
'authorization',
|
|
32
|
+
'headers',
|
|
33
|
+
'endpoint',
|
|
34
|
+
'url',
|
|
35
|
+
'permissions',
|
|
36
|
+
'authority',
|
|
37
|
+
]);
|
|
38
|
+
const FORBIDDEN_NORMALIZED = new Set(FORBIDDEN_AGENT_AUTHORITY_FIELDS.map(normalizeField));
|
|
39
|
+
/** Validate a schema itself before it enters an immutable registry. */
|
|
40
|
+
export function assertBoundedJsonSchema(schema, options = {}) {
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
inspectSchema(schema, '$', 0, seen, options.rejectAuthorityFields ?? true);
|
|
43
|
+
}
|
|
44
|
+
/** Strictly validate JSON data and reject unknown fields or unbounded values. */
|
|
45
|
+
export function validateJsonSchemaValue(schema, value, options = {}) {
|
|
46
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
47
|
+
let bytes;
|
|
48
|
+
try {
|
|
49
|
+
bytes = encodedJsonBytes(value);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw validationError(options.direction, '$', error instanceof Error ? error.message : 'value is not bounded JSON');
|
|
53
|
+
}
|
|
54
|
+
if (bytes > maxBytes) {
|
|
55
|
+
throw validationError(options.direction, '$', `encoded value is ${bytes} bytes; maximum is ${maxBytes}`);
|
|
56
|
+
}
|
|
57
|
+
const state = {
|
|
58
|
+
nodes: 0,
|
|
59
|
+
maxNodes: options.maxNodes ?? DEFAULT_MAX_NODES,
|
|
60
|
+
maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
61
|
+
direction: options.direction,
|
|
62
|
+
ancestors: new Set(),
|
|
63
|
+
};
|
|
64
|
+
validateNode(schema, value, '$', 0, state);
|
|
65
|
+
}
|
|
66
|
+
/** RFC-8785-compatible canonical JSON for the JSON subset used by contracts. */
|
|
67
|
+
export function canonicalJson(value) {
|
|
68
|
+
return canonicalize(value, new Set());
|
|
69
|
+
}
|
|
70
|
+
/** Synchronous browser-safe SHA-256 used for descriptor and content digests. */
|
|
71
|
+
export function sha256Digest(value) {
|
|
72
|
+
const bytes = typeof value === 'string' ? new TextEncoder().encode(value) : value;
|
|
73
|
+
const hash = sha256(bytes);
|
|
74
|
+
return `sha256:${Array.from(hash, (byte) => byte.toString(16).padStart(2, '0')).join('')}`;
|
|
75
|
+
}
|
|
76
|
+
export function digestCanonicalJson(value) {
|
|
77
|
+
return sha256Digest(canonicalJson(value));
|
|
78
|
+
}
|
|
79
|
+
/** Recursively freeze a JSON-like contract object. */
|
|
80
|
+
export function deepFreeze(value) {
|
|
81
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
82
|
+
Object.freeze(value);
|
|
83
|
+
for (const child of Object.values(value)) {
|
|
84
|
+
deepFreeze(child);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
export function isDecimalString(value) {
|
|
90
|
+
return DECIMAL_PATTERN.test(value);
|
|
91
|
+
}
|
|
92
|
+
function inspectSchema(schema, path, depth, seen, rejectAuthorityFields) {
|
|
93
|
+
if (depth > DEFAULT_MAX_DEPTH)
|
|
94
|
+
schemaFailure(path, 'schema is too deeply nested');
|
|
95
|
+
if (seen.has(schema))
|
|
96
|
+
schemaFailure(path, 'recursive schemas are forbidden');
|
|
97
|
+
seen.add(schema);
|
|
98
|
+
if ('oneOf' in schema) {
|
|
99
|
+
if (schema.oneOf.length < 2 || schema.oneOf.length > 16) {
|
|
100
|
+
schemaFailure(path, 'oneOf must contain 2 to 16 bounded alternatives');
|
|
101
|
+
}
|
|
102
|
+
schema.oneOf.forEach((entry, index) => inspectSchema(entry, `${path}.oneOf[${index}]`, depth + 1, seen, rejectAuthorityFields));
|
|
103
|
+
seen.delete(schema);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (schema.enum) {
|
|
107
|
+
if (schema.enum.length === 0 || schema.enum.length > 128) {
|
|
108
|
+
schemaFailure(path, 'enum must contain 1 to 128 values');
|
|
109
|
+
}
|
|
110
|
+
if (new Set(schema.enum.map((entry) => canonicalJson(entry))).size !== schema.enum.length) {
|
|
111
|
+
schemaFailure(path, 'enum values must be unique');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (schema.type === 'object') {
|
|
115
|
+
if (schema.additionalProperties !== false) {
|
|
116
|
+
schemaFailure(path, 'every object must set additionalProperties:false');
|
|
117
|
+
}
|
|
118
|
+
const keys = Object.keys(schema.properties);
|
|
119
|
+
const maxProperties = schema.maxProperties ?? keys.length;
|
|
120
|
+
if (keys.length > 128 || maxProperties > keys.length || maxProperties < 0) {
|
|
121
|
+
schemaFailure(path, 'object property bounds are invalid');
|
|
122
|
+
}
|
|
123
|
+
if (new Set(schema.required).size !== schema.required.length) {
|
|
124
|
+
schemaFailure(path, 'required fields must be unique');
|
|
125
|
+
}
|
|
126
|
+
for (const required of schema.required) {
|
|
127
|
+
if (!(required in schema.properties)) {
|
|
128
|
+
schemaFailure(path, `required field ${required} has no schema`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const [key, child] of Object.entries(schema.properties)) {
|
|
132
|
+
if (rejectAuthorityFields && FORBIDDEN_NORMALIZED.has(normalizeField(key))) {
|
|
133
|
+
schemaFailure(`${path}.properties.${key}`, 'caller authority fields are forbidden');
|
|
134
|
+
}
|
|
135
|
+
inspectSchema(child, `${path}.properties.${key}`, depth + 1, seen, rejectAuthorityFields);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (schema.type === 'array') {
|
|
139
|
+
if (!Number.isSafeInteger(schema.maxItems) ||
|
|
140
|
+
schema.maxItems < 0 ||
|
|
141
|
+
schema.maxItems > 10000 ||
|
|
142
|
+
(schema.minItems !== undefined &&
|
|
143
|
+
(!Number.isSafeInteger(schema.minItems) ||
|
|
144
|
+
schema.minItems < 0 ||
|
|
145
|
+
schema.minItems > schema.maxItems))) {
|
|
146
|
+
schemaFailure(path, 'array item bounds are required and invalid');
|
|
147
|
+
}
|
|
148
|
+
inspectSchema(schema.items, `${path}.items`, depth + 1, seen, rejectAuthorityFields);
|
|
149
|
+
}
|
|
150
|
+
else if (schema.type === 'string') {
|
|
151
|
+
if (schema.maxLength === undefined ||
|
|
152
|
+
!Number.isSafeInteger(schema.maxLength) ||
|
|
153
|
+
schema.maxLength < 0 ||
|
|
154
|
+
schema.maxLength > DEFAULT_MAX_BYTES ||
|
|
155
|
+
(schema.minLength !== undefined &&
|
|
156
|
+
(!Number.isSafeInteger(schema.minLength) ||
|
|
157
|
+
schema.minLength < 0 ||
|
|
158
|
+
schema.minLength > schema.maxLength))) {
|
|
159
|
+
schemaFailure(path, 'every string must have valid min/max length bounds');
|
|
160
|
+
}
|
|
161
|
+
if (schema.pattern !== undefined) {
|
|
162
|
+
try {
|
|
163
|
+
void new RegExp(schema.pattern, 'u');
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
schemaFailure(path, 'string pattern is invalid');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (schema.type === 'number' || schema.type === 'integer') {
|
|
171
|
+
if (schema.minimum === undefined ||
|
|
172
|
+
schema.maximum === undefined ||
|
|
173
|
+
!Number.isFinite(schema.minimum) ||
|
|
174
|
+
!Number.isFinite(schema.maximum) ||
|
|
175
|
+
schema.minimum > schema.maximum) {
|
|
176
|
+
schemaFailure(path, 'every number must have finite minimum and maximum');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
seen.delete(schema);
|
|
180
|
+
}
|
|
181
|
+
function validateNode(schema, value, path, depth, state) {
|
|
182
|
+
state.nodes++;
|
|
183
|
+
if (state.nodes > state.maxNodes) {
|
|
184
|
+
throw validationError(state.direction, path, 'value has too many nodes');
|
|
185
|
+
}
|
|
186
|
+
if (depth > state.maxDepth) {
|
|
187
|
+
throw validationError(state.direction, path, 'value is too deeply nested');
|
|
188
|
+
}
|
|
189
|
+
if ('oneOf' in schema) {
|
|
190
|
+
let matches = 0;
|
|
191
|
+
for (const alternative of schema.oneOf) {
|
|
192
|
+
try {
|
|
193
|
+
const branchState = {
|
|
194
|
+
...state,
|
|
195
|
+
ancestors: new Set(state.ancestors),
|
|
196
|
+
};
|
|
197
|
+
validateNode(alternative, value, path, depth, branchState);
|
|
198
|
+
matches++;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (!(error instanceof CrowdyAgentError))
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (matches !== 1) {
|
|
206
|
+
throw validationError(state.direction, path, `must match exactly one schema (matched ${matches})`);
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (schema.const !== undefined && !Object.is(value, schema.const)) {
|
|
211
|
+
throw validationError(state.direction, path, `must equal ${String(schema.const)}`);
|
|
212
|
+
}
|
|
213
|
+
if (schema.enum && !schema.enum.some((entry) => Object.is(entry, value))) {
|
|
214
|
+
throw validationError(state.direction, path, 'contains an unknown enum value');
|
|
215
|
+
}
|
|
216
|
+
if (schema.type === 'null') {
|
|
217
|
+
if (value !== null)
|
|
218
|
+
throw validationError(state.direction, path, 'must be null');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (schema.type === 'boolean') {
|
|
222
|
+
if (typeof value !== 'boolean') {
|
|
223
|
+
throw validationError(state.direction, path, 'must be a boolean');
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (schema.type === 'string') {
|
|
228
|
+
if (typeof value !== 'string') {
|
|
229
|
+
throw validationError(state.direction, path, 'must be a string');
|
|
230
|
+
}
|
|
231
|
+
if ((schema.minLength !== undefined && value.length < schema.minLength) ||
|
|
232
|
+
(schema.maxLength !== undefined && value.length > schema.maxLength)) {
|
|
233
|
+
throw validationError(state.direction, path, `string length must be ${schema.minLength ?? 0}..${schema.maxLength ?? 'bounded'}`);
|
|
234
|
+
}
|
|
235
|
+
if (schema.pattern && !new RegExp(schema.pattern, 'u').test(value)) {
|
|
236
|
+
throw validationError(state.direction, path, 'does not match the required pattern');
|
|
237
|
+
}
|
|
238
|
+
if (schema.format === 'uuid' && !UUID_PATTERN.test(value)) {
|
|
239
|
+
throw validationError(state.direction, path, 'must be a UUID');
|
|
240
|
+
}
|
|
241
|
+
if (schema.format === 'date-time' &&
|
|
242
|
+
(!DATE_TIME_PATTERN.test(value) || !Number.isFinite(Date.parse(value)))) {
|
|
243
|
+
throw validationError(state.direction, path, 'must be a UTC date-time');
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (schema.type === 'number' || schema.type === 'integer') {
|
|
248
|
+
if (typeof value !== 'number' ||
|
|
249
|
+
!Number.isFinite(value) ||
|
|
250
|
+
(schema.type === 'integer' && !Number.isSafeInteger(value))) {
|
|
251
|
+
throw validationError(state.direction, path, `must be a finite ${schema.type}`);
|
|
252
|
+
}
|
|
253
|
+
if ((schema.minimum !== undefined && value < schema.minimum) ||
|
|
254
|
+
(schema.maximum !== undefined && value > schema.maximum)) {
|
|
255
|
+
throw validationError(state.direction, path, `must be within ${schema.minimum}..${schema.maximum}`);
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (schema.type === 'array') {
|
|
260
|
+
if (!Array.isArray(value)) {
|
|
261
|
+
throw validationError(state.direction, path, 'must be an array');
|
|
262
|
+
}
|
|
263
|
+
if ((schema.minItems !== undefined && value.length < schema.minItems) ||
|
|
264
|
+
value.length > schema.maxItems) {
|
|
265
|
+
throw validationError(state.direction, path, `array length must be ${schema.minItems ?? 0}..${schema.maxItems}`);
|
|
266
|
+
}
|
|
267
|
+
if (schema.uniqueItems) {
|
|
268
|
+
const values = value.map((entry) => canonicalJson(entry));
|
|
269
|
+
if (new Set(values).size !== values.length) {
|
|
270
|
+
throw validationError(state.direction, path, 'array items must be unique');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
enterContainer(value, path, state);
|
|
274
|
+
try {
|
|
275
|
+
value.forEach((entry, index) => validateNode(schema.items, entry, `${path}[${index}]`, depth + 1, state));
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
state.ancestors.delete(value);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!isPlainRecord(value)) {
|
|
283
|
+
throw validationError(state.direction, path, 'must be a plain object');
|
|
284
|
+
}
|
|
285
|
+
if (schema.type !== 'object') {
|
|
286
|
+
throw validationError(state.direction, path, 'schema type mismatch');
|
|
287
|
+
}
|
|
288
|
+
const keys = Object.keys(value);
|
|
289
|
+
const allowed = new Set(Object.keys(schema.properties));
|
|
290
|
+
const unknown = keys.find((key) => !allowed.has(key));
|
|
291
|
+
if (unknown) {
|
|
292
|
+
throw validationError(state.direction, `${path}.${unknown}`, 'unknown fields are forbidden');
|
|
293
|
+
}
|
|
294
|
+
if ((schema.minProperties !== undefined && keys.length < schema.minProperties) ||
|
|
295
|
+
keys.length > (schema.maxProperties ?? allowed.size)) {
|
|
296
|
+
throw validationError(state.direction, path, 'object property count is out of bounds');
|
|
297
|
+
}
|
|
298
|
+
for (const key of schema.required) {
|
|
299
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) {
|
|
300
|
+
throw validationError(state.direction, `${path}.${key}`, 'required field is missing');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
enterContainer(value, path, state);
|
|
304
|
+
try {
|
|
305
|
+
for (const key of keys) {
|
|
306
|
+
validateNode(schema.properties[key], value[key], `${path}.${key}`, depth + 1, state);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
state.ancestors.delete(value);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function enterContainer(value, path, state) {
|
|
314
|
+
if (state.ancestors.has(value)) {
|
|
315
|
+
throw validationError(state.direction, path, 'cyclic values are forbidden');
|
|
316
|
+
}
|
|
317
|
+
state.ancestors.add(value);
|
|
318
|
+
}
|
|
319
|
+
function validationError(direction, field, message) {
|
|
320
|
+
const code = direction === 'OUTPUT'
|
|
321
|
+
? 'AGENT_TOOL_OUTPUT_INVALID'
|
|
322
|
+
: direction === 'SCHEMA'
|
|
323
|
+
? 'AGENT_TOOL_DESCRIPTOR_INVALID'
|
|
324
|
+
: 'AGENT_TOOL_INPUT_INVALID';
|
|
325
|
+
return new CrowdyAgentError(code, `${field}: ${message}`, { field });
|
|
326
|
+
}
|
|
327
|
+
function schemaFailure(path, message) {
|
|
328
|
+
throw validationError('SCHEMA', path, message);
|
|
329
|
+
}
|
|
330
|
+
function encodedJsonBytes(value) {
|
|
331
|
+
try {
|
|
332
|
+
return new TextEncoder().encode(canonicalJson(value)).byteLength;
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
if (error instanceof CrowdyAgentError)
|
|
336
|
+
throw error;
|
|
337
|
+
throw new CrowdyAgentError('AGENT_TOOL_INPUT_INVALID', 'Value is not bounded JSON', {
|
|
338
|
+
cause: error,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function canonicalize(value, ancestors) {
|
|
343
|
+
if (value === null)
|
|
344
|
+
return 'null';
|
|
345
|
+
if (typeof value === 'string')
|
|
346
|
+
return JSON.stringify(value);
|
|
347
|
+
if (typeof value === 'boolean')
|
|
348
|
+
return value ? 'true' : 'false';
|
|
349
|
+
if (typeof value === 'number') {
|
|
350
|
+
if (!Number.isFinite(value)) {
|
|
351
|
+
throw new CrowdyAgentError('AGENT_TOOL_INPUT_INVALID', 'Non-finite numbers are forbidden');
|
|
352
|
+
}
|
|
353
|
+
if (Object.is(value, -0))
|
|
354
|
+
return '0';
|
|
355
|
+
return JSON.stringify(value);
|
|
356
|
+
}
|
|
357
|
+
if (typeof value !== 'object') {
|
|
358
|
+
throw new CrowdyAgentError('AGENT_TOOL_INPUT_INVALID', `Non-JSON value of type ${typeof value} is forbidden`);
|
|
359
|
+
}
|
|
360
|
+
if (ancestors.has(value)) {
|
|
361
|
+
throw new CrowdyAgentError('AGENT_TOOL_INPUT_INVALID', 'Cyclic JSON is forbidden');
|
|
362
|
+
}
|
|
363
|
+
ancestors.add(value);
|
|
364
|
+
try {
|
|
365
|
+
if (Array.isArray(value)) {
|
|
366
|
+
return `[${value.map((entry) => canonicalize(entry, ancestors)).join(',')}]`;
|
|
367
|
+
}
|
|
368
|
+
if (!isPlainRecord(value)) {
|
|
369
|
+
throw new CrowdyAgentError('AGENT_TOOL_INPUT_INVALID', 'Only plain JSON objects are allowed');
|
|
370
|
+
}
|
|
371
|
+
const entries = Object.keys(value)
|
|
372
|
+
.sort()
|
|
373
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalize(value[key], ancestors)}`);
|
|
374
|
+
return `{${entries.join(',')}}`;
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
ancestors.delete(value);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function isPlainRecord(value) {
|
|
381
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
382
|
+
return false;
|
|
383
|
+
const prototype = Object.getPrototypeOf(value);
|
|
384
|
+
return prototype === Object.prototype || prototype === null;
|
|
385
|
+
}
|
|
386
|
+
function normalizeField(value) {
|
|
387
|
+
return value.replace(/[^A-Za-z0-9]/gu, '').toLowerCase();
|
|
388
|
+
}
|
|
389
|
+
function sha256(input) {
|
|
390
|
+
const constants = new Uint32Array([
|
|
391
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
|
392
|
+
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
393
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
|
394
|
+
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
395
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
|
396
|
+
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
397
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
|
398
|
+
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
399
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
|
400
|
+
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
401
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
|
402
|
+
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
403
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
|
404
|
+
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
405
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
|
406
|
+
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
407
|
+
]);
|
|
408
|
+
const bitLength = input.length * 8;
|
|
409
|
+
const paddedLength = Math.ceil((input.length + 9) / 64) * 64;
|
|
410
|
+
const bytes = new Uint8Array(paddedLength);
|
|
411
|
+
bytes.set(input);
|
|
412
|
+
bytes[input.length] = 0x80;
|
|
413
|
+
const view = new DataView(bytes.buffer);
|
|
414
|
+
const high = Math.floor(bitLength / 4294967296);
|
|
415
|
+
const low = bitLength >>> 0;
|
|
416
|
+
view.setUint32(paddedLength - 8, high);
|
|
417
|
+
view.setUint32(paddedLength - 4, low);
|
|
418
|
+
const hash = new Uint32Array([
|
|
419
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
420
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
421
|
+
]);
|
|
422
|
+
const words = new Uint32Array(64);
|
|
423
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
424
|
+
for (let index = 0; index < 16; index++) {
|
|
425
|
+
words[index] = view.getUint32(offset + index * 4);
|
|
426
|
+
}
|
|
427
|
+
for (let index = 16; index < 64; index++) {
|
|
428
|
+
const a = words[index - 15];
|
|
429
|
+
const b = words[index - 2];
|
|
430
|
+
const s0 = rotateRight(a, 7) ^ rotateRight(a, 18) ^ (a >>> 3);
|
|
431
|
+
const s1 = rotateRight(b, 17) ^ rotateRight(b, 19) ^ (b >>> 10);
|
|
432
|
+
words[index] = (words[index - 16] + s0 + words[index - 7] + s1) >>> 0;
|
|
433
|
+
}
|
|
434
|
+
let [a, b, c, d, e, f, g, h] = hash;
|
|
435
|
+
for (let index = 0; index < 64; index++) {
|
|
436
|
+
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
437
|
+
const choice = (e & f) ^ (~e & g);
|
|
438
|
+
const temp1 = (h + sum1 + choice + constants[index] + words[index]) >>> 0;
|
|
439
|
+
const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
440
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
441
|
+
const temp2 = (sum0 + majority) >>> 0;
|
|
442
|
+
h = g;
|
|
443
|
+
g = f;
|
|
444
|
+
f = e;
|
|
445
|
+
e = (d + temp1) >>> 0;
|
|
446
|
+
d = c;
|
|
447
|
+
c = b;
|
|
448
|
+
b = a;
|
|
449
|
+
a = (temp1 + temp2) >>> 0;
|
|
450
|
+
}
|
|
451
|
+
hash[0] = (hash[0] + a) >>> 0;
|
|
452
|
+
hash[1] = (hash[1] + b) >>> 0;
|
|
453
|
+
hash[2] = (hash[2] + c) >>> 0;
|
|
454
|
+
hash[3] = (hash[3] + d) >>> 0;
|
|
455
|
+
hash[4] = (hash[4] + e) >>> 0;
|
|
456
|
+
hash[5] = (hash[5] + f) >>> 0;
|
|
457
|
+
hash[6] = (hash[6] + g) >>> 0;
|
|
458
|
+
hash[7] = (hash[7] + h) >>> 0;
|
|
459
|
+
}
|
|
460
|
+
const output = new Uint8Array(32);
|
|
461
|
+
const outputView = new DataView(output.buffer);
|
|
462
|
+
hash.forEach((word, index) => outputView.setUint32(index * 4, word));
|
|
463
|
+
return output;
|
|
464
|
+
}
|
|
465
|
+
function rotateRight(value, bits) {
|
|
466
|
+
return (value >>> bits) | (value << (32 - bits));
|
|
467
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CrowdyStudioController } from '../crowdy-studio/controller.js';
|
|
2
|
+
import { projectTargets } from '../crowdy-studio/models.js';
|
|
3
|
+
import type { CrowdyAgentBrowserToolHandlersV1 } from './browser-dispatcher.js';
|
|
4
|
+
export interface CrowdyStudioAgentToolsOptionsV1 {
|
|
5
|
+
readonly getClientEpoch?: () => string | null;
|
|
6
|
+
readonly getContextVersion?: () => string | undefined;
|
|
7
|
+
readonly getLeaseKinds?: () => readonly ('WORKSPACE' | 'PLAY')[];
|
|
8
|
+
readonly getHostCapabilityRevision?: () => string | undefined;
|
|
9
|
+
readonly isLeaseActive?: (leaseId: string, kind: 'WORKSPACE' | 'PLAY') => boolean;
|
|
10
|
+
}
|
|
11
|
+
/** Exact browser tools backed by the same headless kernel as the human UI. */
|
|
12
|
+
export declare function createCrowdyStudioAgentTools(controller: CrowdyStudioController, options?: CrowdyStudioAgentToolsOptionsV1): CrowdyAgentBrowserToolHandlersV1;
|
|
13
|
+
/** Keep the canonical target helper reachable for BWF host integrations. */
|
|
14
|
+
export declare const crowdyStudioAgentProjectTargets: typeof projectTargets;
|
|
15
|
+
//# sourceMappingURL=studio-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio-tools.d.ts","sourceRoot":"","sources":["../../src/crowdy-agent/studio-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EACL,cAAc,EAGf,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAGhF,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,SAAS,CAAC,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC;IACjE,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,WAAW,GAAG,MAAM,KACvB,OAAO,CAAC;CACd;AAED,8EAA8E;AAC9E,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,sBAAsB,EAClC,OAAO,GAAE,+BAAoC,GAC5C,gCAAgC,CAgNlC;AAsND,4EAA4E;AAC5E,eAAO,MAAM,+BAA+B,uBAAiB,CAAC"}
|