@noodleseed/one 0.158.0 → 0.160.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 (57) hide show
  1. package/dist/commands/deploy-first-flow.d.ts +1 -1
  2. package/dist/commands/deploy-first-flow.d.ts.map +1 -1
  3. package/dist/commands/deploy-first-flow.js +4 -193
  4. package/dist/commands/deploy-first-flow.js.map +1 -1
  5. package/dist/commands/deploy-ops.d.ts.map +1 -1
  6. package/dist/commands/deploy-ops.js +25 -1
  7. package/dist/commands/deploy-ops.js.map +1 -1
  8. package/dist/commands/deploy-preflight-command.d.ts +8 -0
  9. package/dist/commands/deploy-preflight-command.d.ts.map +1 -0
  10. package/dist/commands/deploy-preflight-command.js +78 -0
  11. package/dist/commands/deploy-preflight-command.js.map +1 -0
  12. package/dist/commands/deploy-preflight-output.d.ts +18 -0
  13. package/dist/commands/deploy-preflight-output.d.ts.map +1 -0
  14. package/dist/commands/deploy-preflight-output.js +176 -0
  15. package/dist/commands/deploy-preflight-output.js.map +1 -0
  16. package/dist/commands/deploy-preflight.d.ts +6 -1
  17. package/dist/commands/deploy-preflight.d.ts.map +1 -1
  18. package/dist/commands/deploy-preflight.js +24 -0
  19. package/dist/commands/deploy-preflight.js.map +1 -1
  20. package/dist/commands/deploy-target.d.ts +1 -0
  21. package/dist/commands/deploy-target.d.ts.map +1 -1
  22. package/dist/commands/deploy-target.js +9 -8
  23. package/dist/commands/deploy-target.js.map +1 -1
  24. package/dist/openapi-import.d.ts.map +1 -1
  25. package/dist/openapi-import.js +51 -23
  26. package/dist/openapi-import.js.map +1 -1
  27. package/dist/plugin-mode/build-readiness-recorder.d.ts.map +1 -1
  28. package/dist/plugin-mode/build-readiness-recorder.js +3 -0
  29. package/dist/plugin-mode/build-readiness-recorder.js.map +1 -1
  30. package/dist/plugin-mode/plugin-operation-contract.d.ts +120 -1
  31. package/dist/plugin-mode/plugin-operation-contract.d.ts.map +1 -1
  32. package/dist/plugin-mode/plugin-operation-contract.js +32 -1
  33. package/dist/plugin-mode/plugin-operation-contract.js.map +1 -1
  34. package/dist/plugin-mode/plugin-operation-tools.d.ts.map +1 -1
  35. package/dist/plugin-mode/plugin-operation-tools.js +47 -2
  36. package/dist/plugin-mode/plugin-operation-tools.js.map +1 -1
  37. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
  38. package/node_modules/@noodle-borg/agent-kit/dist/plugin-bootstrap-skill.js +1 -1
  39. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +1 -1
  40. package/node_modules/@noodle-borg/agent-kit/dist/skill-operations-refs.js +2 -1
  41. package/node_modules/@noodle-borg/agent-kit/dist/skill-router.js +1 -1
  42. package/node_modules/@noodle-borg/agent-kit/dist/skill-verification-ref.js +1 -0
  43. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  44. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-deployment.js +85 -68
  45. package/node_modules/@noodle-borg/control-plane/dist/contracts.d.ts +5 -0
  46. package/node_modules/@noodle-borg/control-plane/dist/contracts.js +9 -0
  47. package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.d.ts +16 -1
  48. package/node_modules/@noodle-borg/control-plane/dist/in-memory-control-plane-store.js +18 -0
  49. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
  50. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  51. package/node_modules/@noodle-borg/openapi-import/dist/index.js +76 -53
  52. package/node_modules/@noodle-borg/openapi-import/dist/request-body.js +125 -0
  53. package/node_modules/@noodle-borg/openapi-import/dist/request-input.js +139 -0
  54. package/node_modules/@noodle-borg/openapi-import/dist/response-schema.js +17 -4
  55. package/node_modules/@noodle-borg/service/dist/routes/org-admin.js +21 -11
  56. package/node_modules/@noodle-borg/service/package.json +1 -1
  57. package/package.json +2 -2
@@ -0,0 +1,139 @@
1
+ const FIELD = /^[A-Za-z_][A-Za-z0-9_]*$/;
2
+ const RESERVED_FIELDS = new Set([
3
+ 'then',
4
+ 'equals',
5
+ 'at',
6
+ 'optional',
7
+ 'toExpression',
8
+ 'toString',
9
+ '__proto__',
10
+ 'constructor',
11
+ 'prototype',
12
+ ]);
13
+ export function identifier(value) {
14
+ const out = value.replace(/[^A-Za-z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
15
+ return /^[A-Za-z_]/.test(out) ? out : `op_${out || 'operation'}`;
16
+ }
17
+ /** Keep wire query names intact; path placeholder names may be normalized with their arguments. */
18
+ export function importOperationParameters(path, inherited, own) {
19
+ const selected = new Map();
20
+ for (const list of [inherited, own]) {
21
+ if (list === undefined)
22
+ continue;
23
+ if (!Array.isArray(list))
24
+ throw new Error('import openapi: parameters must be an array');
25
+ for (const value of list) {
26
+ if (!isRecord(value) ||
27
+ typeof value.name !== 'string' ||
28
+ value.name.length === 0 ||
29
+ value.name.length > 128 ||
30
+ /\p{Cc}/u.test(value.name)) {
31
+ throw new Error('import openapi: use inline parameters with bounded, non-control names');
32
+ }
33
+ if (value.in !== 'path' && value.in !== 'query') {
34
+ throw new Error('import openapi: only path/query parameters are supported; map other locations explicitly in TypeScript');
35
+ }
36
+ if (value.style !== undefined && value.style !== (value.in === 'path' ? 'simple' : 'form')) {
37
+ throw new Error('import openapi: unsupported parameter serialization style; map it explicitly in TypeScript');
38
+ }
39
+ if (value.allowReserved === true ||
40
+ value.allowEmptyValue === true ||
41
+ value.content !== undefined) {
42
+ throw new Error('import openapi: unsupported parameter wire encoding; map it explicitly in TypeScript');
43
+ }
44
+ selected.set(`${value.in}:${value.name}`, value);
45
+ }
46
+ }
47
+ if (selected.size > 200)
48
+ throw new Error('import openapi: operation exceeds 200 parameters');
49
+ const names = new Set();
50
+ const pathNames = new Map();
51
+ const parameters = [...selected.values()].map((value) => {
52
+ const rawName = String(value.name);
53
+ let name = value.in === 'path' ? identifier(rawName) : rawName;
54
+ if (value.in === 'path' && RESERVED_FIELDS.has(name))
55
+ name = `param_${name}`;
56
+ if (!FIELD.test(name) || RESERVED_FIELDS.has(name)) {
57
+ throw new Error('import openapi: query parameter name cannot be addressed by the authoring expression contract; map this API through an application handler');
58
+ }
59
+ if (names.has(name))
60
+ throw new Error('import openapi: parameter input-name collision; map the inputs explicitly');
61
+ names.add(name);
62
+ if (value.in === 'path')
63
+ pathNames.set(rawName, name);
64
+ return {
65
+ name,
66
+ in: value.in === 'path' ? 'path' : 'query',
67
+ required: value.in === 'path' || value.required === true,
68
+ schema: parameterJsonSchema(value.schema),
69
+ };
70
+ });
71
+ const placeholders = [...path.matchAll(/\{([^{}]+)\}/g)].map((match) => match[1]);
72
+ if (placeholders.some((name) => name === undefined || !pathNames.has(name)) ||
73
+ [...pathNames.keys()].some((name) => !placeholders.includes(name))) {
74
+ throw new Error('import openapi: every path placeholder must match a declared path parameter');
75
+ }
76
+ return {
77
+ parameters,
78
+ connectorPath: path.replace(/\{([^{}]+)\}/g, (_match, name) => `{${pathNames.get(name)}}`),
79
+ };
80
+ }
81
+ function parameterJsonSchema(schema) {
82
+ if (schema === undefined)
83
+ return { type: 'string' };
84
+ if (!isRecord(schema))
85
+ throw new Error('import openapi: parameter schema must be an object');
86
+ if (schema.$ref !== undefined)
87
+ throw new Error('import openapi: inline parameter schemas before import; references are not fetched');
88
+ const allowed = new Set([
89
+ 'type',
90
+ 'nullable',
91
+ 'enum',
92
+ 'format',
93
+ 'title',
94
+ 'description',
95
+ 'example',
96
+ 'examples',
97
+ 'deprecated',
98
+ ]);
99
+ if (Object.keys(schema).some((key) => !allowed.has(key))) {
100
+ throw new Error('import openapi: unsupported parameter schema constraint; author its validated mapping explicitly');
101
+ }
102
+ const types = Array.isArray(schema.type)
103
+ ? schema.type
104
+ : [schema.type ?? 'string'];
105
+ const concrete = types.filter((type) => type !== 'null');
106
+ const base = concrete[0];
107
+ if (concrete.length !== 1 ||
108
+ types.length > 2 ||
109
+ typeof base !== 'string' ||
110
+ !['string', 'number', 'integer', 'boolean'].includes(base)) {
111
+ throw new Error('import openapi: unsupported parameter schema; use a scalar or an explicitly authored mapping');
112
+ }
113
+ const enumValues = base === 'string' ? stringEnum(schema.enum) : undefined;
114
+ if (schema.enum !== undefined &&
115
+ (enumValues === undefined || types.includes('null') || schema.nullable === true)) {
116
+ throw new Error('import openapi: unsupported parameter enum/nullable combination; author an explicit mapping');
117
+ }
118
+ const format = typeof schema.format === 'string' ? schema.format : undefined;
119
+ if ((schema.nullable !== undefined && typeof schema.nullable !== 'boolean') ||
120
+ (schema.format !== undefined && format === undefined)) {
121
+ throw new Error('import openapi: invalid parameter nullable or format declaration');
122
+ }
123
+ return {
124
+ type: schema.nullable === true || types.includes('null') ? [base, 'null'] : base,
125
+ ...(enumValues !== undefined ? { enum: enumValues } : {}),
126
+ ...(format !== undefined ? { format } : {}),
127
+ };
128
+ }
129
+ function stringEnum(value) {
130
+ if (!Array.isArray(value) || value.length === 0)
131
+ return undefined;
132
+ if (!value.every((entry) => typeof entry === 'string'))
133
+ return undefined;
134
+ return value;
135
+ }
136
+ function isRecord(value) {
137
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
138
+ }
139
+ //# sourceMappingURL=request-input.js.map
@@ -8,6 +8,10 @@ export const MAX_RESPONSE_SCHEMA_DEPTH = 8;
8
8
  export const MAX_RESPONSE_SCHEMA_NODES = 200;
9
9
  const UNKNOWN = { kind: 'unknown' };
10
10
  const UNSUPPORTED_COMPOSITIONS = ['oneOf', 'anyOf', 'allOf', 'not'];
11
+ /** Shared bounded conversion; request validation is owned by request-body.ts. */
12
+ export function importSchemaTree(raw, ctx, request = false) {
13
+ return convertSchema(raw, ctx, { nodes: 0, boundWarned: false, request }, 0, new Set());
14
+ }
11
15
  /**
12
16
  * Picks the best 2xx `application/json` schema from an operation's `responses` block and
13
17
  * converts it. Returns `undefined` (with warnings pushed to the context) when the operation
@@ -41,14 +45,17 @@ export function toOutputJsonSchema(schema) {
41
45
  case 'string':
42
46
  return withNullable({ type: 'string', ...(schema.enum !== undefined ? { enum: [...schema.enum] } : {}) }, schema.nullable);
43
47
  case 'number':
44
- return withNullable({ type: 'number' }, schema.nullable);
48
+ return withNullable({ type: schema.integer === true ? 'integer' : 'number' }, schema.nullable);
45
49
  case 'boolean':
46
50
  return withNullable({ type: 'boolean' }, schema.nullable);
47
51
  case 'array':
48
52
  return withNullable({ type: 'array', items: toOutputJsonSchema(schema.items) }, schema.nullable);
49
53
  case 'object': {
54
+ const extra = schema.additionalProperties === undefined
55
+ ? {}
56
+ : { additionalProperties: schema.additionalProperties };
50
57
  if (schema.properties.length === 0)
51
- return withNullable({ type: 'object' }, schema.nullable);
58
+ return withNullable({ type: 'object', ...extra }, schema.nullable);
52
59
  const required = schema.properties
53
60
  .filter((property) => property.required)
54
61
  .map((property) => property.name);
@@ -59,6 +66,7 @@ export function toOutputJsonSchema(schema) {
59
66
  toOutputJsonSchema(property.schema),
60
67
  ])),
61
68
  ...(required.length > 0 ? { required } : {}),
69
+ ...extra,
62
70
  }, schema.nullable);
63
71
  }
64
72
  case 'unknown':
@@ -101,7 +109,11 @@ function convertSchema(raw, ctx, state, depth, visitedRefs) {
101
109
  }
102
110
  case 'number':
103
111
  case 'integer':
104
- return { kind: 'number', ...nullableSpread };
112
+ return {
113
+ kind: 'number',
114
+ ...nullableSpread,
115
+ ...(state.request && type === 'integer' ? { integer: true } : {}),
116
+ };
105
117
  case 'boolean':
106
118
  return { kind: 'boolean', ...nullableSpread };
107
119
  case 'array':
@@ -120,6 +132,7 @@ function convertSchema(raw, ctx, state, depth, visitedRefs) {
120
132
  return {
121
133
  kind: 'object',
122
134
  ...nullableSpread,
135
+ ...(state.request ? { additionalProperties: raw.additionalProperties !== false } : {}),
123
136
  properties: Object.entries(properties).map(([name, propertySchema]) => ({
124
137
  name,
125
138
  required: required.has(name),
@@ -143,7 +156,7 @@ function convertRef(ref, ctx, state, depth, visitedRefs) {
143
156
  ctx.warnings.push(`${ctx.operationName}: response schema $ref "${ref}" is not a component schema reference; that part is left untyped`);
144
157
  return UNKNOWN;
145
158
  }
146
- const name = ref.slice(prefix.length);
159
+ const name = ref.slice(prefix.length).replace(/~1/g, '/').replace(/~0/g, '~');
147
160
  if (visitedRefs.has(name)) {
148
161
  ctx.warnings.push(`${ctx.operationName}: response schema $ref "${ref}" is circular; the repeated part is left untyped`);
149
162
  return UNKNOWN;
@@ -1,4 +1,4 @@
1
- import { mcpSubdomainEndpointOptions } from '@noodle-borg/control-plane/portable';
1
+ import { mcpSubdomainEndpointOptions, PersonalWorkspaceOwnerMutationError, } from '@noodle-borg/control-plane/portable';
2
2
  import { normalizePublicBaseDomain, OPENAI_APPS_CHALLENGE_PATH } from '@noodle-borg/module';
3
3
  import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
4
4
  import { OrgSummarySchema } from '@noodle-borg/wire-contracts';
@@ -142,7 +142,7 @@ export async function handleMembers(req, res, gate, controlPlane, maxBody, ref,
142
142
  return sendJson(res, 201, { ok: true, member });
143
143
  }
144
144
  catch (error) {
145
- return sendJson(res, 400, { error: error.message });
145
+ return sendMemberMutationError(res, error);
146
146
  }
147
147
  }
148
148
  if (req.method === 'PATCH' && ref.subject !== undefined) {
@@ -177,7 +177,7 @@ export async function handleMembers(req, res, gate, controlPlane, maxBody, ref,
177
177
  return sendJson(res, 200, { ok: true, member });
178
178
  }
179
179
  catch (error) {
180
- return sendJson(res, 400, { error: error.message });
180
+ return sendMemberMutationError(res, error);
181
181
  }
182
182
  }
183
183
  if (req.method === 'DELETE' && ref.subject !== undefined) {
@@ -186,18 +186,28 @@ export async function handleMembers(req, res, gate, controlPlane, maxBody, ref,
186
186
  if (await demotesLastOwner(controlPlane, ref.org, ref.subject, undefined)) {
187
187
  return sendJson(res, 409, { error: 'cannot remove the last owner' });
188
188
  }
189
- const removed = await controlPlane.removeOrgMember({ org: ref.org, subject: ref.subject });
190
- if (removed) {
191
- await emitOrgAudit(audit, 'org.member.removed', ref.org, identity, 204, {
192
- subject: ref.subject,
193
- });
189
+ try {
190
+ const removed = await controlPlane.removeOrgMember({ org: ref.org, subject: ref.subject });
191
+ if (removed) {
192
+ await emitOrgAudit(audit, 'org.member.removed', ref.org, identity, 204, {
193
+ subject: ref.subject,
194
+ });
195
+ }
196
+ res.writeHead(204);
197
+ res.end();
198
+ return;
199
+ }
200
+ catch (error) {
201
+ return sendMemberMutationError(res, error);
194
202
  }
195
- res.writeHead(204);
196
- res.end();
197
- return;
198
203
  }
199
204
  return sendJson(res, 404, { error: 'not found' });
200
205
  }
206
+ function sendMemberMutationError(res, error) {
207
+ sendJson(res, error instanceof PersonalWorkspaceOwnerMutationError ? 409 : 400, {
208
+ error: error.message,
209
+ });
210
+ }
201
211
  export async function handleInvitations(req, res, gate, controlPlane, maxBody, ref, audit, url, invitationEmail = {}, now = () => new Date()) {
202
212
  const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: true });
203
213
  if (identity === false)
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "^1.29.0",
41
41
  "@noodle-borg/admission-limits": "0.0.0",
42
- "@noodle-borg/agent-kit": "0.95.0",
42
+ "@noodle-borg/agent-kit": "0.97.0",
43
43
  "@noodle-borg/app-package": "0.0.0",
44
44
  "@noodle-borg/assistant-gateway": "0.0.0",
45
45
  "@noodle-borg/auth": "0.0.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/one",
3
- "version": "0.158.0",
3
+ "version": "0.160.0",
4
4
  "private": false,
5
5
  "description": "Noodle CLI by Noodle Seed — author, run, and deploy declarative MCP servers. Embedding the assistant in your own web app is @noodleseed/assistant.",
6
6
  "license": "Apache-2.0",
@@ -235,7 +235,7 @@
235
235
  "@modelcontextprotocol/client": "2.0.0",
236
236
  "@modelcontextprotocol/server": "2.0.0",
237
237
  "@noodle-borg/admission-limits": "0.0.0",
238
- "@noodle-borg/agent-kit": "0.95.0",
238
+ "@noodle-borg/agent-kit": "0.97.0",
239
239
  "@noodle-borg/app-audit": "0.0.0",
240
240
  "@noodle-borg/app-package": "0.0.0",
241
241
  "@noodle-borg/assistant-gateway": "0.0.0",