@vectros-ai/cli 0.26.0 → 0.27.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/dist/cli.js CHANGED
@@ -521,8 +521,8 @@ var require_graceful_fs = __commonJS({
521
521
  fs5.createReadStream = createReadStream3;
522
522
  fs5.createWriteStream = createWriteStream;
523
523
  var fs$readFile = fs5.readFile;
524
- fs5.readFile = readFile4;
525
- function readFile4(path4, options, cb) {
524
+ fs5.readFile = readFile5;
525
+ function readFile5(path4, options, cb) {
526
526
  if (typeof options === "function")
527
527
  cb = options, options = null;
528
528
  return go$readFile(path4, options, cb);
@@ -1614,7 +1614,7 @@ var require_proper_lockfile = __commonJS({
1614
1614
  // src/cli-bootstrap.ts
1615
1615
  var readline2 = __toESM(require("readline/promises"));
1616
1616
 
1617
- // ../../node_modules/@vectros-ai/sdk/dist/index.mjs
1617
+ // node_modules/@vectros-ai/sdk/dist/index.mjs
1618
1618
  var __defProp2 = Object.defineProperty;
1619
1619
  var __export = (target, all) => {
1620
1620
  for (var name in all)
@@ -1635,6 +1635,7 @@ __export(api_exports, {
1635
1635
  ContentTooLargeError: () => ContentTooLargeError,
1636
1636
  DocumentAskRequest: () => DocumentAskRequest,
1637
1637
  DocumentContextEvent: () => DocumentContextEvent,
1638
+ DocumentExtraction: () => DocumentExtraction,
1638
1639
  DocumentLookupRequest: () => DocumentLookupRequest,
1639
1640
  DocumentPatchRequest: () => DocumentPatchRequest,
1640
1641
  DocumentRequest: () => DocumentRequest,
@@ -1687,6 +1688,7 @@ __export(api_exports, {
1687
1688
  UserExistsResponse: () => UserExistsResponse,
1688
1689
  UserRequest: () => UserRequest,
1689
1690
  UserResponse: () => UserResponse,
1691
+ agents: () => agents_exports,
1690
1692
  auth: () => auth_exports,
1691
1693
  compliance: () => compliance_exports,
1692
1694
  documents: () => documents_exports,
@@ -1952,6 +1954,7 @@ var UnprocessableEntityError = class extends VectrosError {
1952
1954
  this.name = this.constructor.name;
1953
1955
  }
1954
1956
  };
1957
+ var agents_exports = {};
1955
1958
  var auth_exports = {};
1956
1959
  var ErasureRequest;
1957
1960
  ((ErasureRequest2) => {
@@ -2156,6 +2159,18 @@ var DocumentContextEvent;
2156
2159
  DocumentContext: "document_context"
2157
2160
  };
2158
2161
  })(DocumentContextEvent || (DocumentContextEvent = {}));
2162
+ var DocumentExtraction;
2163
+ ((DocumentExtraction2) => {
2164
+ DocumentExtraction2.Outcome = {
2165
+ Ok: "OK",
2166
+ Truncated: "TRUNCATED",
2167
+ NoText: "NO_TEXT",
2168
+ Encrypted: "ENCRYPTED",
2169
+ Unsupported: "UNSUPPORTED",
2170
+ TooLarge: "TOO_LARGE",
2171
+ Unreadable: "UNREADABLE"
2172
+ };
2173
+ })(DocumentExtraction || (DocumentExtraction = {}));
2159
2174
  var DocumentRequest;
2160
2175
  ((DocumentRequest2) => {
2161
2176
  DocumentRequest2.IndexMode = {
@@ -4024,24 +4039,32 @@ function handleNonStatusCodeError(error, rawResponse, method, path4) {
4024
4039
  });
4025
4040
  }
4026
4041
  }
4027
- var AuthClient = class {
4042
+ var AgentsClient = class {
4028
4043
  constructor(options) {
4029
4044
  this._options = normalizeClientOptionsWithAuth(options);
4030
4045
  }
4031
4046
  /**
4032
- * Returns the platform's JWT signing public key in RFC 7517 JWKS format. Use it with any JWKS-aware JWT library to verify `inv_*` invite tokens, `st_*` scoped tokens, and other platform-signed tokens locally, without calling back to the API for each verification. The response carries a one-hour `Cache-Control`, so cache it and re-fetch roughly hourly rather than on every verification. The `kid` value changes when the key rotates; re-fetch this document whenever you encounter a token signed with an unknown `kid`.
4047
+ * Returns a page of the agents in the app context, newest first, each at its latest version. A caller holding `runs:c` but not `agents:r` gets only each agent's `id`, `name`, `description`, `latestVersion` and `status`. Retired and purged agents are left out unless `includeRetired=true`. Requires `agents:r` or `runs:c`.
4033
4048
  *
4034
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4049
+ * @param {Vectros.ListAgentsRequest} request
4050
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4035
4051
  *
4036
- * @throws {@link Vectros.InternalServerError}
4052
+ * @throws {@link Vectros.ForbiddenError}
4037
4053
  *
4038
4054
  * @example
4039
- * await client.auth.getJwks()
4055
+ * await client.agents.listAgents()
4040
4056
  */
4041
- getJwks(requestOptions) {
4042
- return HttpResponsePromise.fromPromise(this.__getJwks(requestOptions));
4057
+ listAgents(request = {}, requestOptions) {
4058
+ return HttpResponsePromise.fromPromise(this.__listAgents(request, requestOptions));
4043
4059
  }
4044
- async __getJwks(requestOptions) {
4060
+ async __listAgents(request = {}, requestOptions) {
4061
+ const { contextId, includeRetired, startFrom, limit } = request;
4062
+ const _queryParams = {
4063
+ contextId,
4064
+ includeRetired,
4065
+ startFrom,
4066
+ limit
4067
+ };
4045
4068
  const _authRequest = await this._options.authProvider.getAuthRequest();
4046
4069
  const _headers = mergeHeaders(
4047
4070
  _authRequest.headers,
@@ -4051,11 +4074,11 @@ var AuthClient = class {
4051
4074
  const _response = await fetcher({
4052
4075
  url: url_exports.join(
4053
4076
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4054
- "v1/auth/jwks"
4077
+ "v1/agents"
4055
4078
  ),
4056
4079
  method: "GET",
4057
4080
  headers: _headers,
4058
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4081
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4059
4082
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4060
4083
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4061
4084
  abortSignal: requestOptions?.abortSignal,
@@ -4067,8 +4090,8 @@ var AuthClient = class {
4067
4090
  }
4068
4091
  if (_response.error.reason === "status-code") {
4069
4092
  switch (_response.error.statusCode) {
4070
- case 500:
4071
- throw new InternalServerError(_response.error.body, _response.rawResponse);
4093
+ case 403:
4094
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4072
4095
  default:
4073
4096
  throw new VectrosError({
4074
4097
  statusCode: _response.error.statusCode,
@@ -4077,59 +4100,34 @@ var AuthClient = class {
4077
4100
  });
4078
4101
  }
4079
4102
  }
4080
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/jwks");
4103
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/agents");
4081
4104
  }
4082
4105
  /**
4083
- * Returns a page of per-subject PHI read-access rows: who read which subject's PHI, when, against which record, and whether any sensitive value was actually revealed in plaintext. Metadata only — never the PHI itself. Rows exist only where read-access logging is enabled. Provide at least one query axis: a subject (`subjectType` + `subjectId`) within a `contextId` for the primary accounting query; `resourceId` within a `contextId` for 'who read this record'; `callerKeyId` for 'what did this credential read' (account-wide forensic); or `contextId` alone to enumerate a whole context. `from`/`to` bound the time window. Results are scoped to your account, derived from your token — never from input. Requires the `access-log:r` scope.
4106
+ * Stores a new agent at version 1, or returns the existing agent of this name unchanged. With `?upsert=true` an existing agent whose definition differs gets a new version instead. Requires the `agents:c` scope (also `agents:u` with `?upsert=true`).
4084
4107
  *
4085
- * @param {Vectros.GetAccessLogRequest} request
4086
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4108
+ * @param {Vectros.CreateAgentRequest} request
4109
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4087
4110
  *
4088
4111
  * @throws {@link Vectros.BadRequestError}
4089
4112
  * @throws {@link Vectros.ForbiddenError}
4113
+ * @throws {@link Vectros.ConflictError}
4114
+ * @throws {@link Vectros.TooManyRequestsError}
4090
4115
  *
4091
4116
  * @example
4092
- * await client.auth.getAccessLog({
4093
- * subjectType: "user",
4094
- * subjectId: "user_abc123",
4095
- * contextId: "ctx_intake",
4096
- * action: "read",
4097
- * callerKeyId: "key_abc123",
4098
- * resourceType: "intake_form",
4099
- * resourceId: "rec_456"
4117
+ * await client.agents.createAgent({
4118
+ * body: {
4119
+ * model: "claude-sonnet-5-5"
4120
+ * }
4100
4121
  * })
4101
4122
  */
4102
- getAccessLog(request = {}, requestOptions) {
4103
- return HttpResponsePromise.fromPromise(this.__getAccessLog(request, requestOptions));
4123
+ createAgent(request, requestOptions) {
4124
+ return HttpResponsePromise.fromPromise(this.__createAgent(request, requestOptions));
4104
4125
  }
4105
- async __getAccessLog(request = {}, requestOptions) {
4106
- const {
4107
- subjectType,
4108
- subjectId,
4109
- contextId,
4110
- from: from_,
4111
- to,
4112
- action,
4113
- callerKeyId,
4114
- resourceType,
4115
- resourceId,
4116
- revealedSensitive,
4117
- startFrom,
4118
- limit
4119
- } = request;
4126
+ async __createAgent(request, requestOptions) {
4127
+ const { upsert, contextId, body: _body } = request;
4120
4128
  const _queryParams = {
4121
- subjectType,
4122
- subjectId,
4123
- contextId,
4124
- from: from_,
4125
- to,
4126
- action,
4127
- callerKeyId,
4128
- resourceType,
4129
- resourceId,
4130
- revealedSensitive,
4131
- startFrom,
4132
- limit
4129
+ upsert,
4130
+ contextId
4133
4131
  };
4134
4132
  const _authRequest = await this._options.authProvider.getAuthRequest();
4135
4133
  const _headers = mergeHeaders(
@@ -4140,11 +4138,14 @@ var AuthClient = class {
4140
4138
  const _response = await fetcher({
4141
4139
  url: url_exports.join(
4142
4140
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4143
- "v1/admin/access-log"
4141
+ "v1/agents"
4144
4142
  ),
4145
- method: "GET",
4143
+ method: "POST",
4146
4144
  headers: _headers,
4145
+ contentType: "application/json",
4147
4146
  queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4147
+ requestType: "json",
4148
+ body: _body,
4148
4149
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4149
4150
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4150
4151
  abortSignal: requestOptions?.abortSignal,
@@ -4160,6 +4161,10 @@ var AuthClient = class {
4160
4161
  throw new BadRequestError(_response.error.body, _response.rawResponse);
4161
4162
  case 403:
4162
4163
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4164
+ case 409:
4165
+ throw new ConflictError(_response.error.body, _response.rawResponse);
4166
+ case 429:
4167
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4163
4168
  default:
4164
4169
  throw new VectrosError({
4165
4170
  statusCode: _response.error.statusCode,
@@ -4168,22 +4173,31 @@ var AuthClient = class {
4168
4173
  });
4169
4174
  }
4170
4175
  }
4171
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/access-log");
4176
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/agents");
4172
4177
  }
4173
4178
  /**
4174
- * Lists your scoped API keys (`ssk_*`) in your credential's own environment — a live key lists live keys, a test key lists test keys. Revoked keys are excluded. Requires the `keys:r` scope.
4179
+ * Returns the agent at its latest version, or at `version`. Requires the `agents:r` scope.
4175
4180
  *
4176
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4181
+ * @param {Vectros.GetAgentRequest} request
4182
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4177
4183
  *
4178
4184
  * @throws {@link Vectros.ForbiddenError}
4185
+ * @throws {@link Vectros.NotFoundError}
4179
4186
  *
4180
4187
  * @example
4181
- * await client.auth.listScopedKeys()
4188
+ * await client.agents.getAgent({
4189
+ * id: "id"
4190
+ * })
4182
4191
  */
4183
- listScopedKeys(requestOptions) {
4184
- return HttpResponsePromise.fromPromise(this.__listScopedKeys(requestOptions));
4192
+ getAgent(request, requestOptions) {
4193
+ return HttpResponsePromise.fromPromise(this.__getAgent(request, requestOptions));
4185
4194
  }
4186
- async __listScopedKeys(requestOptions) {
4195
+ async __getAgent(request, requestOptions) {
4196
+ const { id, contextId, version } = request;
4197
+ const _queryParams = {
4198
+ contextId,
4199
+ version
4200
+ };
4187
4201
  const _authRequest = await this._options.authProvider.getAuthRequest();
4188
4202
  const _headers = mergeHeaders(
4189
4203
  _authRequest.headers,
@@ -4193,11 +4207,11 @@ var AuthClient = class {
4193
4207
  const _response = await fetcher({
4194
4208
  url: url_exports.join(
4195
4209
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4196
- "v1/admin/keys/scoped"
4210
+ `v1/agents/${url_exports.encodePathParam(id)}`
4197
4211
  ),
4198
4212
  method: "GET",
4199
4213
  headers: _headers,
4200
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4214
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4201
4215
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4202
4216
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4203
4217
  abortSignal: requestOptions?.abortSignal,
@@ -4211,6 +4225,8 @@ var AuthClient = class {
4211
4225
  switch (_response.error.statusCode) {
4212
4226
  case 403:
4213
4227
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4228
+ case 404:
4229
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4214
4230
  default:
4215
4231
  throw new VectrosError({
4216
4232
  statusCode: _response.error.statusCode,
@@ -4219,13 +4235,13 @@ var AuthClient = class {
4219
4235
  });
4220
4236
  }
4221
4237
  }
4222
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped");
4238
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/agents/{id}");
4223
4239
  }
4224
4240
  /**
4225
- * Creates a scoped API key (an `ssk_*` secret) that inherits its permissions from an existing access profile in your account. The call is idempotent on the combination of tenant, context, user, and key name: re-issuing the same request returns the existing key WITHOUT re-disclosing its raw secret. The raw key is returned ONLY in this response — store it securely, as it cannot be retrieved again. Requires the `keys:c` scope. If you use a scoped credential, `keys:c` alone is not sufficient: because the minted key is durably bound to the profile you name, the profile's effective scopes may not exceed your own, and you may only mint against a profile whose `identityOverrides` values your own identity holds. Minting a key bound to your OWN principal needs nothing further; minting one bound to a DIFFERENT principal additionally requires the `delegate-mint` capability (`granted_capabilities`) on your credential — without it the request is refused. A root API key (`sk_`) is exempt from all three bounds. The profile you name must be `active` AND the user you name must not be `SUSPENDED`: either is refused with `409`, and no root exemption applies to that. Both refusals stop ISSUANCE only. Suspending the profile or the user additionally stops credentials already bound to it, within about five minutes. A `PENDING` user is fine.
4241
+ * Stores the body as the agent's next version. Earlier versions never change. Every optional field you leave out takes its default; `model` is required. With `?restore=true` a retired agent becomes active again with this version. Requires the `agents:u` scope.
4226
4242
  *
4227
- * @param {Vectros.CreateScopedKeyRequest} request
4228
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4243
+ * @param {Vectros.UpdateAgentRequest} request
4244
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4229
4245
  *
4230
4246
  * @throws {@link Vectros.BadRequestError}
4231
4247
  * @throws {@link Vectros.ForbiddenError}
@@ -4234,17 +4250,22 @@ var AuthClient = class {
4234
4250
  * @throws {@link Vectros.TooManyRequestsError}
4235
4251
  *
4236
4252
  * @example
4237
- * await client.auth.createScopedKey({
4238
- * keyName: "research-bot production",
4239
- * tenantId: "ten_live_xxx",
4240
- * contextId: "myapp",
4241
- * userId: "alice-001"
4253
+ * await client.agents.updateAgent({
4254
+ * id: "id",
4255
+ * body: {
4256
+ * model: "claude-sonnet-5-5"
4257
+ * }
4242
4258
  * })
4243
4259
  */
4244
- createScopedKey(request, requestOptions) {
4245
- return HttpResponsePromise.fromPromise(this.__createScopedKey(request, requestOptions));
4260
+ updateAgent(request, requestOptions) {
4261
+ return HttpResponsePromise.fromPromise(this.__updateAgent(request, requestOptions));
4246
4262
  }
4247
- async __createScopedKey(request, requestOptions) {
4263
+ async __updateAgent(request, requestOptions) {
4264
+ const { id, restore, contextId, body: _body } = request;
4265
+ const _queryParams = {
4266
+ restore,
4267
+ contextId
4268
+ };
4248
4269
  const _authRequest = await this._options.authProvider.getAuthRequest();
4249
4270
  const _headers = mergeHeaders(
4250
4271
  _authRequest.headers,
@@ -4254,14 +4275,14 @@ var AuthClient = class {
4254
4275
  const _response = await fetcher({
4255
4276
  url: url_exports.join(
4256
4277
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4257
- "v1/admin/keys/scoped"
4278
+ `v1/agents/${url_exports.encodePathParam(id)}`
4258
4279
  ),
4259
- method: "POST",
4280
+ method: "PUT",
4260
4281
  headers: _headers,
4261
4282
  contentType: "application/json",
4262
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4283
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4263
4284
  requestType: "json",
4264
- body: request,
4285
+ body: _body,
4265
4286
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4266
4287
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4267
4288
  abortSignal: requestOptions?.abortSignal,
@@ -4291,27 +4312,35 @@ var AuthClient = class {
4291
4312
  });
4292
4313
  }
4293
4314
  }
4294
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/admin/keys/scoped");
4315
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/agents/{id}");
4295
4316
  }
4296
4317
  /**
4297
- * Returns the metadata for a single scoped API key. The raw secret is NOT included — it is only ever returned once, when the key is first created. Requires the `keys:r` scope.
4318
+ * Retires the agent: its versions stay readable, it leaves the default listing, and it can be restored. With `?purge=true` a retired agent's content (every version's prompt, description, tools, model, limits and budget) is erased, leaving its id, name, version numbers and timestamps; its change history keeps only a one-way hash of the prompt, description and tools. If the agent is restored while a purge is running, the purge stops (409) and versions it already erased stay erased; purge it again after retiring it. With `?purge=true&version=N` one version that is not the latest is erased. The name of a purged agent stays reserved. Requires the `agents:d` scope.
4298
4319
  *
4299
- * @param {Vectros.GetScopedKeyRequest} request
4300
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4320
+ * @param {Vectros.DeleteAgentRequest} request
4321
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4301
4322
  *
4323
+ * @throws {@link Vectros.BadRequestError}
4302
4324
  * @throws {@link Vectros.ForbiddenError}
4303
4325
  * @throws {@link Vectros.NotFoundError}
4326
+ * @throws {@link Vectros.ConflictError}
4327
+ * @throws {@link Vectros.TooManyRequestsError}
4304
4328
  *
4305
4329
  * @example
4306
- * await client.auth.getScopedKey({
4307
- * keyId: "keyId"
4330
+ * await client.agents.deleteAgent({
4331
+ * id: "id"
4308
4332
  * })
4309
4333
  */
4310
- getScopedKey(request, requestOptions) {
4311
- return HttpResponsePromise.fromPromise(this.__getScopedKey(request, requestOptions));
4334
+ deleteAgent(request, requestOptions) {
4335
+ return HttpResponsePromise.fromPromise(this.__deleteAgent(request, requestOptions));
4312
4336
  }
4313
- async __getScopedKey(request, requestOptions) {
4314
- const { keyId } = request;
4337
+ async __deleteAgent(request, requestOptions) {
4338
+ const { id, purge, version, contextId } = request;
4339
+ const _queryParams = {
4340
+ purge,
4341
+ version,
4342
+ contextId
4343
+ };
4315
4344
  const _authRequest = await this._options.authProvider.getAuthRequest();
4316
4345
  const _headers = mergeHeaders(
4317
4346
  _authRequest.headers,
@@ -4321,11 +4350,11 @@ var AuthClient = class {
4321
4350
  const _response = await fetcher({
4322
4351
  url: url_exports.join(
4323
4352
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4324
- `v1/admin/keys/scoped/${url_exports.encodePathParam(keyId)}`
4353
+ `v1/agents/${url_exports.encodePathParam(id)}`
4325
4354
  ),
4326
- method: "GET",
4355
+ method: "DELETE",
4327
4356
  headers: _headers,
4328
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4357
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4329
4358
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4330
4359
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4331
4360
  abortSignal: requestOptions?.abortSignal,
@@ -4337,10 +4366,16 @@ var AuthClient = class {
4337
4366
  }
4338
4367
  if (_response.error.reason === "status-code") {
4339
4368
  switch (_response.error.statusCode) {
4369
+ case 400:
4370
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4340
4371
  case 403:
4341
4372
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4342
4373
  case 404:
4343
4374
  throw new NotFoundError(_response.error.body, _response.rawResponse);
4375
+ case 409:
4376
+ throw new ConflictError(_response.error.body, _response.rawResponse);
4377
+ case 429:
4378
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4344
4379
  default:
4345
4380
  throw new VectrosError({
4346
4381
  statusCode: _response.error.statusCode,
@@ -4349,28 +4384,33 @@ var AuthClient = class {
4349
4384
  });
4350
4385
  }
4351
4386
  }
4352
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped/{keyId}");
4387
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/v1/agents/{id}");
4353
4388
  }
4354
4389
  /**
4355
- * Revokes a scoped API key. Its status changes to `revoked` and it stops working within about 5 minutes, the maximum time authorization is cached. Revocation is permanent. Requires the `keys:d` scope.
4390
+ * Returns a page of the agent's versions, newest first: who wrote each and when, and whether it was purged. With `includeContent=true` each entry carries its full definition. Requires the `agents:r` scope.
4356
4391
  *
4357
- * @param {Vectros.RevokeScopedKeyRequest} request
4358
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4392
+ * @param {Vectros.ListAgentVersionsRequest} request
4393
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4359
4394
  *
4360
4395
  * @throws {@link Vectros.ForbiddenError}
4361
4396
  * @throws {@link Vectros.NotFoundError}
4362
- * @throws {@link Vectros.TooManyRequestsError}
4363
4397
  *
4364
4398
  * @example
4365
- * await client.auth.revokeScopedKey({
4366
- * keyId: "keyId"
4399
+ * await client.agents.listAgentVersions({
4400
+ * id: "id"
4367
4401
  * })
4368
4402
  */
4369
- revokeScopedKey(request, requestOptions) {
4370
- return HttpResponsePromise.fromPromise(this.__revokeScopedKey(request, requestOptions));
4403
+ listAgentVersions(request, requestOptions) {
4404
+ return HttpResponsePromise.fromPromise(this.__listAgentVersions(request, requestOptions));
4371
4405
  }
4372
- async __revokeScopedKey(request, requestOptions) {
4373
- const { keyId } = request;
4406
+ async __listAgentVersions(request, requestOptions) {
4407
+ const { id, contextId, includeContent, startFrom, limit } = request;
4408
+ const _queryParams = {
4409
+ contextId,
4410
+ includeContent,
4411
+ startFrom,
4412
+ limit
4413
+ };
4374
4414
  const _authRequest = await this._options.authProvider.getAuthRequest();
4375
4415
  const _headers = mergeHeaders(
4376
4416
  _authRequest.headers,
@@ -4380,11 +4420,11 @@ var AuthClient = class {
4380
4420
  const _response = await fetcher({
4381
4421
  url: url_exports.join(
4382
4422
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4383
- `v1/admin/keys/scoped/${url_exports.encodePathParam(keyId)}`
4423
+ `v1/agents/${url_exports.encodePathParam(id)}/versions`
4384
4424
  ),
4385
- method: "DELETE",
4425
+ method: "GET",
4386
4426
  headers: _headers,
4387
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4427
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4388
4428
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4389
4429
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4390
4430
  abortSignal: requestOptions?.abortSignal,
@@ -4392,7 +4432,7 @@ var AuthClient = class {
4392
4432
  logging: this._options.logging
4393
4433
  });
4394
4434
  if (_response.ok) {
4395
- return { data: void 0, rawResponse: _response.rawResponse };
4435
+ return { data: _response.body, rawResponse: _response.rawResponse };
4396
4436
  }
4397
4437
  if (_response.error.reason === "status-code") {
4398
4438
  switch (_response.error.statusCode) {
@@ -4400,8 +4440,6 @@ var AuthClient = class {
4400
4440
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4401
4441
  case 404:
4402
4442
  throw new NotFoundError(_response.error.body, _response.rawResponse);
4403
- case 429:
4404
- throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4405
4443
  default:
4406
4444
  throw new VectrosError({
4407
4445
  statusCode: _response.error.statusCode,
@@ -4410,45 +4448,32 @@ var AuthClient = class {
4410
4448
  });
4411
4449
  }
4412
4450
  }
4413
- return handleNonStatusCodeError(
4414
- _response.error,
4415
- _response.rawResponse,
4416
- "DELETE",
4417
- "/v1/admin/keys/scoped/{keyId}"
4418
- );
4451
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/agents/{id}/versions");
4419
4452
  }
4420
4453
  /**
4421
- * Returns recent API call logs for your account. Each entry represents one API request; request and response bodies are never logged. `startTime` and `endTime` must be ISO-8601 UTC (e.g. `2025-01-15T09:00:00Z`); `endTime` defaults to now. Filter by resource, method, key id, or context id, or set `errorsOnly` to see only failures. Results are scoped to your account, derived from your token — never from input. Requires the `logs:r` scope.
4454
+ * Stops a run. The answer is `202` with the run still `RUNNING`: read it with `GET /v1/runs/{id}` to see it end. A model call in progress is cut within a few seconds; a tool call in progress is first allowed to finish, for up to about 30 seconds, so its change is recorded and billed, and the run stops as soon as it has. The run then ends `CANCELLED` with `errorCode` `CANCELLED_BY_CALLER`, unless it had already completed, in which case it stays `COMPLETED`. Its reservation is settled once, like any run's: a model call in progress when it stopped is charged up to its maximum. A run that has ended (or that had not started, which this cancel ends) answers `200` with its final state, so a cancel can always be retried. A run can be cancelled by the credential that started it (for a token bound to a user, by that user) and by your root key. Requires the `runs:c` scope; with `runs:r` as well the answer is the whole run, otherwise only its `id`, `status`, `terminal`, `reason` and `errorCode`. Takes no body.
4422
4455
  *
4423
- * @param {Vectros.GetAdminLogsRequest} request
4424
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4456
+ * @param {Vectros.CancelRunRequest} request
4457
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4425
4458
  *
4426
4459
  * @throws {@link Vectros.BadRequestError}
4460
+ * @throws {@link Vectros.UnauthorizedError}
4427
4461
  * @throws {@link Vectros.ForbiddenError}
4462
+ * @throws {@link Vectros.NotFoundError}
4463
+ * @throws {@link Vectros.TooManyRequestsError}
4428
4464
  *
4429
4465
  * @example
4430
- * await client.auth.getAdminLogs({
4431
- * startTime: "startTime",
4432
- * resource: "documents",
4433
- * method: "POST",
4434
- * keyId: "key_abc123",
4435
- * contextId: "ctx_intake"
4466
+ * await client.agents.cancelRun({
4467
+ * id: "id"
4436
4468
  * })
4437
4469
  */
4438
- getAdminLogs(request, requestOptions) {
4439
- return HttpResponsePromise.fromPromise(this.__getAdminLogs(request, requestOptions));
4470
+ cancelRun(request, requestOptions) {
4471
+ return HttpResponsePromise.fromPromise(this.__cancelRun(request, requestOptions));
4440
4472
  }
4441
- async __getAdminLogs(request, requestOptions) {
4442
- const { startTime, endTime, resource, method, keyId, contextId, errorsOnly, limit } = request;
4473
+ async __cancelRun(request, requestOptions) {
4474
+ const { id, contextId } = request;
4443
4475
  const _queryParams = {
4444
- startTime,
4445
- endTime,
4446
- resource,
4447
- method,
4448
- keyId,
4449
- contextId,
4450
- errorsOnly,
4451
- limit
4476
+ contextId
4452
4477
  };
4453
4478
  const _authRequest = await this._options.authProvider.getAuthRequest();
4454
4479
  const _headers = mergeHeaders(
@@ -4459,9 +4484,9 @@ var AuthClient = class {
4459
4484
  const _response = await fetcher({
4460
4485
  url: url_exports.join(
4461
4486
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4462
- "v1/admin/logs"
4487
+ `v1/runs/${url_exports.encodePathParam(id)}/cancel`
4463
4488
  ),
4464
- method: "GET",
4489
+ method: "POST",
4465
4490
  headers: _headers,
4466
4491
  queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4467
4492
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -4477,8 +4502,14 @@ var AuthClient = class {
4477
4502
  switch (_response.error.statusCode) {
4478
4503
  case 400:
4479
4504
  throw new BadRequestError(_response.error.body, _response.rawResponse);
4505
+ case 401:
4506
+ throw new UnauthorizedError(_response.error.body, _response.rawResponse);
4480
4507
  case 403:
4481
4508
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4509
+ case 404:
4510
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4511
+ case 429:
4512
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4482
4513
  default:
4483
4514
  throw new VectrosError({
4484
4515
  statusCode: _response.error.statusCode,
@@ -4487,30 +4518,31 @@ var AuthClient = class {
4487
4518
  });
4488
4519
  }
4489
4520
  }
4490
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/logs");
4521
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/runs/{id}/cancel");
4491
4522
  }
4492
4523
  /**
4493
- * Returns the access profiles assigned within the given app context — in effect, who has access to this context and with what scopes. Each profile binds a principal to either a set of inline scopes or a referenced role. Results are paginated. Requires the `profiles:r` scope.
4524
+ * Returns the status and metadata of one run: the agent version that ran, how it ended, its tool calls (never their arguments or results) and what it was charged. It never returns text: the answer is delivered only in the run's stream. A run is visible to the credential that started it (for a token bound to a user, to that user) and to your root key, and is kept for 30 days after it ends. Requires the `runs:r` scope.
4494
4525
  *
4495
- * @param {Vectros.ListAccessProfilesRequest} request
4496
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4526
+ * @param {Vectros.GetRunRequest} request
4527
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4497
4528
  *
4529
+ * @throws {@link Vectros.BadRequestError}
4530
+ * @throws {@link Vectros.UnauthorizedError}
4498
4531
  * @throws {@link Vectros.ForbiddenError}
4499
4532
  * @throws {@link Vectros.NotFoundError}
4500
4533
  *
4501
4534
  * @example
4502
- * await client.auth.listAccessProfiles({
4503
- * contextId: "myapp"
4535
+ * await client.agents.getRun({
4536
+ * id: "id"
4504
4537
  * })
4505
4538
  */
4506
- listAccessProfiles(request, requestOptions) {
4507
- return HttpResponsePromise.fromPromise(this.__listAccessProfiles(request, requestOptions));
4539
+ getRun(request, requestOptions) {
4540
+ return HttpResponsePromise.fromPromise(this.__getRun(request, requestOptions));
4508
4541
  }
4509
- async __listAccessProfiles(request, requestOptions) {
4510
- const { contextId, startFrom, limit } = request;
4542
+ async __getRun(request, requestOptions) {
4543
+ const { id, contextId } = request;
4511
4544
  const _queryParams = {
4512
- startFrom,
4513
- limit
4545
+ contextId
4514
4546
  };
4515
4547
  const _authRequest = await this._options.authProvider.getAuthRequest();
4516
4548
  const _headers = mergeHeaders(
@@ -4521,7 +4553,7 @@ var AuthClient = class {
4521
4553
  const _response = await fetcher({
4522
4554
  url: url_exports.join(
4523
4555
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4524
- `v1/app-contexts/${url_exports.encodePathParam(contextId)}/profiles`
4556
+ `v1/runs/${url_exports.encodePathParam(id)}`
4525
4557
  ),
4526
4558
  method: "GET",
4527
4559
  headers: _headers,
@@ -4537,6 +4569,10 @@ var AuthClient = class {
4537
4569
  }
4538
4570
  if (_response.error.reason === "status-code") {
4539
4571
  switch (_response.error.statusCode) {
4572
+ case 400:
4573
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4574
+ case 401:
4575
+ throw new UnauthorizedError(_response.error.body, _response.rawResponse);
4540
4576
  case 403:
4541
4577
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4542
4578
  case 404:
@@ -4549,31 +4585,635 @@ var AuthClient = class {
4549
4585
  });
4550
4586
  }
4551
4587
  }
4552
- return handleNonStatusCodeError(
4553
- _response.error,
4554
- _response.rawResponse,
4555
- "GET",
4556
- "/v1/app-contexts/{contextId}/profiles"
4557
- );
4588
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/runs/{id}");
4558
4589
  }
4559
4590
  /**
4560
- * Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing profile was returned) tells the two apart. To overwrite an existing profile's `scopes`/`roleId`, `identityOverrides`, and `status` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope, and applies the same `identityOverrides` bounds the update endpoint documents — a scoped credential may not repoint or clear an identity value it does not itself hold). The `principalId` must name a principal that already exists: a `usr_` principal must be a live user in your tenant, so create the user before granting it a profile. A `usr_` id that names no such user is rejected, and no profile is created. `key_` principals are not checked this way. You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` is keyed by ownership namespace in `scope:<namespace>` form — `scope:org` and `scope:client` for the reserved namespaces, or any namespace you have registered — and may name at most two; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
4561
- *
4562
- * @param {Vectros.CreateAccessProfileRequest} request
4563
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4564
- *
4565
- * @throws {@link Vectros.BadRequestError}
4566
- * @throws {@link Vectros.ForbiddenError}
4567
- * @throws {@link Vectros.NotFoundError}
4568
- * @throws {@link Vectros.ConflictError}
4569
- * @throws {@link Vectros.TooManyRequestsError}
4570
- *
4571
- * @example
4591
+ * Starts a run of a stored agent and streams what it does as Server-Sent Events. The response is always `text/event-stream`, whatever `Accept` you send; its `X-Vectros-Run-Id` header names the run. The run's tool calls act as you, with your own permissions, and only through the tools the agent lists. On a prepaid account the run reserves its budget from your inference balance when it starts and returns what its model calls did not use when it ends; a fixed fee per run, tool calls and run time are billed separately, against your plan. Read each event's `type`; ignore types and fields you do not know; stop at the event with `terminal: true`. A stream that ends without one: read the run with `GET /v1/runs/{id}`. Closing the stream stops the run only on a best-effort basis. Requires the `runs:c` scope, and agent runs switched on for your tenant by an account owner.
4592
+ */
4593
+ createRun(request, requestOptions) {
4594
+ return HttpResponsePromise.fromPromise(this.__createRun(request, requestOptions));
4595
+ }
4596
+ async __createRun(request, requestOptions) {
4597
+ const { "Idempotency-Key": idempotencyKey, ..._body } = request;
4598
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4599
+ const _headers = mergeHeaders(
4600
+ _authRequest.headers,
4601
+ this._options?.headers,
4602
+ mergeOnlyDefinedHeaders({ "Idempotency-Key": idempotencyKey }),
4603
+ requestOptions?.headers
4604
+ );
4605
+ const _response = await fetcher({
4606
+ url: url_exports.join(
4607
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4608
+ "v1/runs"
4609
+ ),
4610
+ method: "POST",
4611
+ headers: _headers,
4612
+ contentType: "application/json",
4613
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4614
+ requestType: "json",
4615
+ body: _body,
4616
+ responseType: "sse",
4617
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4618
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4619
+ abortSignal: requestOptions?.abortSignal,
4620
+ fetchFn: this._options?.fetch,
4621
+ logging: this._options.logging
4622
+ });
4623
+ if (_response.ok) {
4624
+ return {
4625
+ data: new Stream({
4626
+ stream: _response.body,
4627
+ parse: (data) => data,
4628
+ signal: requestOptions?.abortSignal,
4629
+ eventShape: {
4630
+ type: "sse"
4631
+ }
4632
+ }),
4633
+ rawResponse: _response.rawResponse
4634
+ };
4635
+ }
4636
+ if (_response.error.reason === "status-code") {
4637
+ switch (_response.error.statusCode) {
4638
+ case 400:
4639
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4640
+ case 401:
4641
+ throw new UnauthorizedError(_response.error.body, _response.rawResponse);
4642
+ case 402:
4643
+ throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
4644
+ case 403:
4645
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4646
+ case 404:
4647
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4648
+ case 409:
4649
+ throw new ConflictError(_response.error.body, _response.rawResponse);
4650
+ case 413:
4651
+ throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
4652
+ case 422:
4653
+ throw new UnprocessableEntityError(_response.error.body, _response.rawResponse);
4654
+ case 429:
4655
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4656
+ case 503:
4657
+ throw new ServiceUnavailableError(_response.error.body, _response.rawResponse);
4658
+ default:
4659
+ throw new VectrosError({
4660
+ statusCode: _response.error.statusCode,
4661
+ body: _response.error.body,
4662
+ rawResponse: _response.rawResponse
4663
+ });
4664
+ }
4665
+ }
4666
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/runs");
4667
+ }
4668
+ };
4669
+ var AuthClient = class {
4670
+ constructor(options) {
4671
+ this._options = normalizeClientOptionsWithAuth(options);
4672
+ }
4673
+ /**
4674
+ * Returns the platform's JWT signing public key in RFC 7517 JWKS format. Use it with any JWKS-aware JWT library to verify `inv_*` invite tokens, `st_*` scoped tokens, and other platform-signed tokens locally, without calling back to the API for each verification. The response carries a one-hour `Cache-Control`, so cache it and re-fetch roughly hourly rather than on every verification. The `kid` value changes when the key rotates; re-fetch this document whenever you encounter a token signed with an unknown `kid`.
4675
+ *
4676
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4677
+ *
4678
+ * @throws {@link Vectros.InternalServerError}
4679
+ *
4680
+ * @example
4681
+ * await client.auth.getJwks()
4682
+ */
4683
+ getJwks(requestOptions) {
4684
+ return HttpResponsePromise.fromPromise(this.__getJwks(requestOptions));
4685
+ }
4686
+ async __getJwks(requestOptions) {
4687
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4688
+ const _headers = mergeHeaders(
4689
+ _authRequest.headers,
4690
+ this._options?.headers,
4691
+ requestOptions?.headers
4692
+ );
4693
+ const _response = await fetcher({
4694
+ url: url_exports.join(
4695
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4696
+ "v1/auth/jwks"
4697
+ ),
4698
+ method: "GET",
4699
+ headers: _headers,
4700
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4701
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4702
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4703
+ abortSignal: requestOptions?.abortSignal,
4704
+ fetchFn: this._options?.fetch,
4705
+ logging: this._options.logging
4706
+ });
4707
+ if (_response.ok) {
4708
+ return { data: _response.body, rawResponse: _response.rawResponse };
4709
+ }
4710
+ if (_response.error.reason === "status-code") {
4711
+ switch (_response.error.statusCode) {
4712
+ case 500:
4713
+ throw new InternalServerError(_response.error.body, _response.rawResponse);
4714
+ default:
4715
+ throw new VectrosError({
4716
+ statusCode: _response.error.statusCode,
4717
+ body: _response.error.body,
4718
+ rawResponse: _response.rawResponse
4719
+ });
4720
+ }
4721
+ }
4722
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/jwks");
4723
+ }
4724
+ /**
4725
+ * Returns a page of per-subject PHI read-access rows: who read which subject's PHI, when, against which record, and whether any sensitive value was actually revealed in plaintext. Metadata only — never the PHI itself. Rows exist only where read-access logging is enabled. Provide at least one query axis: a subject (`subjectType` + `subjectId`) within a `contextId` for the primary accounting query; `resourceId` within a `contextId` for 'who read this record'; `callerKeyId` for 'what did this credential read' (account-wide forensic); or `contextId` alone to enumerate a whole context. `from`/`to` bound the time window. Results are scoped to your account, derived from your token — never from input. Requires the `access-log:r` scope.
4726
+ *
4727
+ * @param {Vectros.GetAccessLogRequest} request
4728
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4729
+ *
4730
+ * @throws {@link Vectros.BadRequestError}
4731
+ * @throws {@link Vectros.ForbiddenError}
4732
+ *
4733
+ * @example
4734
+ * await client.auth.getAccessLog({
4735
+ * subjectType: "user",
4736
+ * subjectId: "user_abc123",
4737
+ * contextId: "ctx_intake",
4738
+ * action: "read",
4739
+ * callerKeyId: "key_abc123",
4740
+ * resourceType: "intake_form",
4741
+ * resourceId: "rec_456"
4742
+ * })
4743
+ */
4744
+ getAccessLog(request = {}, requestOptions) {
4745
+ return HttpResponsePromise.fromPromise(this.__getAccessLog(request, requestOptions));
4746
+ }
4747
+ async __getAccessLog(request = {}, requestOptions) {
4748
+ const {
4749
+ subjectType,
4750
+ subjectId,
4751
+ contextId,
4752
+ from: from_,
4753
+ to,
4754
+ action,
4755
+ callerKeyId,
4756
+ resourceType,
4757
+ resourceId,
4758
+ revealedSensitive,
4759
+ startFrom,
4760
+ limit
4761
+ } = request;
4762
+ const _queryParams = {
4763
+ subjectType,
4764
+ subjectId,
4765
+ contextId,
4766
+ from: from_,
4767
+ to,
4768
+ action,
4769
+ callerKeyId,
4770
+ resourceType,
4771
+ resourceId,
4772
+ revealedSensitive,
4773
+ startFrom,
4774
+ limit
4775
+ };
4776
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4777
+ const _headers = mergeHeaders(
4778
+ _authRequest.headers,
4779
+ this._options?.headers,
4780
+ requestOptions?.headers
4781
+ );
4782
+ const _response = await fetcher({
4783
+ url: url_exports.join(
4784
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4785
+ "v1/admin/access-log"
4786
+ ),
4787
+ method: "GET",
4788
+ headers: _headers,
4789
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4790
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4791
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4792
+ abortSignal: requestOptions?.abortSignal,
4793
+ fetchFn: this._options?.fetch,
4794
+ logging: this._options.logging
4795
+ });
4796
+ if (_response.ok) {
4797
+ return { data: _response.body, rawResponse: _response.rawResponse };
4798
+ }
4799
+ if (_response.error.reason === "status-code") {
4800
+ switch (_response.error.statusCode) {
4801
+ case 400:
4802
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4803
+ case 403:
4804
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4805
+ default:
4806
+ throw new VectrosError({
4807
+ statusCode: _response.error.statusCode,
4808
+ body: _response.error.body,
4809
+ rawResponse: _response.rawResponse
4810
+ });
4811
+ }
4812
+ }
4813
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/access-log");
4814
+ }
4815
+ /**
4816
+ * Lists your scoped API keys (`ssk_*`) in your credential's own environment — a live key lists live keys, a test key lists test keys. Revoked keys are excluded. Requires the `keys:r` scope.
4817
+ *
4818
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4819
+ *
4820
+ * @throws {@link Vectros.ForbiddenError}
4821
+ *
4822
+ * @example
4823
+ * await client.auth.listScopedKeys()
4824
+ */
4825
+ listScopedKeys(requestOptions) {
4826
+ return HttpResponsePromise.fromPromise(this.__listScopedKeys(requestOptions));
4827
+ }
4828
+ async __listScopedKeys(requestOptions) {
4829
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4830
+ const _headers = mergeHeaders(
4831
+ _authRequest.headers,
4832
+ this._options?.headers,
4833
+ requestOptions?.headers
4834
+ );
4835
+ const _response = await fetcher({
4836
+ url: url_exports.join(
4837
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4838
+ "v1/admin/keys/scoped"
4839
+ ),
4840
+ method: "GET",
4841
+ headers: _headers,
4842
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4843
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4844
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4845
+ abortSignal: requestOptions?.abortSignal,
4846
+ fetchFn: this._options?.fetch,
4847
+ logging: this._options.logging
4848
+ });
4849
+ if (_response.ok) {
4850
+ return { data: _response.body, rawResponse: _response.rawResponse };
4851
+ }
4852
+ if (_response.error.reason === "status-code") {
4853
+ switch (_response.error.statusCode) {
4854
+ case 403:
4855
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4856
+ default:
4857
+ throw new VectrosError({
4858
+ statusCode: _response.error.statusCode,
4859
+ body: _response.error.body,
4860
+ rawResponse: _response.rawResponse
4861
+ });
4862
+ }
4863
+ }
4864
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped");
4865
+ }
4866
+ /**
4867
+ * Creates a scoped API key (an `ssk_*` secret) that inherits its permissions from an existing access profile in your account. The call is idempotent on the combination of tenant, context, user, and key name: re-issuing the same request returns the existing key WITHOUT re-disclosing its raw secret. The raw key is returned ONLY in this response — store it securely, as it cannot be retrieved again. Requires the `keys:c` scope. If you use a scoped credential, `keys:c` alone is not sufficient: because the minted key is durably bound to the profile you name, the profile's effective scopes may not exceed your own, and you may only mint against a profile whose `identityOverrides` values your own identity holds. Minting a key bound to your OWN principal needs nothing further; minting one bound to a DIFFERENT principal additionally requires the `delegate-mint` capability (`granted_capabilities`) on your credential — without it the request is refused. A root API key (`sk_`) is exempt from all three bounds. The profile you name must be `active` AND the user you name must not be `SUSPENDED`: either is refused with `409`, and no root exemption applies to that. Both refusals stop ISSUANCE only. Suspending the profile or the user additionally stops credentials already bound to it, within about five minutes. A `PENDING` user is fine.
4868
+ *
4869
+ * @param {Vectros.CreateScopedKeyRequest} request
4870
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4871
+ *
4872
+ * @throws {@link Vectros.BadRequestError}
4873
+ * @throws {@link Vectros.ForbiddenError}
4874
+ * @throws {@link Vectros.NotFoundError}
4875
+ * @throws {@link Vectros.ConflictError}
4876
+ * @throws {@link Vectros.TooManyRequestsError}
4877
+ *
4878
+ * @example
4879
+ * await client.auth.createScopedKey({
4880
+ * keyName: "research-bot production",
4881
+ * tenantId: "ten_live_xxx",
4882
+ * contextId: "myapp",
4883
+ * userId: "alice-001"
4884
+ * })
4885
+ */
4886
+ createScopedKey(request, requestOptions) {
4887
+ return HttpResponsePromise.fromPromise(this.__createScopedKey(request, requestOptions));
4888
+ }
4889
+ async __createScopedKey(request, requestOptions) {
4890
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4891
+ const _headers = mergeHeaders(
4892
+ _authRequest.headers,
4893
+ this._options?.headers,
4894
+ requestOptions?.headers
4895
+ );
4896
+ const _response = await fetcher({
4897
+ url: url_exports.join(
4898
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4899
+ "v1/admin/keys/scoped"
4900
+ ),
4901
+ method: "POST",
4902
+ headers: _headers,
4903
+ contentType: "application/json",
4904
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4905
+ requestType: "json",
4906
+ body: request,
4907
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4908
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4909
+ abortSignal: requestOptions?.abortSignal,
4910
+ fetchFn: this._options?.fetch,
4911
+ logging: this._options.logging
4912
+ });
4913
+ if (_response.ok) {
4914
+ return { data: _response.body, rawResponse: _response.rawResponse };
4915
+ }
4916
+ if (_response.error.reason === "status-code") {
4917
+ switch (_response.error.statusCode) {
4918
+ case 400:
4919
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4920
+ case 403:
4921
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4922
+ case 404:
4923
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4924
+ case 409:
4925
+ throw new ConflictError(_response.error.body, _response.rawResponse);
4926
+ case 429:
4927
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4928
+ default:
4929
+ throw new VectrosError({
4930
+ statusCode: _response.error.statusCode,
4931
+ body: _response.error.body,
4932
+ rawResponse: _response.rawResponse
4933
+ });
4934
+ }
4935
+ }
4936
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/admin/keys/scoped");
4937
+ }
4938
+ /**
4939
+ * Returns the metadata for a single scoped API key. The raw secret is NOT included — it is only ever returned once, when the key is first created. Requires the `keys:r` scope.
4940
+ *
4941
+ * @param {Vectros.GetScopedKeyRequest} request
4942
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4943
+ *
4944
+ * @throws {@link Vectros.ForbiddenError}
4945
+ * @throws {@link Vectros.NotFoundError}
4946
+ *
4947
+ * @example
4948
+ * await client.auth.getScopedKey({
4949
+ * keyId: "keyId"
4950
+ * })
4951
+ */
4952
+ getScopedKey(request, requestOptions) {
4953
+ return HttpResponsePromise.fromPromise(this.__getScopedKey(request, requestOptions));
4954
+ }
4955
+ async __getScopedKey(request, requestOptions) {
4956
+ const { keyId } = request;
4957
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4958
+ const _headers = mergeHeaders(
4959
+ _authRequest.headers,
4960
+ this._options?.headers,
4961
+ requestOptions?.headers
4962
+ );
4963
+ const _response = await fetcher({
4964
+ url: url_exports.join(
4965
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4966
+ `v1/admin/keys/scoped/${url_exports.encodePathParam(keyId)}`
4967
+ ),
4968
+ method: "GET",
4969
+ headers: _headers,
4970
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4971
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4972
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4973
+ abortSignal: requestOptions?.abortSignal,
4974
+ fetchFn: this._options?.fetch,
4975
+ logging: this._options.logging
4976
+ });
4977
+ if (_response.ok) {
4978
+ return { data: _response.body, rawResponse: _response.rawResponse };
4979
+ }
4980
+ if (_response.error.reason === "status-code") {
4981
+ switch (_response.error.statusCode) {
4982
+ case 403:
4983
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4984
+ case 404:
4985
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4986
+ default:
4987
+ throw new VectrosError({
4988
+ statusCode: _response.error.statusCode,
4989
+ body: _response.error.body,
4990
+ rawResponse: _response.rawResponse
4991
+ });
4992
+ }
4993
+ }
4994
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped/{keyId}");
4995
+ }
4996
+ /**
4997
+ * Revokes a scoped API key. Its status changes to `revoked` and it stops working within about 5 minutes, the maximum time authorization is cached. Revocation is permanent. Requires the `keys:d` scope.
4998
+ *
4999
+ * @param {Vectros.RevokeScopedKeyRequest} request
5000
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
5001
+ *
5002
+ * @throws {@link Vectros.ForbiddenError}
5003
+ * @throws {@link Vectros.NotFoundError}
5004
+ * @throws {@link Vectros.TooManyRequestsError}
5005
+ *
5006
+ * @example
5007
+ * await client.auth.revokeScopedKey({
5008
+ * keyId: "keyId"
5009
+ * })
5010
+ */
5011
+ revokeScopedKey(request, requestOptions) {
5012
+ return HttpResponsePromise.fromPromise(this.__revokeScopedKey(request, requestOptions));
5013
+ }
5014
+ async __revokeScopedKey(request, requestOptions) {
5015
+ const { keyId } = request;
5016
+ const _authRequest = await this._options.authProvider.getAuthRequest();
5017
+ const _headers = mergeHeaders(
5018
+ _authRequest.headers,
5019
+ this._options?.headers,
5020
+ requestOptions?.headers
5021
+ );
5022
+ const _response = await fetcher({
5023
+ url: url_exports.join(
5024
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
5025
+ `v1/admin/keys/scoped/${url_exports.encodePathParam(keyId)}`
5026
+ ),
5027
+ method: "DELETE",
5028
+ headers: _headers,
5029
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5030
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5031
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5032
+ abortSignal: requestOptions?.abortSignal,
5033
+ fetchFn: this._options?.fetch,
5034
+ logging: this._options.logging
5035
+ });
5036
+ if (_response.ok) {
5037
+ return { data: void 0, rawResponse: _response.rawResponse };
5038
+ }
5039
+ if (_response.error.reason === "status-code") {
5040
+ switch (_response.error.statusCode) {
5041
+ case 403:
5042
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5043
+ case 404:
5044
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
5045
+ case 429:
5046
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5047
+ default:
5048
+ throw new VectrosError({
5049
+ statusCode: _response.error.statusCode,
5050
+ body: _response.error.body,
5051
+ rawResponse: _response.rawResponse
5052
+ });
5053
+ }
5054
+ }
5055
+ return handleNonStatusCodeError(
5056
+ _response.error,
5057
+ _response.rawResponse,
5058
+ "DELETE",
5059
+ "/v1/admin/keys/scoped/{keyId}"
5060
+ );
5061
+ }
5062
+ /**
5063
+ * Returns recent API call logs for your account. Each entry represents one API request; request and response bodies are never logged. `startTime` and `endTime` must be ISO-8601 UTC (e.g. `2025-01-15T09:00:00Z`); `endTime` defaults to now. Filter by resource, method, key id, or context id, or set `errorsOnly` to see only failures. Results are scoped to your account, derived from your token — never from input. Requires the `logs:r` scope.
5064
+ *
5065
+ * @param {Vectros.GetAdminLogsRequest} request
5066
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
5067
+ *
5068
+ * @throws {@link Vectros.BadRequestError}
5069
+ * @throws {@link Vectros.ForbiddenError}
5070
+ *
5071
+ * @example
5072
+ * await client.auth.getAdminLogs({
5073
+ * startTime: "startTime",
5074
+ * resource: "documents",
5075
+ * method: "POST",
5076
+ * keyId: "key_abc123",
5077
+ * contextId: "ctx_intake"
5078
+ * })
5079
+ */
5080
+ getAdminLogs(request, requestOptions) {
5081
+ return HttpResponsePromise.fromPromise(this.__getAdminLogs(request, requestOptions));
5082
+ }
5083
+ async __getAdminLogs(request, requestOptions) {
5084
+ const { startTime, endTime, resource, method, keyId, contextId, errorsOnly, limit } = request;
5085
+ const _queryParams = {
5086
+ startTime,
5087
+ endTime,
5088
+ resource,
5089
+ method,
5090
+ keyId,
5091
+ contextId,
5092
+ errorsOnly,
5093
+ limit
5094
+ };
5095
+ const _authRequest = await this._options.authProvider.getAuthRequest();
5096
+ const _headers = mergeHeaders(
5097
+ _authRequest.headers,
5098
+ this._options?.headers,
5099
+ requestOptions?.headers
5100
+ );
5101
+ const _response = await fetcher({
5102
+ url: url_exports.join(
5103
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
5104
+ "v1/admin/logs"
5105
+ ),
5106
+ method: "GET",
5107
+ headers: _headers,
5108
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5109
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5110
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5111
+ abortSignal: requestOptions?.abortSignal,
5112
+ fetchFn: this._options?.fetch,
5113
+ logging: this._options.logging
5114
+ });
5115
+ if (_response.ok) {
5116
+ return { data: _response.body, rawResponse: _response.rawResponse };
5117
+ }
5118
+ if (_response.error.reason === "status-code") {
5119
+ switch (_response.error.statusCode) {
5120
+ case 400:
5121
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
5122
+ case 403:
5123
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5124
+ default:
5125
+ throw new VectrosError({
5126
+ statusCode: _response.error.statusCode,
5127
+ body: _response.error.body,
5128
+ rawResponse: _response.rawResponse
5129
+ });
5130
+ }
5131
+ }
5132
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/logs");
5133
+ }
5134
+ /**
5135
+ * Returns the access profiles assigned within the given app context — in effect, who has access to this context and with what scopes. Each profile binds a principal to either a set of inline scopes or a referenced role. Results are paginated. Requires the `profiles:r` scope.
5136
+ *
5137
+ * @param {Vectros.ListAccessProfilesRequest} request
5138
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
5139
+ *
5140
+ * @throws {@link Vectros.ForbiddenError}
5141
+ * @throws {@link Vectros.NotFoundError}
5142
+ *
5143
+ * @example
5144
+ * await client.auth.listAccessProfiles({
5145
+ * contextId: "myapp"
5146
+ * })
5147
+ */
5148
+ listAccessProfiles(request, requestOptions) {
5149
+ return HttpResponsePromise.fromPromise(this.__listAccessProfiles(request, requestOptions));
5150
+ }
5151
+ async __listAccessProfiles(request, requestOptions) {
5152
+ const { contextId, startFrom, limit } = request;
5153
+ const _queryParams = {
5154
+ startFrom,
5155
+ limit
5156
+ };
5157
+ const _authRequest = await this._options.authProvider.getAuthRequest();
5158
+ const _headers = mergeHeaders(
5159
+ _authRequest.headers,
5160
+ this._options?.headers,
5161
+ requestOptions?.headers
5162
+ );
5163
+ const _response = await fetcher({
5164
+ url: url_exports.join(
5165
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
5166
+ `v1/app-contexts/${url_exports.encodePathParam(contextId)}/profiles`
5167
+ ),
5168
+ method: "GET",
5169
+ headers: _headers,
5170
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5171
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5172
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5173
+ abortSignal: requestOptions?.abortSignal,
5174
+ fetchFn: this._options?.fetch,
5175
+ logging: this._options.logging
5176
+ });
5177
+ if (_response.ok) {
5178
+ return { data: _response.body, rawResponse: _response.rawResponse };
5179
+ }
5180
+ if (_response.error.reason === "status-code") {
5181
+ switch (_response.error.statusCode) {
5182
+ case 403:
5183
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5184
+ case 404:
5185
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
5186
+ default:
5187
+ throw new VectrosError({
5188
+ statusCode: _response.error.statusCode,
5189
+ body: _response.error.body,
5190
+ rawResponse: _response.rawResponse
5191
+ });
5192
+ }
5193
+ }
5194
+ return handleNonStatusCodeError(
5195
+ _response.error,
5196
+ _response.rawResponse,
5197
+ "GET",
5198
+ "/v1/app-contexts/{contextId}/profiles"
5199
+ );
5200
+ }
5201
+ /**
5202
+ * Creates a new access profile under the given app context. This call is idempotent by `principalId`: if a profile with the same `principalId` already exists, the existing profile is returned (with status 200) instead of creating a duplicate. The response's `created` field (and the HTTP status — 201 when created, 200 when an existing profile was returned) tells the two apart. To overwrite an existing profile's `scopes`/`roleId`, `identityOverrides`, and `status` instead of returning it unchanged, set `?upsert=true` (this also requires the `profiles:u` scope, and applies the same `identityOverrides` bounds the update endpoint documents — a scoped credential may not repoint or clear an identity value it does not itself hold). The `principalId` must name a principal that already exists: a `usr_` principal must be a live user in your tenant, so create the user before granting it a profile. A `usr_` id that names no such user is rejected, and no profile is created. `key_` principals are not checked this way. You must provide exactly one of `scopes` (an inline list of scopes) or `roleId` (a reference to a role); supplying both, or neither, is rejected. `identityOverrides` is keyed by ownership namespace in `scope:<namespace>` form — `scope:org` and `scope:client` for the reserved namespaces, or any namespace you have registered — and may name at most two; any other key (including the account identifier or `userId`) is rejected. If you use a scoped credential, the profile's effective scopes may not exceed your own; a root API key (`sk_`) is exempt. Requires the `profiles:c` scope.
5203
+ *
5204
+ * @param {Vectros.CreateAccessProfileRequest} request
5205
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
5206
+ *
5207
+ * @throws {@link Vectros.BadRequestError}
5208
+ * @throws {@link Vectros.ForbiddenError}
5209
+ * @throws {@link Vectros.NotFoundError}
5210
+ * @throws {@link Vectros.ConflictError}
5211
+ * @throws {@link Vectros.TooManyRequestsError}
5212
+ *
5213
+ * @example
4572
5214
  * await client.auth.createAccessProfile({
4573
5215
  * contextId: "contextId",
4574
- * body: {
4575
- * principalId: "usr_6ba7b810-9dad-11d1-80b4-00c04fd430c8"
4576
- * }
5216
+ * body: {}
4577
5217
  * })
4578
5218
  */
4579
5219
  createAccessProfile(request, requestOptions) {
@@ -4709,10 +5349,7 @@ var AuthClient = class {
4709
5349
  *
4710
5350
  * @example
4711
5351
  * await client.auth.createAppContext({
4712
- * body: {
4713
- * contextId: "myapp",
4714
- * name: "My Internal App"
4715
- * }
5352
+ * body: {}
4716
5353
  * })
4717
5354
  */
4718
5355
  createAppContext(request, requestOptions) {
@@ -4855,13 +5492,7 @@ var AuthClient = class {
4855
5492
  * @example
4856
5493
  * await client.auth.createRole({
4857
5494
  * contextId: "contextId",
4858
- * body: {
4859
- * roleId: "engineering-member",
4860
- * name: "Engineering Team Member",
4861
- * scopes: [{
4862
- * allowed_actions: ["records:cru", "search:r"]
4863
- * }]
4864
- * }
5495
+ * body: {}
4865
5496
  * })
4866
5497
  */
4867
5498
  createRole(request, requestOptions) {
@@ -5005,9 +5636,7 @@ var AuthClient = class {
5005
5636
  * await client.auth.updateAccessProfile({
5006
5637
  * contextId: "contextId",
5007
5638
  * principalId: "principalId",
5008
- * body: {
5009
- * principalId: "usr_6ba7b810-9dad-11d1-80b4-00c04fd430c8"
5010
- * }
5639
+ * body: {}
5011
5640
  * })
5012
5641
  */
5013
5642
  updateAccessProfile(request, requestOptions) {
@@ -5210,10 +5839,7 @@ var AuthClient = class {
5210
5839
  * @example
5211
5840
  * await client.auth.updateAppContext({
5212
5841
  * contextId: "contextId",
5213
- * body: {
5214
- * contextId: "myapp",
5215
- * name: "My Internal App"
5216
- * }
5842
+ * body: {}
5217
5843
  * })
5218
5844
  */
5219
5845
  updateAppContext(request, requestOptions) {
@@ -5418,13 +6044,7 @@ var AuthClient = class {
5418
6044
  * await client.auth.updateRole({
5419
6045
  * contextId: "contextId",
5420
6046
  * roleId: "roleId",
5421
- * body: {
5422
- * roleId: "engineering-member",
5423
- * name: "Engineering Team Member",
5424
- * scopes: [{
5425
- * allowed_actions: ["records:cru", "search:r"]
5426
- * }]
5427
- * }
6047
+ * body: {}
5428
6048
  * })
5429
6049
  */
5430
6050
  updateRole(request, requestOptions) {
@@ -6876,7 +7496,7 @@ var DocumentsClient = class {
6876
7496
  this._options = normalizeClientOptionsWithAuth(options);
6877
7497
  }
6878
7498
  /**
6879
- * Returns a paginated list of your documents, optionally filtered by folder (`folderId`) and/or owner (`userId` or `scope`). The response is a `{data, nextCursor}` envelope; pass `nextCursor` back as `startFrom` to fetch the next page. Requires the `documents:r` scope.
7499
+ * Returns a paginated list of your documents, optionally filtered by folder (`folderId`), owner (`userId` or `scope`) and/or type (`type`). The response is a `{data, nextCursor}` envelope; pass `nextCursor` back as `startFrom` to fetch the next page. A page can hold fewer documents than `limit`, or none, while `nextCursor` is still set: keep paging until `nextCursor` is null. Requires the `documents:r` scope.
6880
7500
  *
6881
7501
  * @param {Vectros.ListDocumentsRequest} request
6882
7502
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6886,6 +7506,7 @@ var DocumentsClient = class {
6886
7506
  * userId: "550e8400-e29b-41d4-a716-446655440000",
6887
7507
  * scope: "group:eng-team",
6888
7508
  * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
7509
+ * type: "investigation_report",
6889
7510
  * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
6890
7511
  * })
6891
7512
  */
@@ -6893,11 +7514,12 @@ var DocumentsClient = class {
6893
7514
  return HttpResponsePromise.fromPromise(this.__listDocuments(request, requestOptions));
6894
7515
  }
6895
7516
  async __listDocuments(request = {}, requestOptions) {
6896
- const { userId, scope, folderId, startFrom, limit } = request;
7517
+ const { userId, scope, folderId, type: type_, startFrom, limit } = request;
6897
7518
  const _queryParams = {
6898
7519
  userId,
6899
7520
  scope,
6900
7521
  folderId,
7522
+ type: type_,
6901
7523
  startFrom,
6902
7524
  limit
6903
7525
  };
@@ -6947,9 +7569,7 @@ var DocumentsClient = class {
6947
7569
  *
6948
7570
  * @example
6949
7571
  * await client.documents.ingestDocument({
6950
- * body: {
6951
- * title: "Patient Intake Form \u2014 Jane Doe"
6952
- * }
7572
+ * body: {}
6953
7573
  * })
6954
7574
  */
6955
7575
  ingestDocument(request, requestOptions) {
@@ -7079,9 +7699,7 @@ var DocumentsClient = class {
7079
7699
  * @example
7080
7700
  * await client.documents.updateDocument({
7081
7701
  * id: "id",
7082
- * body: {
7083
- * title: "Patient Intake Form \u2014 Jane Doe"
7084
- * }
7702
+ * body: {}
7085
7703
  * })
7086
7704
  */
7087
7705
  updateDocument(request, requestOptions) {
@@ -7479,11 +8097,12 @@ var DocumentsClient = class {
7479
8097
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/download");
7480
8098
  }
7481
8099
  /**
7482
- * Returns the document's full text body when it is retained: retained for text-ingested documents, and for file-uploaded documents unless they were uploaded with `storeText=false` (which discards the extracted text once indexing completes — the original file remains available via `GET /v1/documents/{id}/download`). Returns 404 when the document does not exist, its text was not retained, or extraction has not yet completed. Requires the `documents:r` scope.
8100
+ * Returns the document's full text body when it is retained: retained for text-ingested documents, and for file-uploaded documents unless they were uploaded with `storeText=false` (which discards the extracted text once indexing completes — the original file remains available via `GET /v1/documents/{id}/download`). Returns 404 when the document does not exist, its text was not retained, extraction has not yet completed, or no text could be extracted from its file (see the document's `extraction`). To read a long text in parts, pass `limit` (and `offset` from the second part on): offsets and lengths count Unicode characters (code points), `totalLength` is the length of the whole text, and `nextOffset` is where the next part starts, absent once the end is reached. Requires the `documents:r` scope.
7483
8101
  *
7484
8102
  * @param {Vectros.GetDocumentTextRequest} request
7485
8103
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
7486
8104
  *
8105
+ * @throws {@link Vectros.BadRequestError}
7487
8106
  * @throws {@link Vectros.NotFoundError}
7488
8107
  *
7489
8108
  * @example
@@ -7495,7 +8114,11 @@ var DocumentsClient = class {
7495
8114
  return HttpResponsePromise.fromPromise(this.__getDocumentText(request, requestOptions));
7496
8115
  }
7497
8116
  async __getDocumentText(request, requestOptions) {
7498
- const { id } = request;
8117
+ const { id, offset, limit } = request;
8118
+ const _queryParams = {
8119
+ offset,
8120
+ limit
8121
+ };
7499
8122
  const _authRequest = await this._options.authProvider.getAuthRequest();
7500
8123
  const _headers = mergeHeaders(
7501
8124
  _authRequest.headers,
@@ -7509,7 +8132,7 @@ var DocumentsClient = class {
7509
8132
  ),
7510
8133
  method: "GET",
7511
8134
  headers: _headers,
7512
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
8135
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
7513
8136
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
7514
8137
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
7515
8138
  abortSignal: requestOptions?.abortSignal,
@@ -7521,6 +8144,8 @@ var DocumentsClient = class {
7521
8144
  }
7522
8145
  if (_response.error.reason === "status-code") {
7523
8146
  switch (_response.error.statusCode) {
8147
+ case 400:
8148
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
7524
8149
  case 404:
7525
8150
  throw new NotFoundError(_response.error.body, _response.rawResponse);
7526
8151
  default:
@@ -7740,9 +8365,7 @@ var FoldersClient = class {
7740
8365
  *
7741
8366
  * @example
7742
8367
  * await client.folders.createFolder({
7743
- * body: {
7744
- * name: "Patient Records 2024"
7745
- * }
8368
+ * body: {}
7746
8369
  * })
7747
8370
  */
7748
8371
  createFolder(request, requestOptions) {
@@ -7870,9 +8493,7 @@ var FoldersClient = class {
7870
8493
  * @example
7871
8494
  * await client.folders.updateFolder({
7872
8495
  * id: "id",
7873
- * body: {
7874
- * name: "Patient Records 2024"
7875
- * }
8496
+ * body: {}
7876
8497
  * })
7877
8498
  */
7878
8499
  updateFolder(request, requestOptions) {
@@ -8005,9 +8626,7 @@ var FoldersClient = class {
8005
8626
  * @example
8006
8627
  * await client.folders.patchFolder({
8007
8628
  * id: "id",
8008
- * body: {
8009
- * name: "Patient Records 2024"
8010
- * }
8629
+ * body: {}
8011
8630
  * })
8012
8631
  */
8013
8632
  patchFolder(request, requestOptions) {
@@ -8222,9 +8841,7 @@ var IdentityClient = class {
8222
8841
  * await client.identity.createEntity({
8223
8842
  * namespace: "team",
8224
8843
  * contextId: "myapp",
8225
- * body: {
8226
- * externalId: "team_eng_platform"
8227
- * }
8844
+ * body: {}
8228
8845
  * })
8229
8846
  */
8230
8847
  createEntity(request, requestOptions) {
@@ -8360,9 +8977,7 @@ var IdentityClient = class {
8360
8977
  * namespace: "team",
8361
8978
  * id: "id",
8362
8979
  * contextId: "myapp",
8363
- * body: {
8364
- * externalId: "team_eng_platform"
8365
- * }
8980
+ * body: {}
8366
8981
  * })
8367
8982
  */
8368
8983
  updateEntity(request, requestOptions) {
@@ -8702,10 +9317,7 @@ var IdentityClient = class {
8702
9317
  * @example
8703
9318
  * await client.identity.updateNamespace({
8704
9319
  * namespace: "team",
8705
- * body: {
8706
- * namespace: "team",
8707
- * specificityRank: 1500
8708
- * }
9320
+ * body: {}
8709
9321
  * })
8710
9322
  */
8711
9323
  updateNamespace(request, requestOptions) {
@@ -8892,10 +9504,7 @@ var IdentityClient = class {
8892
9504
  *
8893
9505
  * @example
8894
9506
  * await client.identity.registerNamespace({
8895
- * body: {
8896
- * namespace: "team",
8897
- * specificityRank: 1500
8898
- * }
9507
+ * body: {}
8899
9508
  * })
8900
9509
  */
8901
9510
  registerNamespace(request, requestOptions) {
@@ -9028,9 +9637,7 @@ var IdentityClient = class {
9028
9637
  *
9029
9638
  * @example
9030
9639
  * await client.identity.createUser({
9031
- * body: {
9032
- * externalId: "usr_12345"
9033
- * }
9640
+ * body: {}
9034
9641
  * })
9035
9642
  */
9036
9643
  createUser(request, requestOptions) {
@@ -9149,6 +9756,7 @@ var IdentityClient = class {
9149
9756
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
9150
9757
  *
9151
9758
  * @throws {@link Vectros.BadRequestError}
9759
+ * @throws {@link Vectros.ForbiddenError}
9152
9760
  * @throws {@link Vectros.NotFoundError}
9153
9761
  * @throws {@link Vectros.ConflictError}
9154
9762
  * @throws {@link Vectros.TooManyRequestsError}
@@ -9156,9 +9764,7 @@ var IdentityClient = class {
9156
9764
  * @example
9157
9765
  * await client.identity.updateUser({
9158
9766
  * id: "550e8400-e29b-41d4-a716-446655440000",
9159
- * body: {
9160
- * externalId: "usr_12345"
9161
- * }
9767
+ * body: {}
9162
9768
  * })
9163
9769
  */
9164
9770
  updateUser(request, requestOptions) {
@@ -9196,6 +9802,8 @@ var IdentityClient = class {
9196
9802
  switch (_response.error.statusCode) {
9197
9803
  case 400:
9198
9804
  throw new BadRequestError(_response.error.body, _response.rawResponse);
9805
+ case 403:
9806
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
9199
9807
  case 404:
9200
9808
  throw new NotFoundError(_response.error.body, _response.rawResponse);
9201
9809
  case 409:
@@ -9218,6 +9826,7 @@ var IdentityClient = class {
9218
9826
  * @param {Vectros.DeleteUserRequest} request
9219
9827
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
9220
9828
  *
9829
+ * @throws {@link Vectros.ForbiddenError}
9221
9830
  * @throws {@link Vectros.NotFoundError}
9222
9831
  * @throws {@link Vectros.ConflictError}
9223
9832
  * @throws {@link Vectros.TooManyRequestsError}
@@ -9257,6 +9866,8 @@ var IdentityClient = class {
9257
9866
  }
9258
9867
  if (_response.error.reason === "status-code") {
9259
9868
  switch (_response.error.statusCode) {
9869
+ case 403:
9870
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
9260
9871
  case 404:
9261
9872
  throw new NotFoundError(_response.error.body, _response.rawResponse);
9262
9873
  case 409:
@@ -9461,7 +10072,7 @@ var InferenceClient = class {
9461
10072
  this._options = normalizeClientOptionsWithAuth(options);
9462
10073
  }
9463
10074
  /**
9464
- * Returns the whole platform model catalog, whatever your plan: `availableOn` names each model's plan tiers. Each entry also carries the context window and the credit rates per 1K input and output tokens: `regionPricing.base` for global-region requests, `regionPricing.us` for default requests, and `longContext` for prompts over its threshold. A model reached through a `providerAlias` is not listed. Use this to populate model pickers and to validate a request before calling `/v1/chat`, `/v1/rag`, or `/v1/documents/{id}/ask`.
10075
+ * Returns the whole platform model catalog, whatever your plan: `availableOn` names each model's plan tiers. Each entry also carries the context window and the credit rates per 1K input and output tokens: `regionPricing.base` for global-region requests, `regionPricing.us` for default requests, `longContext` for prompts over its threshold, and `cachePricing` for prompt-cache tokens on a model that has cache prices. A model reached through a `providerAlias` is not listed. Use this to populate model pickers and to validate a request before calling `/v1/chat`, `/v1/rag`, or `/v1/documents/{id}/ask`.
9465
10076
  *
9466
10077
  * @param {InferenceClient.RequestOptions} requestOptions - Request-specific configuration.
9467
10078
  *
@@ -9505,7 +10116,7 @@ var InferenceClient = class {
9505
10116
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/models");
9506
10117
  }
9507
10118
  /**
9508
- * Streams a model response as Server-Sent Events (SSE). Send the full conversation history in the `messages` array; a message with role `system` is extracted and used as the system prompt. On a pre-paid account, token cost is debited from your inference balance; usage-billed accounts accrue it for post-billing. A request routed through your own `providerAlias` incurs no Vectros token cost. A small per-call flat fee is charged to your monthly credit allowance, except when the call fails on the server side. Requires the `inference:r` scope.
10119
+ * Streams a model response as Server-Sent Events (SSE). Send the full conversation history in the `messages` array; a message with role `system` is extracted and used as the system prompt. On a pre-paid account, token cost is debited from your inference balance; usage-billed accounts accrue it for post-billing. A request routed through your own `providerAlias` incurs no Vectros token cost. A per-call platform fee is charged to your monthly credit allowance, except when the call fails on the server side: 0.2 credits for a call of up to 60 seconds, then 1 credit per 5 minutes of the call's duration. Requires the `inference:r` scope.
9509
10120
  */
9510
10121
  chatInference(request, requestOptions) {
9511
10122
  return HttpResponsePromise.fromPromise(this.__chatInference(request, requestOptions));
@@ -9577,7 +10188,7 @@ var InferenceClient = class {
9577
10188
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/chat");
9578
10189
  }
9579
10190
  /**
9580
- * Loads a single document's extracted text, supplies it as context, and streams a model answer about it. The document must be fully indexed. If the document exceeds the 32K-token cap, the call returns 413 with no credits charged — use `POST /v1/rag` instead to answer over larger or multi-document collections. Requires the `inference:r` scope.
10191
+ * Loads a single document's extracted text, supplies it as context, and streams a model answer about it. The document must have finished processing and have text. If the document exceeds the 32K-token cap, the call returns 413 with no credits charged — use `POST /v1/rag` instead to answer over larger or multi-document collections. Requires the `inference:r` scope.
9581
10192
  */
9582
10193
  documentAsk(request, requestOptions) {
9583
10194
  return HttpResponsePromise.fromPromise(this.__documentAsk(request, requestOptions));
@@ -10712,8 +11323,7 @@ var SchemasClient = class {
10712
11323
  * }],
10713
11324
  * capabilities: {
10714
11325
  * "auditHistory": true
10715
- * },
10716
- * allowedSurfaces: ["record"]
11326
+ * }
10717
11327
  * }
10718
11328
  * })
10719
11329
  */
@@ -10843,11 +11453,7 @@ var SchemasClient = class {
10843
11453
  * @example
10844
11454
  * await client.schemas.updateSchema({
10845
11455
  * id: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
10846
- * body: {
10847
- * typeName: "intake_form",
10848
- * displayName: "Client Intake Form",
10849
- * allowedSurfaces: ["record"]
10850
- * }
11456
+ * body: {}
10851
11457
  * })
10852
11458
  */
10853
11459
  updateSchema(request, requestOptions) {
@@ -11387,7 +11993,7 @@ var ScriptsClient = class {
11387
11993
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/scripts/{id}");
11388
11994
  }
11389
11995
  /**
11390
- * Permanently deletes one script version by ID. Deleting one version does not affect any other version of the same name. Requires the `scripts:d` scope. Refused with 409 if a live trigger rule still references this version — either directly (a pinned `scriptRef`), or via `"latest"` when this IS the newest version of its name. Delete or re-point those rules first.
11996
+ * Permanently deletes one script version by ID. Deleting one version does not affect any other version of the same name. Requires the `scripts:d` scope. Refused with 409 if a live trigger rule still references this version — either directly (a pinned `scriptRef`), or via `"latest"` when this IS the newest version of its name. Delete or re-point those rules first. Also refused with a retryable 409, deleting nothing, when it cannot be told yet whether this is the newest version (for example just after several of this name's newest versions were deleted, or while its first version is gone and the listing has not caught up) and a `"latest"` rule depends on the script: try again shortly.
11391
11997
  *
11392
11998
  * @param {Vectros.DeleteScriptRequest} request
11393
11999
  * @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -11651,17 +12257,7 @@ var TriggersClient = class {
11651
12257
  *
11652
12258
  * @example
11653
12259
  * await client.triggers.createTrigger({
11654
- * body: {
11655
- * name: "on-intake-create",
11656
- * firingSource: {
11657
- * schemaId: "sch_9f8e7d6c",
11658
- * event: "CREATE"
11659
- * },
11660
- * scriptRef: {
11661
- * name: "notify-assignee",
11662
- * version: "3"
11663
- * }
11664
- * }
12260
+ * body: {}
11665
12261
  * })
11666
12262
  */
11667
12263
  createTrigger(request, requestOptions) {
@@ -11790,17 +12386,7 @@ var TriggersClient = class {
11790
12386
  * @example
11791
12387
  * await client.triggers.updateTrigger({
11792
12388
  * id: "id",
11793
- * body: {
11794
- * name: "on-intake-create",
11795
- * firingSource: {
11796
- * schemaId: "sch_9f8e7d6c",
11797
- * event: "CREATE"
11798
- * },
11799
- * scriptRef: {
11800
- * name: "notify-assignee",
11801
- * version: "3"
11802
- * }
11803
- * }
12389
+ * body: {}
11804
12390
  * })
11805
12391
  */
11806
12392
  updateTrigger(request, requestOptions) {
@@ -11925,6 +12511,9 @@ var VectrosClient = class {
11925
12511
  get auth() {
11926
12512
  return this._auth ?? (this._auth = new AuthClient(this._options));
11927
12513
  }
12514
+ get agents() {
12515
+ return this._agents ?? (this._agents = new AgentsClient(this._options));
12516
+ }
11928
12517
  get documents() {
11929
12518
  return this._documents ?? (this._documents = new DocumentsClient(this._options));
11930
12519
  }
@@ -11992,7 +12581,11 @@ var logging;
11992
12581
  var import_zod = require("zod");
11993
12582
  var import_zod2 = require("zod");
11994
12583
  var CONTEXT_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
11995
- var FORGERY_CHARS_RE = /[\x00-\x1f\x7f-\x9f\u00ad\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2060\u2066-\u2069\ufeff\u{e0000}-\u{e007f}]/u;
12584
+ var FORGERY_CHARS_RE = (
12585
+ // eslint-disable-next-line no-control-regex -- matching control characters is what this pattern is for
12586
+ /[\x00-\x1f\x7f-\x9f\u00ad\u061c\u115f\u1160\u180e\u200b-\u200f\u2028\u2029\u202a-\u202e\u2060-\u2064\u2066-\u206f\u3164\ufeff\uffa0\ufff9-\ufffb\u{1bca0}-\u{1bca3}\u{1d173}-\u{1d17a}\u{e0000}-\u{e007f}]/u
12587
+ );
12588
+ var FORGERY_CHARS_GLOBAL_RE = new RegExp(FORGERY_CHARS_RE.source, "gu");
11996
12589
  function safeDisplayString(base) {
11997
12590
  return base.refine((v) => !FORGERY_CHARS_RE.test(v), {
11998
12591
  message: "must not contain control characters, invisible/zero-width characters, line/paragraph separators, or bidi marks/overrides \u2014 these can forge, hide, or reorder text in a rendered plan preview"
@@ -12159,7 +12752,8 @@ var BlueprintLookupFieldSchema = import_zod.z.union([
12159
12752
  // or a declared field on this schema. Locked at create. Valid on a
12160
12753
  // composite lookup too — it orders WITHIN a group when a query supplies
12161
12754
  // fewer values than the lookup declares, not across the whole result.
12162
- sortBy: import_zod.z.string().min(1).optional(),
12755
+ // Printed by the CLI's lint findings, so held to the displayed-text rule like the other names here.
12756
+ sortBy: safeDisplayString(import_zod.z.string().min(1)).optional(),
12163
12757
  // Opt a field past the fixed fast-index budget into a higher-cost
12164
12758
  // secondary index. No effect on a field that fits within the budget.
12165
12759
  allowOverflow: import_zod.z.boolean().optional()
@@ -12376,7 +12970,7 @@ var BlueprintAccessProfileSchema = import_zod.z.object({
12376
12970
  // this file, and so a future preview enhancement that starts showing these values (a very
12377
12971
  // plausible one, given the existing renderPlanDataScope precedent) doesn't silently reopen
12378
12972
  // this exact class on a field that looks, byte-for-byte, like every other one already closed.
12379
- dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
12973
+ dataScope: import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
12380
12974
  // Optional identity overrides for the service principal's profile — scope
12381
12975
  // values its key STAMPS onto everything it writes (a credential can only
12382
12976
  // stamp ownership its identity carries). Keys are the namespaced `scope:<ns>`
@@ -12389,7 +12983,7 @@ var BlueprintAccessProfileSchema = import_zod.z.object({
12389
12983
  // below (`lintIdentityOverrides`, placeholder-aware — see that function's
12390
12984
  // docstring); this `.min(1)` is only the shape-level "non-blank" guard on
12391
12985
  // the value, not the grammar check.
12392
- identityOverrides: import_zod.z.record(import_zod.z.string().min(1)).optional(),
12986
+ identityOverrides: import_zod.z.record(import_zod.z.string(), import_zod.z.string().min(1)).optional(),
12393
12987
  // Optional capabilities granted to the blueprint's own service-principal
12394
12988
  // profile. DECISION: allowed here, not role-clause-only. On the platform,
12395
12989
  // an access profile's scopes are themselves a list of clauses — the exact
@@ -12453,7 +13047,7 @@ var BlueprintAccessProfileSchema = import_zod.z.object({
12453
13047
  });
12454
13048
  var BlueprintRoleClauseSchema = import_zod.z.object({
12455
13049
  allowedActions: import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(1),
12456
- dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
13050
+ dataScope: import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
12457
13051
  dataScopeRef: import_zod.z.string().min(1).optional(),
12458
13052
  capabilities: GrantedCapabilitiesSchema,
12459
13053
  assignableRoles: AssignableRolesSchema
@@ -12467,11 +13061,12 @@ var BlueprintRoleClauseSchema = import_zod.z.object({
12467
13061
  }
12468
13062
  });
12469
13063
  var BlueprintFragmentsSchema = import_zod.z.record(
12470
- import_zod.z.record(import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()])))
13064
+ import_zod.z.string(),
13065
+ import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()])))
12471
13066
  );
12472
13067
  var BlueprintRolesSchema = import_zod.z.record(safeDisplayString(import_zod.z.string().min(1)), import_zod.z.array(BlueprintRoleClauseSchema).min(1));
12473
- var BlueprintRoleAssumableSchema = import_zod.z.record(import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(1));
12474
- var BlueprintRoleAssumableMapSchema = import_zod.z.record(BlueprintRoleAssumableSchema);
13068
+ var BlueprintRoleAssumableSchema = import_zod.z.record(import_zod.z.string(), import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(1));
13069
+ var BlueprintRoleAssumableMapSchema = import_zod.z.record(import_zod.z.string(), BlueprintRoleAssumableSchema);
12475
13070
  var BlueprintTriggerFiringSourceSchema = import_zod.z.object({
12476
13071
  schemaName: import_zod.z.string().min(1),
12477
13072
  event: import_zod.z.enum(["CREATE", "UPDATE", "DELETE"])
@@ -12505,7 +13100,7 @@ var BlueprintTriggerEntrySchema = import_zod.z.object({
12505
13100
  scriptRef: BlueprintTriggerScriptRefSchema,
12506
13101
  allowedActions: import_zod.z.array(safeDisplayString(import_zod.z.string().min(1))).min(1).optional(),
12507
13102
  roleIds: import_zod.z.array(import_zod.z.string().min(1)).min(1).optional(),
12508
- dataScope: import_zod.z.record(import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
13103
+ dataScope: import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.union([safeDisplayString(import_zod.z.string().min(1)), import_zod.z.null()]))).optional(),
12509
13104
  capabilities: GrantedCapabilitiesSchema,
12510
13105
  manifest: BlueprintTriggerManifestSchema,
12511
13106
  /**
@@ -12585,7 +13180,113 @@ var BlueprintScriptEntrySchema = import_zod.z.object({
12585
13180
  message: `'declaredInputContract' exceeds the platform's ${SCRIPT_INPUT_CONTRACT_MAX_BYTES.toLocaleString("en-US")}-byte cap (measured in UTF-8 bytes, not characters)`
12586
13181
  }).optional()
12587
13182
  }).strict();
12588
- var BlueprintScriptsSchema = import_zod.z.record(BlueprintScriptEntrySchema);
13183
+ var BlueprintScriptsSchema = import_zod.z.record(import_zod.z.string(), BlueprintScriptEntrySchema);
13184
+ var BLUEPRINT_FORMAT_VERSION = 2;
13185
+ var AGENT_LIMITS = {
13186
+ descriptionChars: 500,
13187
+ modelChars: 100,
13188
+ systemChars: 32e3,
13189
+ tools: 50,
13190
+ maxSteps: 25,
13191
+ minTokensPerCall: 16,
13192
+ maxTokensPerCall: 8192,
13193
+ maxToolCalls: 50,
13194
+ maxWrites: 1e3,
13195
+ maxInferenceCents: 500
13196
+ };
13197
+ var AGENT_TOOL_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
13198
+ var BlueprintAgentToolSchema = import_zod.z.object({
13199
+ /** The tool's name in the platform's tool catalog. Checked against the catalog when the blueprint is applied. */
13200
+ name: import_zod.z.string().regex(AGENT_TOOL_NAME_RE, {
13201
+ message: "a tool name is 1-64 characters of letters, digits, underscores ('_') and hyphens ('-')"
13202
+ }),
13203
+ /** Required `true` for a tool that creates, changes or deletes data. */
13204
+ allowWrites: import_zod.z.boolean().optional()
13205
+ }).strict();
13206
+ var BlueprintAgentEntrySchema = import_zod.z.object({
13207
+ description: import_zod.z.string().max(AGENT_LIMITS.descriptionChars).optional(),
13208
+ /**
13209
+ * Whose identity the agent's tool calls run with. `CALLER` (the caller who starts the run) is the only value the platform accepts
13210
+ * today. Checked here for its SHAPE only, never against a list: the set will grow, and the platform refuses a value it does not
13211
+ * support when the blueprint is applied, so an older copy of this package never refuses a value a newer platform accepts.
13212
+ */
13213
+ authority: import_zod.z.string().regex(/^[A-Z][A-Z_]{0,31}$/, { message: "authority is an upper-case name such as 'CALLER'" }).optional(),
13214
+ /** The model the agent runs on, by its id in the model catalog. */
13215
+ // Shown in the plan preview, so held to the same rule as every other displayed text.
13216
+ model: safeDisplayString(import_zod.z.string().min(1).max(AGENT_LIMITS.modelChars)),
13217
+ /** The system prompt. */
13218
+ system: import_zod.z.string().max(AGENT_LIMITS.systemChars).optional(),
13219
+ /** The tool whitelist. */
13220
+ tools: import_zod.z.array(BlueprintAgentToolSchema).max(AGENT_LIMITS.tools).optional(),
13221
+ limits: import_zod.z.object({
13222
+ maxSteps: import_zod.z.number().int().min(1).max(AGENT_LIMITS.maxSteps).optional(),
13223
+ maxTokensPerCall: import_zod.z.number().int().min(AGENT_LIMITS.minTokensPerCall).max(AGENT_LIMITS.maxTokensPerCall).optional(),
13224
+ maxToolCalls: import_zod.z.number().int().min(0).max(AGENT_LIMITS.maxToolCalls).optional(),
13225
+ maxWrites: import_zod.z.number().int().min(0).max(AGENT_LIMITS.maxWrites).optional()
13226
+ }).strict().optional(),
13227
+ budget: import_zod.z.object({
13228
+ /** US cents per run, up to 500. The platform also enforces a minimum and refuses a lower value when the blueprint is applied. */
13229
+ maxInferenceCents: import_zod.z.number().int().min(1).max(AGENT_LIMITS.maxInferenceCents).optional()
13230
+ }).strict().optional()
13231
+ }).strict();
13232
+ var BlueprintAgentsSchema = import_zod.z.record(safeDisplayString(import_zod.z.string()), BlueprintAgentEntrySchema);
13233
+ function assertSupportedFormatVersion(input) {
13234
+ if (input === null || typeof input !== "object") return;
13235
+ const declared = input.formatVersion;
13236
+ if (typeof declared === "number" && Number.isInteger(declared) && declared > BLUEPRINT_FORMAT_VERSION) {
13237
+ throw new BlueprintValidationError(
13238
+ `This blueprint uses format version ${declared}; this tool supports format versions up to ${BLUEPRINT_FORMAT_VERSION}. Upgrade @vectros-ai/cli and @vectros-ai/blueprints and try again.`,
13239
+ [{ path: "formatVersion", message: `unsupported format version ${declared}` }]
13240
+ );
13241
+ }
13242
+ }
13243
+ function lintAgents(value, ctx) {
13244
+ const bp = value;
13245
+ const agents = bp.agents;
13246
+ if (!agents) return;
13247
+ if ((bp.formatVersion ?? 1) < 2) {
13248
+ ctx.addIssue({
13249
+ code: import_zod.z.ZodIssueCode.custom,
13250
+ path: ["formatVersion"],
13251
+ message: "an 'agents' section needs 'formatVersion: 2' (so a tool that reads an older format refuses the file instead of misreading it)"
13252
+ });
13253
+ }
13254
+ for (const [name, agent] of Object.entries(agents)) {
13255
+ if (!SCRIPT_NAME_RE.test(name)) {
13256
+ ctx.addIssue({
13257
+ code: import_zod.z.ZodIssueCode.custom,
13258
+ path: ["agents", name],
13259
+ message: `agents['${displaySafe(name, 80)}'] is not a valid agent name \u2014 use 1-64 characters of letters, digits, underscores ('_') and hyphens ('-'). The map key IS the name the platform stores.`
13260
+ });
13261
+ }
13262
+ const tools = agent?.tools ?? [];
13263
+ const seen = /* @__PURE__ */ new Set();
13264
+ tools.forEach((tool, i) => {
13265
+ if (seen.has(tool.name)) {
13266
+ ctx.addIssue({
13267
+ code: import_zod.z.ZodIssueCode.custom,
13268
+ path: ["agents", name, "tools", i, "name"],
13269
+ message: `tool '${displaySafe(tool.name, 80)}' is listed twice`
13270
+ });
13271
+ }
13272
+ seen.add(tool.name);
13273
+ });
13274
+ if (tools.some((t) => t.allowWrites === true) && (agent?.limits?.maxWrites ?? 0) < 1) {
13275
+ ctx.addIssue({
13276
+ code: import_zod.z.ZodIssueCode.custom,
13277
+ path: ["agents", name, "limits", "maxWrites"],
13278
+ message: "limits.maxWrites must be at least 1 when a tool has allowWrites: true"
13279
+ });
13280
+ }
13281
+ if (tools.length > 0 && agent?.limits?.maxToolCalls === 0) {
13282
+ ctx.addIssue({
13283
+ code: import_zod.z.ZodIssueCode.custom,
13284
+ path: ["agents", name, "limits", "maxToolCalls"],
13285
+ message: "limits.maxToolCalls must be at least 1 when tools are listed"
13286
+ });
13287
+ }
13288
+ }
13289
+ }
12589
13290
  var IDENTITY_NAMESPACE_RE = /^[a-z][a-z0-9_-]{1,31}$/;
12590
13291
  var FORBIDDEN_IDENTITY_NAMESPACES = /* @__PURE__ */ new Set(["record", "document", "entity", "user"]);
12591
13292
  var IdentityKindSchema = import_zod.z.string().min(1).superRefine((val, ctx) => {
@@ -12613,7 +13314,7 @@ var IdentityDeclSchema = import_zod.z.object({
12613
13314
  // sibling fields) — it does reach a platform API payload as an entity's `name`.
12614
13315
  externalId: safeDisplayString(import_zod.z.string().min(1)),
12615
13316
  displayName: safeDisplayString(import_zod.z.string().min(1)).optional(),
12616
- metadata: import_zod.z.record(import_zod.z.unknown()).optional()
13317
+ metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
12617
13318
  }).strict();
12618
13319
  var IDENTITY_NAME_RE = /^[A-Za-z_]\w*$/;
12619
13320
  var IdentitiesDeclSchema = import_zod.z.record(
@@ -12759,8 +13460,7 @@ var BlueprintIssuerSchema = import_zod.z.object({
12759
13460
  */
12760
13461
  capturedClaims: import_zod.z.array(
12761
13462
  // `safeDisplayString` (a `.refine`) then the blank-name `.refine` — both ordered AFTER
12762
- // `.max`, since `.refine` returns a ZodEffects, which has no `.max` (`.refine` itself
12763
- // chains fine onto a ZodEffects — it's on the ZodType base, not ZodString-specific).
13463
+ // `.max`, by the same last-call convention.
12764
13464
  // `.min(1)` would be a LENGTH check and would admit a whitespace-only name while the
12765
13465
  // message claims "non-blank"; the platform rejects on isBlank(). Note the value is
12766
13466
  // REJECTED, not trimmed — it reaches the wire exactly as authored, matching how the
@@ -12973,7 +13673,7 @@ var RecordSeedSchema = import_zod.z.object({
12973
13673
  surface: import_zod.z.literal("record"),
12974
13674
  ...SeedCommonShape,
12975
13675
  /** The record payload, validated against the bound schema. */
12976
- fields: import_zod.z.record(import_zod.z.unknown())
13676
+ fields: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown())
12977
13677
  }).strict();
12978
13678
  var DocumentSeedSchema = import_zod.z.object({
12979
13679
  surface: import_zod.z.literal("document"),
@@ -12983,7 +13683,7 @@ var DocumentSeedSchema = import_zod.z.object({
12983
13683
  /** Raw text content to ingest + index — REQUIRED and non-empty (the platform rejects a blank ingest). */
12984
13684
  text: import_zod.z.string().min(1),
12985
13685
  /** Optional structured payload bound to the schema (the document's `fields`). */
12986
- fields: import_zod.z.record(import_zod.z.unknown()).optional()
13686
+ fields: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
12987
13687
  }).strict();
12988
13688
  var BlueprintSeedRecordSchema = import_zod.z.discriminatedUnion("surface", [
12989
13689
  RecordSeedSchema,
@@ -13608,6 +14308,8 @@ var BLUEPRINT_FIELD_PHASES = Object.freeze({
13608
14308
  // principal/AccessProfile pairs instead of one), so it applies in the identical in-context phase,
13609
14309
  // under the same re-minted context-confined token.
13610
14310
  triggers: "in-context",
14311
+ // Written with the same in-context, context-confined token as scripts and triggers.
14312
+ agents: "in-context",
13611
14313
  // Setting it requires the platform provisioning capability, which only the bootstrap credential
13612
14314
  // carries — the same reasoning as issuers/namespaces above. It is applied to the app context
13613
14315
  // itself, which the bootstrap phase already creates, so there is nothing for the in-context pass
@@ -13734,7 +14436,7 @@ var BlueprintSchema = import_zod.z.object({
13734
14436
  // safeDisplayString: rendered raw into buildPlan's context step (`identity projection:
13735
14437
  // [...]`) — same rendered-plan-preview shape as `capturedClaims` (its structural pair);
13736
14438
  // the same ordering rule applies (safeDisplayString's own `.refine` before the blank-name `.refine` below,
13737
- // both after `.max` since `.refine` returns a ZodEffects with no `.max`).
14439
+ // both after `.max`, by the last-call convention).
13738
14440
  safeDisplayString(
13739
14441
  import_zod.z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
13740
14442
  message: `an identityProjectionClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
@@ -13765,7 +14467,16 @@ var BlueprintSchema = import_zod.z.object({
13765
14467
  * composes) are live authority, not a declaration awaiting a future release. Author the grant as
13766
14468
  * narrowly as you would any other credential's.
13767
14469
  */
13768
- triggers: BlueprintTriggersSchema.optional()
14470
+ triggers: BlueprintTriggersSchema.optional(),
14471
+ /**
14472
+ * The blueprint format version; absent means 1. A blueprint with an `agents` section declares 2.
14473
+ */
14474
+ formatVersion: import_zod.z.number().int().min(1).max(BLUEPRINT_FORMAT_VERSION).optional(),
14475
+ /**
14476
+ * Optional stored agents: agentName -> definition. A run of an agent calls tools with the identity
14477
+ * and permissions of the caller who starts the run, further limited by the agent's tool whitelist.
14478
+ */
14479
+ agents: BlueprintAgentsSchema.optional()
13769
14480
  }).strict().superRefine((bp, ctx) => {
13770
14481
  lintSelfTokenPlacement(bp, ctx);
13771
14482
  lintIdentityRefs(bp, ctx);
@@ -13784,17 +14495,46 @@ var BlueprintSchema = import_zod.z.object({
13784
14495
  lintTriggers(bp, ctx);
13785
14496
  lintTriggerFields(bp, ctx);
13786
14497
  lintScripts(bp, ctx);
14498
+ lintAgents(bp, ctx);
13787
14499
  });
14500
+ function displaySafe(text, maxLength = Number.POSITIVE_INFINITY) {
14501
+ const shown = Array.from(text.replace(FORGERY_CHARS_GLOBAL_RE, "?"));
14502
+ return shown.length > maxLength ? `${shown.slice(0, maxLength).join("")}...` : shown.join("");
14503
+ }
14504
+ function displaySafeInline(text, maxLength = Number.POSITIVE_INFINITY) {
14505
+ return displaySafe(text.replace(/\r\n|[\r\n\u0085\u2028\u2029]/g, " "), maxLength);
14506
+ }
14507
+ function displaySafeLines(text) {
14508
+ return text.replace(/\r\n/g, "\n").split("\n").map((line) => displaySafe(line)).join("\n");
14509
+ }
14510
+ function neutralizeIssues(issues) {
14511
+ return issues.map((i) => ({ path: displaySafe(i.path), message: displaySafe(i.message) }));
14512
+ }
14513
+ function quotedKeysSafe(issue) {
14514
+ return displaySafe(issue.message, issue.code === "unrecognized_keys" ? 300 : Number.POSITIVE_INFINITY);
14515
+ }
13788
14516
  function formatIssuePath(path4) {
13789
14517
  let out = "";
13790
14518
  for (const seg of path4) {
13791
14519
  if (typeof seg === "number") out += `[${seg}]`;
13792
- else out += out.length ? `.${seg}` : seg;
14520
+ else out += out.length ? `.${displaySafe(String(seg))}` : displaySafe(String(seg));
13793
14521
  }
13794
14522
  return out.length ? out : "(root)";
13795
14523
  }
14524
+ function flattenZodIssues(error) {
14525
+ const out = [];
14526
+ for (const i of error.issues) {
14527
+ const nested = i.code === "invalid_key" ? i.issues : void 0;
14528
+ if (nested && nested.length > 0) {
14529
+ for (const n of nested) out.push({ path: [...i.path, ...n.path], message: quotedKeysSafe(n) });
14530
+ } else {
14531
+ out.push({ path: [...i.path], message: quotedKeysSafe(i) });
14532
+ }
14533
+ }
14534
+ return out;
14535
+ }
13796
14536
  function toBlueprintIssues(error) {
13797
- return error.issues.map((i) => ({ path: formatIssuePath(i.path), message: i.message }));
14537
+ return flattenZodIssues(error).map((i) => ({ path: formatIssuePath(i.path), message: i.message }));
13798
14538
  }
13799
14539
  function renderIssues(issues) {
13800
14540
  return issues.map((i) => ` \u2022 ${i.path}: ${i.message}`).join("\n");
@@ -13807,12 +14547,13 @@ var BlueprintValidationError = class extends Error {
13807
14547
  */
13808
14548
  issues;
13809
14549
  constructor(message, issues = []) {
13810
- super(message);
14550
+ super(displaySafeLines(message));
13811
14551
  this.name = "BlueprintValidationError";
13812
- this.issues = issues;
14552
+ this.issues = neutralizeIssues(issues);
13813
14553
  }
13814
14554
  };
13815
14555
  function parseBlueprint(input) {
14556
+ assertSupportedFormatVersion(input);
13816
14557
  const result = BlueprintSchema.safeParse(input);
13817
14558
  if (!result.success) {
13818
14559
  const issues = toBlueprintIssues(result.error);
@@ -13828,9 +14569,9 @@ var IDENTITY_TOKEN_RE = /\$\{\{\s*identities\.([A-Za-z_]\w*)\s*\}\}/g;
13828
14569
  var BlueprintIdentityError = class extends Error {
13829
14570
  issues;
13830
14571
  constructor(message, issues = []) {
13831
- super(message);
14572
+ super(displaySafeLines(message));
13832
14573
  this.name = "BlueprintIdentityError";
13833
- this.issues = issues;
14574
+ this.issues = neutralizeIssues(issues);
13834
14575
  }
13835
14576
  };
13836
14577
  function collectIdentityReferences(value) {
@@ -13868,8 +14609,8 @@ async function resolveBlueprintIdentities(raw, resolve) {
13868
14609
  if (!parsed.success) {
13869
14610
  throw new BlueprintIdentityError(
13870
14611
  "Blueprint identities block is invalid",
13871
- parsed.error.issues.map((i) => ({
13872
- path: i.path.length ? `identities.${i.path.join(".")}` : "identities",
14612
+ flattenZodIssues(parsed.error).map((i) => ({
14613
+ path: i.path.length ? `identities.${i.path.map(String).join(".")}` : "identities",
13873
14614
  message: i.message
13874
14615
  }))
13875
14616
  );
@@ -13878,7 +14619,7 @@ async function resolveBlueprintIdentities(raw, resolve) {
13878
14619
  const undeclared = refs.filter((name) => !(name in declared));
13879
14620
  if (undeclared.length) {
13880
14621
  throw new BlueprintIdentityError(
13881
- `Blueprint references undeclared identities: ${undeclared.join(", ")}`,
14622
+ `Blueprint references undeclared identities: ${undeclared.map((n) => displaySafe(n, 80)).join(", ")}`,
13882
14623
  undeclared.map((name) => ({
13883
14624
  path: `identities.${name}`,
13884
14625
  message: `'\${{ identities.${name} }}' is referenced but not declared in the 'identities' block`
@@ -13895,8 +14636,11 @@ async function resolveBlueprintIdentities(raw, resolve) {
13895
14636
  idMap[name] = id;
13896
14637
  } catch (err) {
13897
14638
  throw new BlueprintIdentityError(
13898
- `Failed to resolve identity '${name}' (${decl.kind} externalId=${decl.externalId}): ${err instanceof Error ? err.message : String(err)}`,
13899
- [{ path: `identities.${name}`, message: err instanceof Error ? err.message : String(err) }]
14639
+ `Failed to resolve identity '${displaySafe(name, 80)}' (${decl.kind} externalId=${displaySafe(decl.externalId, 80)}): ${displaySafeInline(
14640
+ err instanceof Error ? err.message : String(err),
14641
+ 500
14642
+ )}`,
14643
+ [{ path: `identities.${name}`, message: displaySafeInline(err instanceof Error ? err.message : String(err), 500) }]
13900
14644
  );
13901
14645
  }
13902
14646
  }
@@ -13909,13 +14653,13 @@ var InputDeclSchema = import_zod2.z.object({
13909
14653
  default: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number(), import_zod2.z.boolean()]).optional(),
13910
14654
  description: import_zod2.z.string().optional()
13911
14655
  }).strict();
13912
- var InputsDeclSchema = import_zod2.z.record(InputDeclSchema);
14656
+ var InputsDeclSchema = import_zod2.z.record(import_zod2.z.string(), InputDeclSchema);
13913
14657
  var BlueprintInputError = class extends Error {
13914
14658
  issues;
13915
14659
  constructor(message, issues = []) {
13916
- super(message);
14660
+ super(displaySafeLines(message));
13917
14661
  this.name = "BlueprintInputError";
13918
- this.issues = issues;
14662
+ this.issues = neutralizeIssues(issues);
13919
14663
  }
13920
14664
  };
13921
14665
  function renderIssues2(issues) {
@@ -14107,7 +14851,7 @@ function resolveBlueprintInputs(raw, supplied = {}) {
14107
14851
  const parsed = InputsDeclSchema.safeParse(rawInputs);
14108
14852
  if (!parsed.success) {
14109
14853
  for (const i of parsed.error.issues) {
14110
- const p = i.path.length ? `inputs.${i.path.join(".")}` : "inputs";
14854
+ const p = i.path.length ? `inputs.${i.path.map(String).join(".")}` : "inputs";
14111
14855
  issues.push({ path: p, message: i.message });
14112
14856
  }
14113
14857
  } else {
@@ -14144,8 +14888,9 @@ function resolveBlueprintInputs(raw, supplied = {}) {
14144
14888
  const ctx = { declared, values, builtins, issues };
14145
14889
  const substituted = substituteValue(body, ctx, "");
14146
14890
  if (issues.length) {
14891
+ const clean = neutralizeIssues(issues);
14147
14892
  throw new BlueprintInputError(`Blueprint variable resolution failed:
14148
- ${renderIssues2(issues)}`, issues);
14893
+ ${renderIssues2(clean)}`, clean);
14149
14894
  }
14150
14895
  return substituted;
14151
14896
  }
@@ -15750,6 +16495,48 @@ function createLogger2(ctx = {}) {
15750
16495
  );
15751
16496
  }
15752
16497
 
16498
+ // src/terminal-text.ts
16499
+ var STRIPPED = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
16500
+ function stripControlCharacters(value, maxLength = 200) {
16501
+ const characters = Array.from(value.replace(STRIPPED, ""));
16502
+ return characters.length > maxLength ? `${characters.slice(0, maxLength).join("")}...` : characters.join("");
16503
+ }
16504
+ function singleLine(value, maxLength = 200) {
16505
+ return stripControlCharacters(value.replace(/\r\n|[\r\n\u0085\u2028\u2029\t\v\f]/g, " "), maxLength);
16506
+ }
16507
+ function cleanBlock(value, maxLineLength = 2e3, limits = {}) {
16508
+ const maxLines = limits.maxLines ?? 200;
16509
+ const maxChars = limits.maxChars ?? 2e4;
16510
+ const lines = value.split(/\r\n|[\r\n\u0085\u2028\u2029]/);
16511
+ let cut = lines.length > maxLines;
16512
+ let text = lines.slice(0, maxLines).map((line) => stripControlCharacters(line.replace(/\t/g, " "), maxLineLength)).join("\n");
16513
+ if (text.length > maxChars) {
16514
+ text = text.slice(0, maxChars);
16515
+ cut = true;
16516
+ }
16517
+ return cut ? `${text}
16518
+ [truncated]` : text;
16519
+ }
16520
+ function shortPath(value, maxLength = 300) {
16521
+ const clean = Array.from(singleLine(value, Number.MAX_SAFE_INTEGER));
16522
+ if (clean.length <= maxLength) return clean.join("");
16523
+ const head = Math.floor((maxLength - 3) / 3);
16524
+ return `${clean.slice(0, head).join("")}...${clean.slice(clean.length - (maxLength - 3 - head)).join("")}`;
16525
+ }
16526
+ function errorText(err) {
16527
+ return cleanBlock(err instanceof Error ? err.message : String(err));
16528
+ }
16529
+ function quotedBlock(value, indent = " ") {
16530
+ return cleanBlock(value).split("\n").map((line, i) => i === 0 || line === "" ? line : `${indent}${line}`).join("\n");
16531
+ }
16532
+ function asText(value, fallback = "(unprintable)") {
16533
+ try {
16534
+ return String(value);
16535
+ } catch {
16536
+ return fallback;
16537
+ }
16538
+ }
16539
+
15753
16540
  // src/output.ts
15754
16541
  function formatJson(value) {
15755
16542
  return JSON.stringify(value, null, 2);
@@ -15791,19 +16578,19 @@ function extractServerError(err) {
15791
16578
  if (e.body && typeof e.body === "object") {
15792
16579
  const b = e.body;
15793
16580
  if (typeof b.message === "string" && b.message.trim()) serverMessage = b.message.trim();
15794
- if (typeof b.requestId === "string" && b.requestId.trim()) requestId = b.requestId.trim();
15795
- if (typeof b.errorCode === "string" && b.errorCode.trim()) errorCode = b.errorCode.trim();
16581
+ if (typeof b.requestId === "string" && b.requestId.trim()) requestId = singleLine(b.requestId.trim(), 100);
16582
+ if (typeof b.errorCode === "string" && b.errorCode.trim()) errorCode = singleLine(b.errorCode.trim(), 100);
15796
16583
  } else if (typeof e.body === "string" && e.body.trim()) {
15797
16584
  serverMessage = e.body.trim();
15798
16585
  }
15799
- if (serverMessage) return { statusCode, message: serverMessage, requestId, errorCode };
16586
+ if (serverMessage) return { statusCode, message: cleanBlock(serverMessage), requestId, errorCode };
15800
16587
  const message = typeof e.message === "string" ? e.message : "";
15801
16588
  const isSdkEnvelope = /\nStatus code:/.test(message);
15802
- if (message && !isSdkEnvelope) return { statusCode, message, requestId, errorCode };
16589
+ if (message && !isSdkEnvelope) return { statusCode, message: cleanBlock(message), requestId, errorCode };
15803
16590
  if (statusCode) return { statusCode, message: `HTTP ${statusCode}`, requestId, errorCode };
15804
- if (message) return { statusCode, message, requestId, errorCode };
16591
+ if (message) return { statusCode, message: cleanBlock(message), requestId, errorCode };
15805
16592
  }
15806
- return { message: String(err) };
16593
+ return { message: cleanBlock(String(err)) };
15807
16594
  }
15808
16595
  function formatError(err) {
15809
16596
  return extractServerError(err).message;
@@ -16021,6 +16808,33 @@ var PROVISION_OPTIONS = [
16021
16808
  ]
16022
16809
  }
16023
16810
  },
16811
+ {
16812
+ flag: "--allow-agent-adoption",
16813
+ stacked: true,
16814
+ modes: ["bootstrap", "apply"],
16815
+ help: {
16816
+ bootstrap: [
16817
+ "Required to write a blueprint's agent over an agent that already exists",
16818
+ "in this context but carries no blueprint mark (made by hand, or by a tool",
16819
+ "that set none) \u2014 without it, bootstrap refuses for that name, naming it,",
16820
+ "and writes nothing to the agent. Adopting marks the agent as this",
16821
+ "blueprint's and writes the declared definition as its next version if it",
16822
+ "differs, so every run using its latest version changes. Never needed for a",
16823
+ "brand-new agent, or one this blueprint already manages. Does NOT help if",
16824
+ "the agent is managed by a DIFFERENT blueprint \u2014 that is always refused."
16825
+ ],
16826
+ apply: [
16827
+ "Required to write a blueprint's agent over an agent that already exists",
16828
+ "in this context but carries no blueprint mark (made by hand, or by a tool",
16829
+ "that set none) \u2014 without it, apply refuses for that name, naming it,",
16830
+ "and writes nothing to the agent. Adopting marks the agent as this",
16831
+ "blueprint's and writes the declared definition as its next version if it",
16832
+ "differs, so every run using its latest version changes. Never needed for a",
16833
+ "brand-new agent, or one this blueprint already manages. Does NOT help if",
16834
+ "the agent is managed by a DIFFERENT blueprint \u2014 that is always refused."
16835
+ ]
16836
+ }
16837
+ },
16024
16838
  {
16025
16839
  flag: "--tenant",
16026
16840
  value: "test|live",
@@ -16162,16 +16976,24 @@ var PROVISION_OPTIONS = [
16162
16976
  "\u2014 e.g. a deliberate second machine for an integration you already own.",
16163
16977
  "Refused by default so an unexpected collision halts a CI run loudly",
16164
16978
  "instead of silently minting a credential bound to the wrong identity.",
16165
- "Never needed for --rotate or an ordinary same-machine re-apply."
16979
+ "Not needed for --rotate or an ordinary same-machine re-apply of the",
16980
+ "blueprint's own service principal (a trigger principal is the exception, below).",
16981
+ "Also required the same way when a trigger's service principal already exists",
16982
+ "and this blueprint did not mark it (made by an earlier release, or by hand).",
16983
+ "The first confirmation marks it as this blueprint's (needs users:u); then it is not asked again."
16166
16984
  ],
16167
16985
  apply: [
16168
16986
  "Required (with --yes, or with no TTY) when the blueprint's service principal",
16169
16987
  "already exists in your tenant. Apply never reads a local key record, so that is",
16170
16988
  "every apply once the principal exists, on any machine, including one that ran",
16171
- "bootstrap. It lets apply rewrite that principal's access profile (the",
16172
- "blueprint's own service principal only). In a pipeline this flag is STANDING",
16173
- "consent: it also waves through a blueprint whose servicePrincipal.externalId",
16174
- "collides with an unrelated identity, so keep the blueprint's source reviewed.",
16989
+ "bootstrap. It lets apply rewrite that principal's access profile. It also",
16990
+ "covers a trigger's service principal that already exists and that this",
16991
+ "blueprint did not mark (made by an earlier release, or by hand): the first",
16992
+ "confirmation marks it as this blueprint's (needs users:u; apply says if it",
16993
+ "could not), and it is not asked again. In a",
16994
+ "pipeline this flag is STANDING consent: it also waves through a blueprint whose",
16995
+ "servicePrincipal.externalId or trigger name collides with an unrelated identity,",
16996
+ "so keep the blueprint's source reviewed.",
16175
16997
  "Every run that proceeds prints which principal it reused and why."
16176
16998
  ]
16177
16999
  }
@@ -16487,13 +17309,6 @@ var import_promises = require("fs/promises");
16487
17309
  var import_node_os = require("os");
16488
17310
  var import_node_path = require("path");
16489
17311
 
16490
- // src/terminal-text.ts
16491
- var STRIPPED = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
16492
- function stripControlCharacters(value, maxLength = 200) {
16493
- const characters = Array.from(value.replace(STRIPPED, ""));
16494
- return characters.length > maxLength ? `${characters.slice(0, maxLength).join("")}...` : characters.join("");
16495
- }
16496
-
16497
17312
  // src/pkce.ts
16498
17313
  var import_node_crypto = require("crypto");
16499
17314
 
@@ -16794,10 +17609,10 @@ function parseDocument(raw, filePath) {
16794
17609
  const ext = (0, import_node_path2.extname)(filePath).toLowerCase();
16795
17610
  if (ext === ".yaml" || ext === ".yml") {
16796
17611
  try {
16797
- return (0, import_yaml.parse)(raw);
17612
+ return (0, import_yaml.parse)(raw, { logLevel: "error" });
16798
17613
  } catch (err) {
16799
17614
  if (err instanceof import_yaml.YAMLParseError) {
16800
- throw new Error(`Blueprint YAML is invalid in '${filePath}': ${err.message}`);
17615
+ throw new Error(`Blueprint YAML is invalid in '${shortPath(filePath, 300)}': ${quotedBlock(err.message)}`);
16801
17616
  }
16802
17617
  throw err;
16803
17618
  }
@@ -16806,7 +17621,7 @@ function parseDocument(raw, filePath) {
16806
17621
  return JSON.parse(raw);
16807
17622
  } catch (err) {
16808
17623
  throw new BlueprintValidationError(
16809
- `Blueprint is not valid JSON in '${filePath}': ${err instanceof Error ? err.message : String(err)}`
17624
+ `Blueprint is not valid JSON in '${shortPath(filePath, 300)}': ${quotedBlock(err instanceof Error ? err.message : String(err))}`
16810
17625
  );
16811
17626
  }
16812
17627
  }
@@ -16816,7 +17631,7 @@ async function loadBlueprintFile(filePath, supplied = {}) {
16816
17631
  raw = await (0, import_promises2.readFile)(filePath, "utf8");
16817
17632
  } catch (err) {
16818
17633
  const msg = err instanceof Error ? err.message : String(err);
16819
- throw new Error(`Cannot read blueprint file '${filePath}': ${msg}`);
17634
+ throw new Error(`Cannot read blueprint file '${shortPath(filePath, 300)}': ${quotedBlock(msg)}`);
16820
17635
  }
16821
17636
  const parsed = parseDocument(raw, filePath);
16822
17637
  const resolved = resolveBlueprintInputs(parsed, supplied);
@@ -16838,11 +17653,11 @@ async function loadValuesFile(filePath) {
16838
17653
  try {
16839
17654
  raw = await (0, import_promises2.readFile)(filePath, "utf8");
16840
17655
  } catch (err) {
16841
- throw new Error(`Cannot read --values file '${filePath}': ${err instanceof Error ? err.message : String(err)}`);
17656
+ throw new Error(`Cannot read --values file '${shortPath(filePath, 300)}': ${quotedBlock(err instanceof Error ? err.message : String(err))}`);
16842
17657
  }
16843
17658
  const parsed = parseDocument(raw, filePath);
16844
17659
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
16845
- throw new Error(`--values file '${filePath}' must be a map of name: value pairs.`);
17660
+ throw new Error(`--values file '${shortPath(filePath, 300)}' must be a map of name: value pairs.`);
16846
17661
  }
16847
17662
  return parsed;
16848
17663
  }
@@ -16861,7 +17676,8 @@ var DATA_PLANE_ALLOWLIST = [
16861
17676
  "documents",
16862
17677
  "folders",
16863
17678
  "inference",
16864
- "entities"
17679
+ "entities",
17680
+ "runs"
16865
17681
  ];
16866
17682
  var KNOWN_CONTROL_PLANE_RESOURCES = [
16867
17683
  "keys",
@@ -16903,7 +17719,10 @@ var KNOWN_CONTROL_PLANE_RESOURCES = [
16903
17719
  // no admitted resource has an `x` verb — adding it would instead make `records:x` pass the op
16904
17720
  // check and be admitted as data-plane.
16905
17721
  "scripts",
16906
- "triggers"
17722
+ "triggers",
17723
+ // Stored agent definitions: authoring one steers every run of it, so the platform treats it as
17724
+ // control-plane on every verb. Listed for MESSAGE QUALITY, as above.
17725
+ "agents"
16907
17726
  ];
16908
17727
  var PARTIALLY_ADMITTED_OPS = /* @__PURE__ */ new Map([
16909
17728
  ["profiles", /* @__PURE__ */ new Set(["r", "u", "d"])]
@@ -17141,6 +17960,9 @@ function assertBlueprintGates(blueprint) {
17141
17960
  }
17142
17961
  }
17143
17962
  for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
17963
+ const carried = entry.allowedActions ?? (entry.roleIds ?? []).flatMap((id) => (blueprint.roles?.[id] ?? []).flatMap((clause) => clause.allowedActions));
17964
+ const refused = triggerRefusedGrants(carried);
17965
+ if (refused.length > 0) throw withTriggerContext(new ScopeGateError(refused), triggerName);
17144
17966
  if (entry.allowedActions === void 0) continue;
17145
17967
  try {
17146
17968
  assertScopeGate(entry.allowedActions, entry.capabilities);
@@ -17179,6 +18001,20 @@ function assertScriptGrantsNameADeclaredScript(blueprint) {
17179
18001
  }
17180
18002
  if (rejected.length > 0) throw new ScopeGateError(rejected);
17181
18003
  }
18004
+ var TRIGGER_REFUSED_RESOURCES = /* @__PURE__ */ new Set(["runs"]);
18005
+ function triggerRefusedGrants(actions) {
18006
+ const out = [];
18007
+ for (const action of actions) {
18008
+ const resource = action.trim().split(":")[0];
18009
+ if (TRIGGER_REFUSED_RESOURCES.has(resource)) {
18010
+ out.push({
18011
+ action,
18012
+ reason: `requests '${resource}' for a trigger. A trigger principal is unattended and fires on every matching write, so it may not be granted '${resource}'; grant it to a role or the blueprint's own credential instead`
18013
+ });
18014
+ }
18015
+ }
18016
+ return out;
18017
+ }
17182
18018
  function withTriggerContext(err, triggerName) {
17183
18019
  if (err instanceof ScopeGateError || err instanceof CapabilityGateError) {
17184
18020
  for (const r of err.rejected) r.source ??= `trigger '${triggerName}'`;
@@ -17476,6 +18312,45 @@ async function drainPages(fetch2) {
17476
18312
  return out;
17477
18313
  }
17478
18314
 
18315
+ // src/agent-api.ts
18316
+ var AGENT_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
18317
+ var APPLY_AGENT_SCOPES = "'agents:c', plus 'agents:u' and 'agents:r' when the agent already exists (changed or not)";
18318
+ function agentErrorHint(err, contextId, scopes) {
18319
+ const info = extractServerError(err);
18320
+ if (info.statusCode === 403) {
18321
+ return `This needs the ${scopes} scope on the credential, in context '${contextId}'. Grant it to the principal doing this, or use a credential that holds it.`;
18322
+ }
18323
+ if (info.statusCode === 400 && /already in use/i.test(info.message)) {
18324
+ return "If the agent exists, this credential may be unable to read it: re-applying or writing over an existing agent needs 'agents:r' and 'agents:u' as well as 'agents:c'.";
18325
+ }
18326
+ if (info.errorCode === "AGENT_RETIRED" || info.statusCode === 409 && /\b(retired|purged)\b/i.test(info.message)) {
18327
+ return "A retired agent keeps its name. Restore it with `vectros agents version <id> --restore --file <definition>` (a purged agent cannot be restored), or remove the agent from the blueprint.";
18328
+ }
18329
+ return "";
18330
+ }
18331
+ function agentRequestError(err, subject, contextId, scopes) {
18332
+ const info = extractServerError(err);
18333
+ const hint = agentErrorHint(err, contextId, scopes);
18334
+ const message = singleLine(info.message, 500);
18335
+ if (info.statusCode === 403) {
18336
+ return new Error(`${subject}: the platform refused the request (403). ${hint} Platform message: ${message}`, { cause: err });
18337
+ }
18338
+ return new Error(`${subject}: ${message}${hint ? ` ${hint}` : ""}`, { cause: err });
18339
+ }
18340
+ function sanitizedAgentError(err) {
18341
+ const info = extractServerError(err);
18342
+ const message = singleLine(info.message, 500);
18343
+ return Object.assign(new Error(message), {
18344
+ statusCode: info.statusCode,
18345
+ body: {
18346
+ message,
18347
+ requestId: info.requestId === void 0 ? void 0 : singleLine(info.requestId, 100),
18348
+ errorCode: info.errorCode === void 0 ? void 0 : singleLine(info.errorCode, 100)
18349
+ },
18350
+ cause: err
18351
+ });
18352
+ }
18353
+
17479
18354
  // src/shell-quote.ts
17480
18355
  function shellQuote(value) {
17481
18356
  if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
@@ -17489,6 +18364,19 @@ function actionOf(created) {
17489
18364
  function triggerPrincipalExternalId(blueprintName, triggerName) {
17490
18365
  return `${blueprintName}-trigger-${triggerName}`;
17491
18366
  }
18367
+ function triggerPrincipalProvenance(payload, blueprintName) {
18368
+ const marker = payload?.provisionedBy;
18369
+ if (marker === blueprintName) return { kind: "ours" };
18370
+ if (typeof marker === "string" && marker.length > 0) return { kind: "other-blueprint", markedBy: marker };
18371
+ return { kind: "unmarked" };
18372
+ }
18373
+ var SENSITIVE_MASK = "[redacted]";
18374
+ function payloadIsMasked(value) {
18375
+ if (value === SENSITIVE_MASK) return true;
18376
+ if (Array.isArray(value)) return value.some(payloadIsMasked);
18377
+ if (value !== null && typeof value === "object") return Object.values(value).some(payloadIsMasked);
18378
+ return false;
18379
+ }
17492
18380
  var SCRIPT_CONFLICT_RETRY_DELAY_MS = 750;
17493
18381
  function isVersionConflict(err) {
17494
18382
  return typeof err === "object" && err !== null && err.statusCode === 409;
@@ -17542,7 +18430,7 @@ function scriptMatches(stored, declared) {
17542
18430
  var ScriptOwnershipConflictError = class extends Error {
17543
18431
  constructor(name, owner, blueprintName) {
17544
18432
  super(
17545
- `Script '${name}' is already provisioned by a different blueprint ('${owner}', this blueprint is '${blueprintName}') \u2014 refusing to push over it. No script version is pushed (anything already provisioned earlier in this same apply is unaffected). If you intended to replace that blueprint's script, do it under ITS name; if two blueprints legitimately need distinct code, give this one a different script name.`
18433
+ `Script '${name}' is already provisioned by a different blueprint ('${singleLine(owner, 100)}', this blueprint is '${blueprintName}') \u2014 refusing to push over it. No script version is pushed (anything already provisioned earlier in this same apply is unaffected). If you intended to replace that blueprint's script, do it under ITS name; if two blueprints legitimately need distinct code, give this one a different script name.`
17546
18434
  );
17547
18435
  this.name = "ScriptOwnershipConflictError";
17548
18436
  }
@@ -17655,6 +18543,86 @@ async function ensureScripts(args) {
17655
18543
  }
17656
18544
  return actions;
17657
18545
  }
18546
+ var AgentAdoptionNotAllowedError = class extends Error {
18547
+ constructor(agentName) {
18548
+ super(
18549
+ `Agent '${agentName}' already exists in this context and no blueprint manages it (it was made by hand, or by a tool that set no mark). Applying would mark it as this blueprint's and, if the declared definition differs, write that definition as its next version, so every run that uses the agent's latest version would start behaving differently. Nothing has been written to it, and its earlier versions stay readable. Re-run with --allow-agent-adoption once you have confirmed this blueprint should take over '${agentName}'. (Anything provisioned earlier in this same apply is unaffected.)`
18550
+ );
18551
+ this.agentName = agentName;
18552
+ this.name = "AgentAdoptionNotAllowedError";
18553
+ }
18554
+ agentName;
18555
+ };
18556
+ var AgentOwnershipConflictError = class extends Error {
18557
+ constructor(agentName, markedBy, blueprintName) {
18558
+ super(
18559
+ `Agent '${agentName}' is managed by the blueprint '${singleLine(markedBy, 100)}', not this one ('${blueprintName}'). Nothing has been written to it. An agent's mark cannot be changed, so give this blueprint's agent a different name, or apply the blueprint that manages it.`
18560
+ );
18561
+ this.agentName = agentName;
18562
+ this.markedBy = markedBy;
18563
+ this.name = "AgentOwnershipConflictError";
18564
+ }
18565
+ agentName;
18566
+ markedBy;
18567
+ };
18568
+ function isDefiniteRefusal(err) {
18569
+ const status = err?.statusCode;
18570
+ return typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429;
18571
+ }
18572
+ async function ensureAgents(args) {
18573
+ const { ctxClient, agents, blueprintName, contextId, allowAgentAdoption, onAgentCreated, onAgentSeen, onAgentAttempt, onAgentRefused, log } = args;
18574
+ const actions = [];
18575
+ const trackedByCaller = onAgentAttempt !== void 0;
18576
+ const write = async (name, upsert) => {
18577
+ try {
18578
+ return await ctxClient.agents.createAgent(
18579
+ {
18580
+ contextId,
18581
+ ...upsert ? { upsert: true } : {},
18582
+ body: { ...agents[name], name, provisionedBy: blueprintName }
18583
+ },
18584
+ // The caller is keeping track of the agents it creates so that it can remove them. A create the client retried by
18585
+ // itself after a gateway error would come back as an agent that already existed, hiding that this call made it.
18586
+ trackedByCaller ? { maxRetries: 0 } : void 0
18587
+ );
18588
+ } catch (err) {
18589
+ if (isDefiniteRefusal(err)) onAgentRefused?.(name);
18590
+ throw agentRequestError(err, `Agent '${name}'`, contextId, APPLY_AGENT_SCOPES);
18591
+ }
18592
+ };
18593
+ const versionOf = (name, answer) => {
18594
+ const version = answer.version ?? answer.latestVersion;
18595
+ if (!answer.id || version === void 0) {
18596
+ throw new Error(`Agent '${name}': the platform's answer named no agent id or version.`);
18597
+ }
18598
+ return version;
18599
+ };
18600
+ for (const name of Object.keys(agents)) {
18601
+ if (onAgentAttempt?.(name) === false) break;
18602
+ const first = await write(name, false);
18603
+ if (!first.created) onAgentSeen?.({ name, id: first.id });
18604
+ const before = versionOf(name, first);
18605
+ if (first.created) {
18606
+ onAgentCreated?.({ name, id: first.id });
18607
+ actions.push({ name, action: "created", id: first.id, version: before });
18608
+ log.debug({ component: "loader", agentName: name, agentVersion: before }, "agent created at version 1");
18609
+ continue;
18610
+ }
18611
+ const owner = first.provisionedBy?.trim() || void 0;
18612
+ if (owner !== void 0 && owner !== blueprintName) throw new AgentOwnershipConflictError(name, owner, blueprintName);
18613
+ const adopting = owner === void 0;
18614
+ if (adopting && !allowAgentAdoption) throw new AgentAdoptionNotAllowedError(name);
18615
+ const second = await write(name, true);
18616
+ const after = versionOf(name, second);
18617
+ const action = adopting ? "adopted" : after > before ? "versioned" : "unchanged";
18618
+ actions.push({ name, action, id: first.id, version: after, previousVersion: before });
18619
+ log.debug(
18620
+ { component: "loader", agentName: name, agentVersionBefore: before, agentVersionAfter: after, action },
18621
+ adopting ? "agent adopted into this blueprint" : action === "versioned" ? "agent definition changed \u2014 next version written" : "agent already current"
18622
+ );
18623
+ }
18624
+ return actions;
18625
+ }
17658
18626
  async function pruneUndeclaredTriggers(args) {
17659
18627
  const { ctxClient, blueprintName, contextId, declaredNames, log } = args;
17660
18628
  const pruned = [];
@@ -17836,21 +18804,41 @@ var LostKeyError = class extends Error {
17836
18804
  }
17837
18805
  keyName;
17838
18806
  };
17839
- var ExistingPrincipalNotConfirmedError = class extends Error {
17840
- constructor(contextId, userId, externalId, mintsKey = true) {
18807
+ var ExistingPrincipalNotConfirmedError = class extends Error {
18808
+ constructor(contextId, userId, externalId, mintsKey = true) {
18809
+ super(
18810
+ mintsKey ? `About to bind a NEW scoped key to a principal that already existed in this tenant before this run (externalId '${externalId}', userId '${userId}', context '${contextId}'). This is either an intentional multi-machine setup for an integration you already own, or this blueprint's servicePrincipal.externalId collided with an unrelated existing identity \u2014 confirm before a new credential is minted bound to it.` : `About to write the grants of a principal that already existed in this tenant before this run (externalId '${externalId}', userId '${userId}', context '${contextId}'). This is either a re-apply of a blueprint you already manage, or this blueprint's servicePrincipal.externalId collided with an unrelated existing identity \u2014 confirm before that identity's access profile is changed.`
18811
+ );
18812
+ this.contextId = contextId;
18813
+ this.userId = userId;
18814
+ this.externalId = externalId;
18815
+ this.mintsKey = mintsKey;
18816
+ this.name = "ExistingPrincipalNotConfirmedError";
18817
+ }
18818
+ contextId;
18819
+ userId;
18820
+ externalId;
18821
+ mintsKey;
18822
+ };
18823
+ var ExistingTriggerPrincipalNotConfirmedError = class extends Error {
18824
+ constructor(contextId, triggerName, userId, externalId, provenance, markedBy) {
17841
18825
  super(
17842
- mintsKey ? `About to bind a NEW scoped key to a principal that already existed in this tenant before this run (externalId '${externalId}', userId '${userId}', context '${contextId}'). This is either an intentional multi-machine setup for an integration you already own, or this blueprint's servicePrincipal.externalId collided with an unrelated existing identity \u2014 confirm before a new credential is minted bound to it.` : `About to write the grants of a principal that already existed in this tenant before this run (externalId '${externalId}', userId '${userId}', context '${contextId}'). This is either a re-apply of a blueprint you already manage, or this blueprint's servicePrincipal.externalId collided with an unrelated existing identity \u2014 confirm before that identity's access profile is changed.`
18826
+ `The service principal for trigger '${triggerName}' (externalId '${externalId}', userId '${userId}', context '${contextId}') already existed in this tenant and ` + (provenance === "other-blueprint" ? `is marked as provisioned by the blueprint '${singleLine(markedBy ?? "", 100)}', not this one. ` : `carries no provenance marker, so it was not created by this blueprint's trigger provisioning (it may be from an earlier CLI release, or created by hand). `) + `Applying would write that identity's access profile to this blueprint's trigger grant, and nothing has been written to it yet.`
17843
18827
  );
17844
18828
  this.contextId = contextId;
18829
+ this.triggerName = triggerName;
17845
18830
  this.userId = userId;
17846
18831
  this.externalId = externalId;
17847
- this.mintsKey = mintsKey;
17848
- this.name = "ExistingPrincipalNotConfirmedError";
18832
+ this.provenance = provenance;
18833
+ this.markedBy = markedBy;
18834
+ this.name = "ExistingTriggerPrincipalNotConfirmedError";
17849
18835
  }
17850
18836
  contextId;
18837
+ triggerName;
17851
18838
  userId;
17852
18839
  externalId;
17853
- mintsKey;
18840
+ provenance;
18841
+ markedBy;
17854
18842
  };
17855
18843
  function mapEnumValues(enumValues) {
17856
18844
  if (!enumValues) return void 0;
@@ -17941,6 +18929,9 @@ var UnresolvedIdentitiesError = class extends Error {
17941
18929
  this.name = "UnresolvedIdentitiesError";
17942
18930
  }
17943
18931
  };
18932
+ function declaresAgents(blueprint) {
18933
+ return Object.keys(blueprint.agents ?? {}).length > 0;
18934
+ }
17944
18935
  function assertNoUnresolvedIdentities(blueprint) {
17945
18936
  const hasBlock = blueprint.identities && Object.keys(blueprint.identities).length > 0;
17946
18937
  const refs = collectIdentityReferences(blueprint);
@@ -18075,6 +19066,11 @@ async function runLoader(client, input) {
18075
19066
  readExistingKey,
18076
19067
  persistMintedKey,
18077
19068
  allowScriptAdoption = false,
19069
+ allowAgentAdoption = false,
19070
+ onAgentCreated,
19071
+ onAgentSeen,
19072
+ onAgentAttempt,
19073
+ onAgentRefused,
18078
19074
  log
18079
19075
  } = input;
18080
19076
  if (skipKey && rotate) {
@@ -18083,9 +19079,66 @@ async function runLoader(client, input) {
18083
19079
  assertNoUnresolvedIdentities(blueprint);
18084
19080
  const { contextAction, servicePrincipalUserId: userId, userAction, namespaceActions } = provisioned;
18085
19081
  assertBlueprintGates(blueprint);
19082
+ const triggerPrincipalIds = /* @__PURE__ */ new Map();
19083
+ const triggerPrincipalsToMark = /* @__PURE__ */ new Map();
19084
+ const pendingConfirmation = [];
19085
+ for (const triggerName of Object.keys(blueprint.triggers ?? {})) {
19086
+ const externalId = triggerPrincipalExternalId(blueprint.name, triggerName);
19087
+ const displayName = `Trigger: ${triggerName}`;
19088
+ const triggerUser = await client.identity.createUser({
19089
+ body: { externalId, type: "SERVICE", payload: { displayName, provisionedBy: blueprint.name } }
19090
+ });
19091
+ const triggerUserId = triggerUser.id;
19092
+ if (!triggerUserId) {
19093
+ throw new Error(`Trigger '${triggerName}' service-principal create returned no user id.`);
19094
+ }
19095
+ triggerPrincipalIds.set(triggerName, triggerUserId);
19096
+ if (triggerUser.created) continue;
19097
+ const provenance = triggerPrincipalProvenance(triggerUser.payload, blueprint.name);
19098
+ if (provenance.kind === "ours") continue;
19099
+ pendingConfirmation.push({
19100
+ triggerName,
19101
+ externalId,
19102
+ userId: triggerUserId,
19103
+ userType: triggerUser.type,
19104
+ provenance: provenance.kind,
19105
+ markedBy: provenance.kind === "other-blueprint" ? provenance.markedBy : void 0
19106
+ });
19107
+ }
19108
+ let askedTheOperator = false;
19109
+ for (const p of pendingConfirmation) {
19110
+ let proceed = true;
19111
+ if (input.confirmExistingTriggerPrincipal) {
19112
+ askedTheOperator = true;
19113
+ proceed = await input.confirmExistingTriggerPrincipal({
19114
+ contextId: blueprint.contextId,
19115
+ triggerName: p.triggerName,
19116
+ userId: p.userId,
19117
+ externalId: p.externalId,
19118
+ provenance: p.provenance,
19119
+ markedBy: p.markedBy,
19120
+ userType: p.userType
19121
+ });
19122
+ }
19123
+ if (!proceed) {
19124
+ throw new ExistingTriggerPrincipalNotConfirmedError(
19125
+ blueprint.contextId,
19126
+ p.triggerName,
19127
+ p.userId,
19128
+ p.externalId,
19129
+ p.provenance,
19130
+ p.markedBy
19131
+ );
19132
+ }
19133
+ if (input.confirmExistingTriggerPrincipal) {
19134
+ triggerPrincipalsToMark.set(p.triggerName, { externalId: p.externalId, userId: p.userId, userType: p.userType });
19135
+ }
19136
+ }
19137
+ const triggerPrincipalsReused = [];
18086
19138
  const needsContextPinnedBootstrapForIssuers = (blueprint.issuers ?? []).length > 0;
18087
- const ctxBootstrapClient = provisioned.ctxBootstrapClient ?? (needsContextPinnedBootstrapForIssuers ? input.remintBootstrapForContext ? await input.remintBootstrapForContext(blueprint.contextId) : client : void 0);
18088
- if (ctxBootstrapClient && !provisioned.ctxBootstrapClient && input.remintBootstrapForContext) {
19139
+ const staleAfterPrompt = askedTheOperator && needsContextPinnedBootstrapForIssuers && !!input.remintBootstrapForContext;
19140
+ const ctxBootstrapClient = (staleAfterPrompt ? await input.remintBootstrapForContext(blueprint.contextId) : void 0) ?? provisioned.ctxBootstrapClient ?? (needsContextPinnedBootstrapForIssuers ? input.remintBootstrapForContext ? await input.remintBootstrapForContext(blueprint.contextId) : client : void 0);
19141
+ if (ctxBootstrapClient && (staleAfterPrompt || !provisioned.ctxBootstrapClient) && input.remintBootstrapForContext) {
18089
19142
  log.debug({ component: "loader", contextId: blueprint.contextId }, "re-minted context-pinned bootstrap client (issuers)");
18090
19143
  }
18091
19144
  const issuerActions = [];
@@ -18267,16 +19320,9 @@ async function runLoader(client, input) {
18267
19320
  `Trigger '${triggerName}' names schema '${entry.firingSource.schemaName}', which has no resolved schemaId \u2014 the schema failed to provision.`
18268
19321
  );
18269
19322
  }
18270
- const triggerUser = await client.identity.createUser({
18271
- body: {
18272
- externalId: triggerPrincipalExternalId(blueprint.name, triggerName),
18273
- type: "SERVICE",
18274
- payload: { displayName: `Trigger: ${triggerName}` }
18275
- }
18276
- });
18277
- const triggerUserId = triggerUser.id;
19323
+ const triggerUserId = triggerPrincipalIds.get(triggerName);
18278
19324
  if (!triggerUserId) {
18279
- throw new Error(`Trigger '${triggerName}' service-principal create returned no user id.`);
19325
+ throw new Error(`Trigger '${triggerName}' has no resolved service principal.`);
18280
19326
  }
18281
19327
  const triggerPlaceholderProfileBody = {
18282
19328
  principalId: `usr_${triggerUserId}`,
@@ -18341,6 +19387,44 @@ async function runLoader(client, input) {
18341
19387
  { component: "loader", triggerName, principalId: `usr_${triggerUserId}` },
18342
19388
  "trigger access profile widened to its declared grant"
18343
19389
  );
19390
+ const toMark = triggerPrincipalsToMark.get(triggerName);
19391
+ if (toMark) {
19392
+ let reason;
19393
+ try {
19394
+ if (toMark.userType !== void 0 && toMark.userType !== "SERVICE") {
19395
+ reason = `it is a ${toMark.userType} user, not a service principal`;
19396
+ } else {
19397
+ const fresh = await ctxClient.identity.getUser({ id: toMark.userId });
19398
+ const payload = fresh.payload;
19399
+ if (fresh.type !== void 0 && fresh.type !== "SERVICE") {
19400
+ reason = `it is a ${fresh.type} user, not a service principal`;
19401
+ } else if (payloadIsMasked(payload)) {
19402
+ reason = "its payload is masked for this credential (a schema marks fields sensitive), so writing it back would replace real values";
19403
+ } else {
19404
+ await ctxClient.identity.createUser({
19405
+ upsert: true,
19406
+ body: {
19407
+ externalId: toMark.externalId,
19408
+ type: "SERVICE",
19409
+ payload: {
19410
+ ...payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : {},
19411
+ provisionedBy: blueprint.name
19412
+ }
19413
+ }
19414
+ });
19415
+ }
19416
+ }
19417
+ } catch (err) {
19418
+ reason = err instanceof Error ? err.message : String(err);
19419
+ }
19420
+ if (reason !== void 0) {
19421
+ log.warn(
19422
+ { component: "loader", triggerName, reason },
19423
+ "could not mark the confirmed trigger principal as this blueprint's; the next run will ask again"
19424
+ );
19425
+ }
19426
+ triggerPrincipalsReused.push({ triggerName, externalId: toMark.externalId, marked: reason === void 0, ...reason !== void 0 ? { reason } : {} });
19427
+ }
18344
19428
  }
18345
19429
  let triggerPrunes = [];
18346
19430
  let triggerPruneSkipped = [];
@@ -18363,6 +19447,18 @@ async function runLoader(client, input) {
18363
19447
  log
18364
19448
  }));
18365
19449
  }
19450
+ const agentActions = declaresAgents(blueprint) ? await ensureAgents({
19451
+ ctxClient,
19452
+ agents: blueprint.agents ?? {},
19453
+ blueprintName: blueprint.name,
19454
+ contextId: blueprint.contextId,
19455
+ allowAgentAdoption,
19456
+ onAgentCreated,
19457
+ onAgentSeen,
19458
+ onAgentAttempt,
19459
+ onAgentRefused,
19460
+ log
19461
+ }) : [];
18366
19462
  const keyName = skipKey ? "" : keyNameFor(blueprint.name, machineId);
18367
19463
  let keyAction = skipKey ? "skipped" : "created";
18368
19464
  let revokedKeyId;
@@ -18523,7 +19619,9 @@ async function runLoader(client, input) {
18523
19619
  schemaActions,
18524
19620
  roleActions,
18525
19621
  scriptActions,
19622
+ agentActions,
18526
19623
  triggerActions,
19624
+ triggerPrincipalsReused,
18527
19625
  triggerPrunes,
18528
19626
  triggerPruneSkipped,
18529
19627
  issuerActions,
@@ -18567,6 +19665,12 @@ function buildPlan(blueprint, opts = {}) {
18567
19665
  summary: `namespace '${ns.namespace}' (${ownerNote}${entityNote}${membershipNote}) [${BLUEPRINT_FIELD_PHASES.namespaces}-token phase]`
18568
19666
  });
18569
19667
  }
19668
+ for (const triggerName of Object.keys(blueprint.triggers ?? {})) {
19669
+ steps.push({
19670
+ kind: "principal",
19671
+ summary: `trigger '${triggerName}' principal '${triggerPrincipalExternalId(blueprint.name, triggerName)}' (SERVICE, marked as this blueprint's; an existing one without that mark needs confirmation)`
19672
+ });
19673
+ }
18570
19674
  for (const iss of blueprint.issuers ?? []) {
18571
19675
  const issuerUrl = sanitizeForPlanDisplay(iss.issuer);
18572
19676
  const jwksNote = `, jwksUri: ${sanitizeForPlanDisplay(iss.jwksUri)}`;
@@ -18638,10 +19742,6 @@ function buildPlan(blueprint, opts = {}) {
18638
19742
  });
18639
19743
  }
18640
19744
  for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
18641
- steps.push({
18642
- kind: "principal",
18643
- summary: `trigger '${triggerName}' principal '${blueprint.name}-trigger-${triggerName}' (SERVICE)`
18644
- });
18645
19745
  steps.push({
18646
19746
  kind: "profile",
18647
19747
  summary: `trigger '${triggerName}' profile: minimal placeholder grant (minted before the trigger row exists)`
@@ -18666,6 +19766,19 @@ function buildPlan(blueprint, opts = {}) {
18666
19766
  });
18667
19767
  }
18668
19768
  }
19769
+ for (const [agentName, entry] of Object.entries(blueprint.agents ?? {})) {
19770
+ const tools = (entry.tools ?? []).map((t) => t.allowWrites ? `${sanitizeForPlanDisplay(t.name, 64)} (writes)` : sanitizeForPlanDisplay(t.name, 64));
19771
+ const parts = [`model ${sanitizeForPlanDisplay(entry.model)}`, tools.length ? `tools [${tools.join(", ")}]` : "no tools"];
19772
+ if (entry.authority !== void 0) parts.push(`authority ${sanitizeForPlanDisplay(entry.authority, 32)}`);
19773
+ const limits = Object.entries(entry.limits ?? {}).filter(([, v]) => v !== void 0);
19774
+ if (limits.length) parts.push(`limits {${limits.map(([k, v]) => `${k} ${v}`).join(", ")}}`);
19775
+ if (entry.budget?.maxInferenceCents !== void 0) parts.push(`budget ${entry.budget.maxInferenceCents} cents per run`);
19776
+ if (entry.system) parts.push(`system prompt ${Array.from(entry.system).length} characters`);
19777
+ steps.push({
19778
+ kind: "agent",
19779
+ summary: `agent '${sanitizeForPlanDisplay(agentName, 64)}' (ensure the stored definition matches: ${parts.join(", ")}; a changed definition becomes the next version) [${BLUEPRINT_FIELD_PHASES.agents}-token phase]`
19780
+ });
19781
+ }
18669
19782
  if (!opts.noKey) {
18670
19783
  steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
18671
19784
  }
@@ -18718,6 +19831,19 @@ function renderPlan(blueprint, steps, opts = {}) {
18718
19831
  ` and would break any trigger still referencing it. Remove one deliberately with`,
18719
19832
  ` 'vectros scripts delete', after 'vectros scripts list' shows you what is there.`
18720
19833
  );
19834
+ if (steps.some((s) => s.kind === "agent")) {
19835
+ legendLines.push(
19836
+ ` Counter-exception: an agent is never edited in place \u2014 a changed definition ADDS a new`,
19837
+ ` immutable version, and an unchanged one writes nothing. Agents are not reconciled by`,
19838
+ ` absence either: one this blueprint declared earlier and no longer does stays stored and`,
19839
+ ` runnable. Retire it deliberately through the API (DELETE /v1/agents/{id}).`,
19840
+ ` An agent that already exists and that nothing manages (made by hand) is REFUSED unless`,
19841
+ ` --allow-agent-adoption is passed: adopting it marks it as this blueprint's and gives it`,
19842
+ ` a new version if the definition differs. Another blueprint's agent is refused, and so`,
19843
+ ` is a retired one. Writing agents needs 'agents:c' on the applying credential, plus`,
19844
+ ` 'agents:u' and 'agents:r' for an agent that already exists.`
19845
+ );
19846
+ }
18721
19847
  if (blueprint.issuers?.some((i) => i.restrictedToDomain === void 0)) {
18722
19848
  legendLines.push(
18723
19849
  ` Note: a newly registered issuer without restrictedToDomain starts pending verification on platform`,
@@ -18792,7 +19918,7 @@ function displayChallengeValue(value) {
18792
19918
  return value === void 0 ? void 0 : printable(value) ?? WITHHELD;
18793
19919
  }
18794
19920
  function renderChallengeBlock(view, ctx, now) {
18795
- const id = view.issuerId ?? "(unknown)";
19921
+ const id = singleLine(String(view.issuerId ?? "(unknown)"), 100);
18796
19922
  const rawClaim = view.verificationClaim;
18797
19923
  const rawNonce = view.verificationNonce;
18798
19924
  const claim = rawClaim === void 0 ? "(not reported)" : printable(rawClaim) ?? WITHHELD;
@@ -18837,7 +19963,7 @@ function renderChallengeBlock(view, ctx, now) {
18837
19963
  `;
18838
19964
  }
18839
19965
  function renderExpiredBlock(view, ctx, rerun) {
18840
- const id = view.issuerId ?? "(unknown)";
19966
+ const id = singleLine(String(view.issuerId ?? "(unknown)"), 100);
18841
19967
  const expired = view.verificationExpiresAt === void 0 ? "(unknown)" : printable(view.verificationExpiresAt) ?? WITHHELD;
18842
19968
  const lines = [];
18843
19969
  lines.push(` \u2716 issuer '${id}' is STILL pending verification and its challenge EXPIRED at ${expired} (by this machine's clock).`);
@@ -20972,6 +22098,7 @@ function parseBootstrapArgs(argv) {
20972
22098
  noSeed: false,
20973
22099
  allowTenantWideNamespaces: false,
20974
22100
  allowScriptAdoption: false,
22101
+ allowAgentAdoption: false,
20975
22102
  requireVerifiedIssuers: false,
20976
22103
  client: "desktop",
20977
22104
  tenant: "live",
@@ -21040,6 +22167,9 @@ function parseBootstrapArgs(argv) {
21040
22167
  case "--allow-script-adoption":
21041
22168
  args.allowScriptAdoption = true;
21042
22169
  break;
22170
+ case "--allow-agent-adoption":
22171
+ args.allowAgentAdoption = true;
22172
+ break;
21043
22173
  case "--require-verified-issuers":
21044
22174
  args.requireVerifiedIssuers = true;
21045
22175
  break;
@@ -21097,6 +22227,38 @@ function buildConfirmExistingPrincipal(args, prompt, isTty) {
21097
22227
  return args.confirmExistingPrincipal;
21098
22228
  };
21099
22229
  }
22230
+ function buildConfirmExistingTriggerPrincipal(args, prompt, isTty, say = (text) => void process.stdout.write(text)) {
22231
+ const reusing = (d, how) => say(
22232
+ ` \u26A0 reusing existing trigger service principal '${d.externalId}' (trigger '${d.triggerName}') \u2014 proceeding because ${how}.
22233
+ `
22234
+ );
22235
+ return async (d) => {
22236
+ if (!args.yes && isTty) {
22237
+ const ans = await prompt(
22238
+ `
22239
+ \u26A0 The service principal '${d.externalId}' for trigger '${d.triggerName}' already exists in your tenant ` + (d.userType !== void 0 && d.userType !== "SERVICE" ? `and is a ${d.userType} user, not a service principal.
22240
+ ` : d.provenance === "other-blueprint" ? `and is marked as provisioned by the blueprint '${singleLine(d.markedBy ?? "", 100)}', not this one.
22241
+ ` : `and carries no provenance marker (an earlier CLI release, or created by hand).
22242
+ `) + ` Continuing writes that identity's access profile to this blueprint's trigger grant, and marks it as
22243
+ this blueprint's. Expected when you re-apply a blueprint you already manage. If you don't recognize
22244
+ this identity, do NOT proceed: it may be a name collision with something you don't control.
22245
+ Proceed and use it? [y/N]: `
22246
+ );
22247
+ const ok = existingPrincipalConfirmed(ans);
22248
+ if (ok) reusing(d, "you confirmed it at the prompt");
22249
+ return ok;
22250
+ }
22251
+ if (args.confirmExistingPrincipal) reusing(d, "--confirm-existing-principal was passed");
22252
+ return args.confirmExistingPrincipal;
22253
+ };
22254
+ }
22255
+ function triggerPrincipalMarkLines(reused) {
22256
+ return (reused ?? []).map(
22257
+ (r) => r.marked ? ` \u2714 '${r.externalId}' is now marked as this blueprint's, so the next run will not ask again.
22258
+ ` : ` \u26A0 '${r.externalId}' was NOT marked as this blueprint's (${singleLine(asText(r.reason ?? "unknown reason"), 300)}), so the next run will ask again.
22259
+ `
22260
+ ).join("");
22261
+ }
21100
22262
  function seedSummaryLine(opts) {
21101
22263
  if (opts.noSeed) {
21102
22264
  return opts.declinedInteractively ? " \u2714 Seeded 0 record(s) \u2014 skipped (you declined sample data)\n" : " \u2714 Seeded 0 record(s) \u2014 skipped (--no-seed)\n";
@@ -21107,7 +22269,7 @@ function seedSummaryLine(opts) {
21107
22269
  function seedIndexWarningBlock(failures) {
21108
22270
  if (failures.length === 0) return "";
21109
22271
  const lines = failures.map((f) => {
21110
- const reason = f.message ?? f.code ?? "indexing failed";
22272
+ const reason = singleLine(asText(f.message ?? f.code ?? "indexing failed"), 300);
21111
22273
  return ` \u2022 ${f.typeName}/${f.externalId} (${f.surface}): ${reason}
21112
22274
  `;
21113
22275
  });
@@ -21175,6 +22337,22 @@ function scriptTally(items) {
21175
22337
  if (unchanged) seg.push(`${unchanged} already current \u2014 nothing written`);
21176
22338
  return `${items.length} script${items.length === 1 ? "" : "s"} (${seg.join("; ")})`;
21177
22339
  }
22340
+ function agentTally(items) {
22341
+ if (items.length === 0) return null;
22342
+ const seg = items.map((i) => {
22343
+ switch (i.action) {
22344
+ case "created":
22345
+ return `'${i.name}' created at version ${i.version}`;
22346
+ case "unchanged":
22347
+ return `'${i.name}' unchanged at version ${i.version}`;
22348
+ case "versioned":
22349
+ return `'${i.name}' version ${i.previousVersion} -> ${i.version}`;
22350
+ case "adopted":
22351
+ return i.previousVersion === i.version ? `'${i.name}' adopted (marked as this blueprint's, unchanged at version ${i.version})` : `'${i.name}' adopted (marked as this blueprint's, version ${i.previousVersion} -> ${i.version})`;
22352
+ }
22353
+ });
22354
+ return `${items.length} agent${items.length === 1 ? "" : "s"} (${seg.join("; ")})`;
22355
+ }
21178
22356
  function applyOutcomeSummary(result, opts = {}) {
21179
22357
  const parts = [
21180
22358
  reconcileTally(result.schemaActions, "schema"),
@@ -21191,13 +22369,15 @@ function applyOutcomeSummary(result, opts = {}) {
21191
22369
  // wording applies unchanged; buildPlan's dry-run preview already discloses each trigger's own
21192
22370
  // steps individually, this is the apply-time tally counterpart.
21193
22371
  reconcileTally(result.triggerActions, "trigger"),
22372
+ // Agents — written after the triggers, so reported after them.
22373
+ agentTally(result.agentActions),
21194
22374
  `profile ${result.profileAction}`
21195
22375
  ].filter((p) => p !== null);
21196
22376
  const detail = parts.length ? ` \u2014 ${parts.join("; ")}` : "";
21197
22377
  const principalNote = (opts.principalNote ?? true) && result.userAction === "reconciled" ? ` \u26A0 service principal already existed in this tenant before this run (not newly created)
21198
22378
  ` : "";
21199
22379
  const pruneNotes = result.triggerPrunes.map(
21200
- (p) => ` \u26A0 trigger '${p.name}' removed from the blueprint \u2014 de-provisioned (rule + access profile deleted)
22380
+ (p) => ` \u26A0 trigger '${singleLine(asText(p.name), 100)}' removed from the blueprint \u2014 de-provisioned (rule + access profile deleted)
21201
22381
  ` + // Not a warning: 404 on the profile means it was already gone, so the end state is exactly
21202
22382
  // what a clean prune produces. Said out loud only so the absent second delete isn't read as
21203
22383
  // a silent skip. Every OTHER profile-delete failure lands in triggerPruneSkipped instead.
@@ -21205,9 +22385,9 @@ function applyOutcomeSummary(result, opts = {}) {
21205
22385
  `)
21206
22386
  ).join("");
21207
22387
  const pruneSkippedNotes = result.triggerPruneSkipped.map(
21208
- (s) => s.failed === false ? s.name ? ` \u2139 trigger '${s.name}' is not declared here and was left in place \u2014 ${s.reason}.
22388
+ (s) => s.failed === false ? s.name ? ` \u2139 trigger '${singleLine(asText(s.name), 100)}' is not declared here and was left in place \u2014 ${s.reason}.
21209
22389
  ` : ` \u2139 a trigger rule was left in place \u2014 ${s.reason}.
21210
- ` : s.name ? ` \u26A0 trigger '${s.name}' is no longer declared but could NOT be removed \u2014 ${s.reason}. It is still live.
22390
+ ` : s.name ? ` \u26A0 trigger '${singleLine(asText(s.name), 100)}' is no longer declared but could NOT be removed \u2014 ${s.reason}. It is still live.
21211
22391
  ` : ` \u26A0 triggers could not be reconciled this run \u2014 ${s.reason}. A trigger removed from this blueprint may still be live.
21212
22392
  `
21213
22393
  ).join("");
@@ -21360,7 +22540,7 @@ async function runBootstrapCli(argv, deps = {}) {
21360
22540
  try {
21361
22541
  args = parseBootstrapArgs(argv);
21362
22542
  } catch (err) {
21363
- process.stderr.write(`${redactCredentials(err instanceof Error ? err.message : String(err))}
22543
+ process.stderr.write(`${redactCredentials(errorText(err))}
21364
22544
 
21365
22545
  ${BOOTSTRAP_HELP}
21366
22546
  `);
@@ -21492,7 +22672,7 @@ ${IDENTITIES_TO_ENSURE_HEADING}
21492
22672
  if (!keyring) {
21493
22673
  process.stderr.write(
21494
22674
  `
21495
- \u26A0 Could not open the keyring (${keyringOpenError instanceof Error ? keyringOpenError.message : String(keyringOpenError)}).
22675
+ \u26A0 Could not open the keyring (${errorText(keyringOpenError)}).
21496
22676
  Falling back to embedding the raw secret directly in the MCP config \u2014 this is NOT
21497
22677
  login-gated. Run \`vectros keyring doctor\` once it's fixed.
21498
22678
  `
@@ -21533,7 +22713,7 @@ ${IDENTITIES_TO_ENSURE_HEADING}
21533
22713
  } catch (err) {
21534
22714
  process.stderr.write(
21535
22715
  `
21536
- \u26A0 Could not store this key in the keyring (${err instanceof Error ? err.message : String(err)}).
22716
+ \u26A0 Could not store this key in the keyring (${errorText(err)}).
21537
22717
  Falling back to embedding the raw secret directly in the MCP config \u2014 this is NOT
21538
22718
  login-gated. Run \`vectros keyring doctor\` to see why the store failed.
21539
22719
  `
@@ -21565,10 +22745,12 @@ ${IDENTITIES_TO_ENSURE_HEADING}
21565
22745
  rotate: args.rotate,
21566
22746
  skipSeed: noSeed,
21567
22747
  allowScriptAdoption: args.allowScriptAdoption,
22748
+ allowAgentAdoption: args.allowAgentAdoption,
21568
22749
  readExistingKey,
21569
22750
  persistMintedKey,
21570
22751
  provisioned,
21571
22752
  remintBootstrapForContext,
22753
+ confirmExistingTriggerPrincipal: buildConfirmExistingTriggerPrincipal(args, promptCtx.prompt, isTty),
21572
22754
  remintForContext: buildRemintForContext({
21573
22755
  cognitoToken,
21574
22756
  apiBaseUrl: baseUrl,
@@ -21577,6 +22759,7 @@ ${IDENTITIES_TO_ENSURE_HEADING}
21577
22759
  }),
21578
22760
  log
21579
22761
  });
22762
+ process.stdout.write(triggerPrincipalMarkLines(result.triggerPrincipalsReused));
21580
22763
  const finishApply = () => finishIssuers(
21581
22764
  result.issuerActions,
21582
22765
  { context: blueprint.contextId, tenant: args.tenant, env: args.env, baseUrl: args.baseUrl },
@@ -21645,7 +22828,7 @@ ${codeAddHint}`);
21645
22828
  codeAddHint
21646
22829
  );
21647
22830
  } catch (err) {
21648
- process.stderr.write(` \u26A0 Could not merge config (${err instanceof Error ? err.message : String(err)}).
22831
+ process.stderr.write(` \u26A0 Could not merge config (${errorText(err)}).
21649
22832
  `);
21650
22833
  process.stdout.write(`
21651
22834
  Add this manually instead:
@@ -21657,24 +22840,37 @@ ${codeAddHint}`);
21657
22840
  } catch (err) {
21658
22841
  if (err instanceof SessionExpiredError) {
21659
22842
  process.stderr.write(`
21660
- \u2716 ${err.message}
22843
+ \u2716 ${errorText(err)}
21661
22844
  `);
21662
22845
  return 2;
21663
22846
  }
21664
22847
  if (err instanceof LostKeyError) {
21665
22848
  process.stderr.write(`
21666
- \u2716 ${err.message}
22849
+ \u2716 ${errorText(err)}
21667
22850
  `);
21668
22851
  return 3;
21669
22852
  }
21670
22853
  if (err instanceof ExistingPrincipalNotConfirmedError) {
21671
22854
  process.stderr.write(
21672
22855
  `
21673
- \u2716 ${err.message}
22856
+ \u2716 ${errorText(err)}
21674
22857
 
21675
22858
  If this is expected (a re-apply, or a deliberate second machine for an integration you already own),
21676
22859
  re-run with --confirm-existing-principal, or confirm interactively.
21677
22860
  If you don't recognize this identity, investigate the blueprint's servicePrincipal.externalId before re-running.
22861
+ `
22862
+ );
22863
+ return 3;
22864
+ }
22865
+ if (err instanceof ExistingTriggerPrincipalNotConfirmedError) {
22866
+ process.stderr.write(
22867
+ `
22868
+ \u2716 ${errorText(err)}
22869
+
22870
+ If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal,
22871
+ or confirm interactively; the principal is then marked as this blueprint's and not asked about again.
22872
+ If you don't recognize this identity, investigate the trigger's service principal first.
22873
+ No schema, role, access profile, trigger, key or seed record was written.
21678
22874
  `
21679
22875
  );
21680
22876
  return 3;
@@ -21682,7 +22878,7 @@ ${codeAddHint}`);
21682
22878
  if (err instanceof BlueprintIdentityError || err instanceof UnresolvedIdentitiesError) {
21683
22879
  process.stderr.write(
21684
22880
  `
21685
- \u2716 ${err.message}
22881
+ \u2716 ${errorText(err)}
21686
22882
  The app-context, service principal, and any declared namespaces were already provisioned. No schemas, profile, roles, key, or seed records were.
21687
22883
  `
21688
22884
  );
@@ -21691,7 +22887,7 @@ ${codeAddHint}`);
21691
22887
  if (err instanceof IssuerScopeNotAppliedError) {
21692
22888
  process.stderr.write(
21693
22889
  `
21694
- \u2716 ${err.message}
22890
+ \u2716 ${errorText(err)}
21695
22891
  The app-context, service principal, and any declared namespaces were already provisioned. No schemas, profile, roles, key, or seed records were.
21696
22892
  `
21697
22893
  );
@@ -21699,7 +22895,7 @@ ${codeAddHint}`);
21699
22895
  }
21700
22896
  if (err instanceof TenantWideNamespacesNotAllowedError) {
21701
22897
  process.stderr.write(`
21702
- \u2716 ${err.message}
22898
+ \u2716 ${errorText(err)}
21703
22899
  Nothing was provisioned.
21704
22900
  `);
21705
22901
  return 2;
@@ -21707,7 +22903,7 @@ ${codeAddHint}`);
21707
22903
  process.stderr.write(`
21708
22904
  ${formatCliError(err, { action: "Bootstrap failed" })}`);
21709
22905
  if (process.env.VECTROS_MCP_DEBUG && err instanceof Error && err.stack) {
21710
- process.stderr.write(`${err.stack}
22906
+ process.stderr.write(`${cleanBlock(err.stack)}
21711
22907
  `);
21712
22908
  }
21713
22909
  return 1;
@@ -21732,6 +22928,7 @@ function parseApplyArgs(argv) {
21732
22928
  seed: false,
21733
22929
  allowTenantWideNamespaces: false,
21734
22930
  allowScriptAdoption: false,
22931
+ allowAgentAdoption: false,
21735
22932
  requireVerifiedIssuers: false,
21736
22933
  confirmExistingPrincipal: false,
21737
22934
  prune: false,
@@ -21792,6 +22989,9 @@ function parseApplyArgs(argv) {
21792
22989
  case "--allow-script-adoption":
21793
22990
  args.allowScriptAdoption = true;
21794
22991
  break;
22992
+ case "--allow-agent-adoption":
22993
+ args.allowAgentAdoption = true;
22994
+ break;
21795
22995
  case "--require-verified-issuers":
21796
22996
  args.requireVerifiedIssuers = true;
21797
22997
  break;
@@ -21864,16 +23064,18 @@ Usage:
21864
23064
  [--set name=value ...] [--values <file>] [--seed]
21865
23065
  [--confirm-existing-principal] [--require-verified-issuers]
21866
23066
  [--allow-tenant-wide-namespaces] [--allow-script-adoption]
23067
+ [--allow-agent-adoption]
21867
23068
  [--token <t> | env VECTROS_BOOTSTRAP_TOKEN] [--base-url <url>]
21868
23069
 
21869
23070
  <file|name> is a path to your own blueprint (YAML/JSON) or the name of a bundled one
21870
23071
  (${BLUEPRINT_NAMES.join(", ")}). Everything it declares is created or reconciled \u2014 the context, its
21871
- service principal and access profile, schemas, roles, issuers, triggers and scripts (and, only with --seed, its
23072
+ service principal and access profile, schemas, roles, issuers, triggers, scripts and agents (and, only with --seed, its
21872
23073
  sample seed records) \u2014 but NO scoped key is minted, nothing is written to a keyring and no MCP client config is
21873
23074
  touched, so a CI runner can re-apply a changed blueprint. To also get a credential, use 'vectros bootstrap'. It
21874
23075
  authenticates the same way bootstrap does and refuses on the same gates (scope, capability, tenant-wide namespaces)
21875
23076
  before anything changes. (A script pushed onto an existing hand-authored name is checked when the scripts are
21876
- pushed, partway through: --allow-script-adoption.)
23077
+ pushed, partway through: --allow-script-adoption. An agent that exists with no blueprint mark is checked when the
23078
+ agents are written, also partway through: --allow-agent-adoption.)
21877
23079
 
21878
23080
  Before it changes anything it prints the account, tenant, context and API it is pointed at, then the plan, and
21879
23081
  asks to proceed unless --yes is given. With no terminal --yes is required.
@@ -21908,7 +23110,7 @@ Exit codes:
21908
23110
  3 needs a confirmation it did not get (an existing service principal: --confirm-existing-principal)
21909
23111
  4 applied in full, but --require-verified-issuers was given and an issuer is pending verification`;
21910
23112
  function blueprintRefusal(err) {
21911
- const message = err instanceof Error ? err.message : String(err);
23113
+ const message = errorText(err);
21912
23114
  if (message.startsWith("Unknown blueprint ")) {
21913
23115
  return `No bundled blueprint matches the argument you gave. Bundled blueprints: ${BLUEPRINT_NAMES.join(", ")} (or pass a path like ./my.yaml).`;
21914
23116
  }
@@ -21929,7 +23131,7 @@ async function runApplyCli(argv, deps = {}) {
21929
23131
  } catch (err) {
21930
23132
  process.stderr.write(
21931
23133
  `
21932
- \u2716 ${redactCredentials(err instanceof Error ? err.message : String(err))}
23134
+ \u2716 ${redactCredentials(errorText(err))}
21933
23135
  See 'vectros blueprint apply --help'. Nothing was changed.
21934
23136
  `
21935
23137
  );
@@ -21959,7 +23161,7 @@ async function runApplyCli(argv, deps = {}) {
21959
23161
  try {
21960
23162
  baseUrl = envToBaseUrl(args.env, args.baseUrl ?? process.env.VECTROS_API_BASE_URL);
21961
23163
  } catch (err) {
21962
- return refuse(err instanceof Error ? err.message : String(err));
23164
+ return refuse(errorText(err));
21963
23165
  }
21964
23166
  const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
21965
23167
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
@@ -21995,7 +23197,7 @@ async function runApplyCli(argv, deps = {}) {
21995
23197
  try {
21996
23198
  cognitoToken = await resolveBridgeCognitoToken(args, promptCtx.prompt, isTty, deps.resolveCognitoToken);
21997
23199
  } catch (err) {
21998
- return refuse(err instanceof Error ? err.message : String(err));
23200
+ return refuse(errorText(err));
21999
23201
  }
22000
23202
  const mintBridge = deps.mintBridgeToken ?? mintBridgeToken;
22001
23203
  const mintContext = deps.mintContextToken ?? mintContextToken;
@@ -22113,6 +23315,7 @@ ${formatCliError(err, { action: "Could not mint a fresh bootstrap token after yo
22113
23315
  client,
22114
23316
  log
22115
23317
  );
23318
+ const confirmTriggerPrincipal = buildConfirmExistingTriggerPrincipal(args, promptCtx.prompt, isTty);
22116
23319
  const result = await (deps.runLoader ?? runLoader)(client, {
22117
23320
  blueprint: resolvedBlueprint,
22118
23321
  tenantId,
@@ -22122,14 +23325,17 @@ ${formatCliError(err, { action: "Could not mint a fresh bootstrap token after yo
22122
23325
  // Off unless asked for: apply never deletes on its own. `blueprint-test` sets the same switch off.
22123
23326
  pruneTriggers: args.prune,
22124
23327
  allowScriptAdoption: args.allowScriptAdoption,
23328
+ allowAgentAdoption: args.allowAgentAdoption,
22125
23329
  readExistingKey: async () => void 0,
22126
23330
  persistMintedKey: async () => {
22127
23331
  },
22128
23332
  provisioned,
22129
23333
  remintBootstrapForContext,
23334
+ confirmExistingTriggerPrincipal: confirmTriggerPrincipal,
22130
23335
  remintForContext: buildRemintForContext({ cognitoToken, apiBaseUrl: baseUrl, tenant, mintContext }),
22131
23336
  log
22132
23337
  });
23338
+ process.stdout.write(triggerPrincipalMarkLines(result.triggerPrincipalsReused));
22133
23339
  process.stdout.write(
22134
23340
  applyOutcomeSummary(result, { principalNote: false }) + ` \u2714 Applied '${blueprint.name}' to the ${tenant} tenant, context '${blueprint.contextId}'. No scoped key was minted and no client configuration was touched.
22135
23341
  ` + applySeedSummary(blueprint, args.seed, result.seededCount) + seedIndexWarningBlock(result.seedIndexFailures) + (args.prune ? "" : ` \u2139 Triggers were not pruned (--prune was not passed): a trigger this blueprint provisioned earlier and its file no longer declares is left in place. Pass --prune to remove it.
@@ -22162,12 +23368,26 @@ function reportApplyError(err) {
22162
23368
  if (err instanceof ExistingPrincipalNotConfirmedError) {
22163
23369
  process.stderr.write(
22164
23370
  `
22165
- \u2716 ${err.message}
23371
+ \u2716 ${errorText(err)}
22166
23372
 
22167
23373
  If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal
22168
23374
  (with --yes in a pipeline, where it is standing consent), or confirm at the prompt.
22169
23375
  If you don't recognize this identity, investigate the blueprint's servicePrincipal.externalId first.
22170
23376
  Only the principal lookup had run; no context, grant or anything else was created or changed.
23377
+ `
23378
+ );
23379
+ return 3;
23380
+ }
23381
+ if (err instanceof ExistingTriggerPrincipalNotConfirmedError) {
23382
+ process.stderr.write(
23383
+ `
23384
+ \u2716 ${errorText(err)}
23385
+
23386
+ If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal
23387
+ (with --yes in a pipeline, where it is standing consent), or confirm at the prompt; the principal is then
23388
+ marked as this blueprint's and not asked about again.
23389
+ If you don't recognize this identity, investigate the trigger's service principal first.
23390
+ No schema, role, access profile, trigger or seed record was written.
22171
23391
  `
22172
23392
  );
22173
23393
  return 3;
@@ -22175,7 +23395,7 @@ function reportApplyError(err) {
22175
23395
  if (err instanceof BlueprintIdentityError || err instanceof UnresolvedIdentitiesError) {
22176
23396
  process.stderr.write(
22177
23397
  `
22178
- \u2716 ${err.message}
23398
+ \u2716 ${errorText(err)}
22179
23399
  The app-context, service principal, and any declared namespaces were already provisioned. No schemas, profile, roles or seed records were.
22180
23400
  `
22181
23401
  );
@@ -22184,7 +23404,7 @@ function reportApplyError(err) {
22184
23404
  if (err instanceof IssuerScopeNotAppliedError) {
22185
23405
  process.stderr.write(
22186
23406
  `
22187
- \u2716 ${err.message}
23407
+ \u2716 ${errorText(err)}
22188
23408
  The app-context, service principal, and any declared namespaces were already provisioned. No schemas, profile, roles or seed records were.
22189
23409
  `
22190
23410
  );
@@ -22192,7 +23412,7 @@ function reportApplyError(err) {
22192
23412
  }
22193
23413
  if (err instanceof TenantWideNamespacesNotAllowedError) {
22194
23414
  process.stderr.write(`
22195
- \u2716 ${err.message}
23415
+ \u2716 ${errorText(err)}
22196
23416
  Nothing was changed.
22197
23417
  `);
22198
23418
  return 2;
@@ -22203,7 +23423,7 @@ ${formatCliError(err, { action: "Apply failed" })} The apply stopped part-way:
22203
23423
  `
22204
23424
  );
22205
23425
  if (process.env.VECTROS_MCP_DEBUG && err instanceof Error && err.stack) {
22206
- process.stderr.write(`${err.stack}
23426
+ process.stderr.write(`${cleanBlock(err.stack)}
22207
23427
  `);
22208
23428
  }
22209
23429
  return 1;
@@ -22211,6 +23431,8 @@ ${formatCliError(err, { action: "Apply failed" })} The apply stopped part-way:
22211
23431
 
22212
23432
  // src/lint.ts
22213
23433
  var ENUM_COMPATIBLE_TYPES = /* @__PURE__ */ new Set(["enum", "string", "string[]"]);
23434
+ var PLATFORM_RESERVED_PAYLOAD_FIELD_NAMES = ["externalId", "partnerUserId", "scopes", "userId"];
23435
+ var PAYLOAD_RESERVING_SURFACES = ["record", "document"];
22214
23436
  var BUILT_IN_SORT_BY = /* @__PURE__ */ new Set(["createdAt", "lastUpdated"]);
22215
23437
  function lookupFieldNames(lf) {
22216
23438
  if (typeof lf === "string") return [lf];
@@ -22284,6 +23506,17 @@ function lintBlueprint(bp) {
22284
23506
  }
22285
23507
  fieldIds.add(f.fieldId);
22286
23508
  fieldsById.set(f.fieldId, f);
23509
+ if (PLATFORM_RESERVED_PAYLOAD_FIELD_NAMES.includes(f.fieldId) && (schema.allowedSurfaces ?? ["record"]).some((surface) => PAYLOAD_RESERVING_SURFACES.includes(surface))) {
23510
+ const seeds = (bp.seed ?? []).filter((rec) => rec.typeName === schema.typeName);
23511
+ const seedSetsIt = seeds.some((rec) => Object.prototype.hasOwnProperty.call(rec.fields ?? {}, f.fieldId));
23512
+ const seedsFail = seedSetsIt || f.required === true && seeds.some((rec) => rec.fields !== void 0);
23513
+ const hint = f.fieldId === "externalId" || f.fieldId === "scopes" ? `'${f.fieldId}' is a top-level field of a record or document, and a seed sets it at the top level of the seed, not as a schema field. ` : `Choose a different fieldId. `;
23514
+ findings.push({
23515
+ severity: "warning",
23516
+ rule: "reserved-payload-field-name",
23517
+ message: `schema '${schema.typeName}', field '${f.fieldId}': the platform reserves '${f.fieldId}' (with ${PLATFORM_RESERVED_PAYLOAD_FIELD_NAMES.filter((n) => n !== f.fieldId).join(", ")}) for the top level of a record or document, so a payload that carries it is refused and this field can never hold a value. ` + hint + (seedsFail ? `This schema's seed(s) will fail when they are provisioned (a seed that sets it, or a required field the seed cannot fill). Remove the field.` : f.required === true ? `The field is required, so every write to this schema will fail. Remove the field.` : `No seed trips it today, but any write that sets it will fail. Remove the field.`)
23518
+ });
23519
+ }
22287
23520
  if (f.enumValues && f.enumValues.length > 0 && !ENUM_COMPATIBLE_TYPES.has(f.fieldType)) {
22288
23521
  findings.push({
22289
23522
  severity: "warning",
@@ -22300,6 +23533,13 @@ function lintBlueprint(bp) {
22300
23533
  }
22301
23534
  for (const lf of schema.lookupFields ?? []) {
22302
23535
  for (const name of lookupFieldNames(lf)) {
23536
+ if (PLATFORM_RESERVED_PAYLOAD_FIELD_NAMES.includes(name)) {
23537
+ findings.push({
23538
+ severity: "error",
23539
+ rule: "reserved-lookup-field-name",
23540
+ message: `schema '${schema.typeName}': lookupField '${name}' is a name the platform reserves (${PLATFORM_RESERVED_PAYLOAD_FIELD_NAMES.join(", ")}), so it refuses the schema when it is created. A record is found by externalId through its own top-level externalId; choose a different name for an unrelated payload identifier.`
23541
+ });
23542
+ }
22303
23543
  if (!fieldIds.has(name)) {
22304
23544
  findings.push({
22305
23545
  severity: "error",
@@ -22518,7 +23758,7 @@ schemas:
22518
23758
  lookupFields: [title] # extra O(1) exact-match lookup indexes (max 10)
22519
23759
 
22520
23760
  accessProfile:
22521
- # SCOPE GATE: data-plane only (records/schemas/search/documents/folders/inference/entities),
23761
+ # SCOPE GATE: data-plane only (records/schemas/search/documents/folders/inference/entities/runs),
22522
23762
  # PLUS profiles:r/u/d (bounded by the platform to redistributing access you already have,
22523
23763
  # never granting more), profiles:c:self (create a profile for yourself only \u2014 the
22524
23764
  # platform resolves "self" from the caller, not from anything you declare here) and
@@ -22544,7 +23784,7 @@ accessProfile:
22544
23784
  dataScope: {}
22545
23785
  # capabilities is a SEPARATE gated dimension from allowedActions \u2014 named platform
22546
23786
  # effects a resource:ops verb can't express. Only 'member-lifecycle' and
22547
- # 'delegate-principal-stamp' are admitted today; every other name is REJECTED, same
23787
+ # 'delegate-principal-stamp' are admitted; every other name is REJECTED, same
22548
23788
  # posture as the scope gate above. 'member-lifecycle' is also what lets this clause
22549
23789
  # carry a bare profiles:c.
22550
23790
  # capabilities: [member-lifecycle]
@@ -22675,7 +23915,7 @@ async function resolveAndValidate(arg, verb, supplied) {
22675
23915
  try {
22676
23916
  blueprint = await resolveBlueprintArg(arg, supplied);
22677
23917
  } catch (err) {
22678
- process.stderr.write(` \u2716 ${err instanceof Error ? err.message : String(err)}
23918
+ process.stderr.write(` \u2716 ${errorText(err)}
22679
23919
  `);
22680
23920
  return null;
22681
23921
  }
@@ -22702,7 +23942,7 @@ async function resolveAndValidate(arg, verb, supplied) {
22702
23942
  for (const f of findings) {
22703
23943
  const mark = f.severity === "error" ? "\u2716" : "\u26A0";
22704
23944
  const stream = f.severity === "error" ? process.stderr : process.stdout;
22705
- stream.write(` ${mark} [${f.rule}] ${f.message}
23945
+ stream.write(` ${mark} [${f.rule}] ${singleLine(f.message, 2e3)}
22706
23946
  `);
22707
23947
  }
22708
23948
  if (!lintPasses(findings)) {
@@ -22717,7 +23957,7 @@ async function runValidate(args) {
22717
23957
  try {
22718
23958
  supplied = await buildInputSupply(args.set, args.values);
22719
23959
  } catch (err) {
22720
- process.stderr.write(` \u2716 ${err instanceof Error ? err.message : String(err)}
23960
+ process.stderr.write(` \u2716 ${errorText(err)}
22721
23961
  `);
22722
23962
  return 2;
22723
23963
  }
@@ -22729,6 +23969,13 @@ async function runValidate(args) {
22729
23969
  process.stdout.write(
22730
23970
  ` \u2714 '${blueprint.name}' is valid \u2014 structural \u2713, scope gate \u2713 [${effectiveAccessProfileActions(blueprint).join(", ")}]${roleNote}` + (capGate.accepted.length ? `, capability gate \u2713 [${capGate.accepted.join(", ")}]` : "") + `, lint \u2713` + (warnCount ? ` (${warnCount} warning${warnCount === 1 ? "" : "s"})` : "") + "\n"
22731
23971
  );
23972
+ if (declaresAgents(blueprint)) {
23973
+ const n = Object.keys(blueprint.agents ?? {}).length;
23974
+ process.stdout.write(
23975
+ ` \u2714 The 'agents' section is valid (${n} agent${n === 1 ? "" : "s"}). 'apply' creates each through POST /v1/agents, which needs the 'agents:c' scope on the credential that applies it ('agents:u' and 'agents:r' as well for an agent that already exists).
23976
+ `
23977
+ );
23978
+ }
22732
23979
  return 0;
22733
23980
  }
22734
23981
  async function runPlan(args) {
@@ -22736,13 +23983,20 @@ async function runPlan(args) {
22736
23983
  try {
22737
23984
  supplied = await buildInputSupply(args.set, args.values);
22738
23985
  } catch (err) {
22739
- process.stderr.write(` \u2716 ${err instanceof Error ? err.message : String(err)}
23986
+ process.stderr.write(` \u2716 ${errorText(err)}
22740
23987
  `);
22741
23988
  return 2;
22742
23989
  }
22743
23990
  const blueprint = await resolveAndValidate(args.file, "plan", supplied);
22744
23991
  if (!blueprint) return 2;
22745
- const steps = buildPlan(blueprint);
23992
+ let steps;
23993
+ try {
23994
+ steps = buildPlan(blueprint);
23995
+ } catch (err) {
23996
+ process.stderr.write(` \u2716 ${errorText(err)}
23997
+ `);
23998
+ return 2;
23999
+ }
22746
24000
  process.stdout.write(`
22747
24001
  ${renderPlan(blueprint, steps)}
22748
24002
  `);
@@ -22812,7 +24066,7 @@ async function runInit(args) {
22812
24066
  try {
22813
24067
  await (0, import_promises3.writeFile)(outPath, scaffold, "utf8");
22814
24068
  } catch (err) {
22815
- process.stderr.write(` \u2716 Could not write ${outPath}: ${err instanceof Error ? err.message : String(err)}
24069
+ process.stderr.write(` \u2716 Could not write ${outPath}: ${errorText(err)}
22816
24070
  `);
22817
24071
  return 2;
22818
24072
  }
@@ -22845,7 +24099,7 @@ async function runBlueprintCli(argv, deps = {}) {
22845
24099
  try {
22846
24100
  args = parseBlueprintArgs(argv);
22847
24101
  } catch (err) {
22848
- process.stderr.write(`${err instanceof Error ? err.message : String(err)}
24102
+ process.stderr.write(`${errorText(err)}
22849
24103
 
22850
24104
  ${BLUEPRINT_HELP}
22851
24105
  `);
@@ -25870,100 +27124,338 @@ async function runScriptsList(opts, deps = {}) {
25870
27124
  }
25871
27125
  process.stdout.write(
25872
27126
  "\n" + formatTable(rows, [
25873
- { header: "name", get: (s) => s.name },
25874
- { header: "version", get: (s) => s.scriptVersion === void 0 ? void 0 : String(s.scriptVersion) },
25875
- { header: "id", get: (s) => s.id },
25876
- { header: "bytes", get: (s) => s.source === void 0 ? void 0 : String(utf8Bytes2(s.source)) },
25877
- { header: "createdAt", get: (s) => s.createdAt }
25878
- ]) + "\n"
27127
+ { header: "name", get: (s) => s.name },
27128
+ { header: "version", get: (s) => s.scriptVersion === void 0 ? void 0 : String(s.scriptVersion) },
27129
+ { header: "id", get: (s) => s.id },
27130
+ { header: "bytes", get: (s) => s.source === void 0 ? void 0 : String(utf8Bytes2(s.source)) },
27131
+ { header: "createdAt", get: (s) => s.createdAt }
27132
+ ]) + "\n"
27133
+ );
27134
+ return 0;
27135
+ } catch (err) {
27136
+ process.stderr.write(formatCliError(err, { action: "scripts list failed" }));
27137
+ return 1;
27138
+ }
27139
+ });
27140
+ }
27141
+ async function runScriptsGet(opts, deps = {}) {
27142
+ return withAuth(opts, deps, async (auth) => {
27143
+ const client = auth.client;
27144
+ try {
27145
+ const script = await client.scripts.getScript({ id: opts.id });
27146
+ if (opts.json) {
27147
+ process.stdout.write(`${formatJson(script)}
27148
+ `);
27149
+ return 0;
27150
+ }
27151
+ process.stdout.write(`
27152
+ ${renderScript(script, { source: true })}
27153
+ `);
27154
+ return 0;
27155
+ } catch (err) {
27156
+ process.stderr.write(formatCliError(err, { action: "scripts get failed" }));
27157
+ return 1;
27158
+ }
27159
+ });
27160
+ }
27161
+ async function runScriptsDelete(opts, deps = {}) {
27162
+ process.stderr.write(
27163
+ `
27164
+ \u26A0 Deleting script version '${opts.id}' is permanent \u2014 the stored source is the only copy.
27165
+ Other versions of the same name are unaffected.
27166
+ The platform refuses this delete if a trigger rule still references this version \u2014
27167
+ directly, or via 'latest' if this is the name's current newest version \u2014 and names the
27168
+ offending rule instead of deleting. If no rule references it, the delete proceeds.
27169
+ And if this is the name's LATEST version, its identity is REUSED: the next push of that
27170
+ name recomputes the same version number and the same id, so anything holding a pinned
27171
+ '${opts.id}' or a name@version reference will silently resolve to different source.
27172
+
27173
+ `
27174
+ );
27175
+ if (!opts.force) {
27176
+ const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
27177
+ if (!isTty) {
27178
+ process.stderr.write(
27179
+ ` \u2716 scripts delete is irreversible and needs an interactive confirmation.
27180
+ Re-run with --force to delete '${opts.id}' without a prompt.
27181
+ `
27182
+ );
27183
+ return 2;
27184
+ }
27185
+ const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
27186
+ const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
27187
+ return { prompt: (q) => rl.question(q), close: () => rl.close() };
27188
+ })();
27189
+ try {
27190
+ const typed = await promptCtx.prompt(` Type the version id ('${opts.id}') to confirm: `);
27191
+ if (typed.trim() !== opts.id) {
27192
+ process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
27193
+ `);
27194
+ return 1;
27195
+ }
27196
+ } finally {
27197
+ promptCtx.close();
27198
+ }
27199
+ }
27200
+ return withAuth(opts, deps, async (auth) => {
27201
+ const client = auth.client;
27202
+ try {
27203
+ await client.scripts.deleteScript({ id: opts.id });
27204
+ if (opts.json) {
27205
+ process.stdout.write(`${formatJson({ id: opts.id, deleted: true })}
27206
+ `);
27207
+ return 0;
27208
+ }
27209
+ process.stdout.write(` \u2714 Deleted script version '${opts.id}' from '${opts.context}'.
27210
+ `);
27211
+ return 0;
27212
+ } catch (err) {
27213
+ process.stderr.write(formatCliError(err, { action: "scripts delete failed" }));
27214
+ return 1;
27215
+ }
27216
+ });
27217
+ }
27218
+ function errText(err) {
27219
+ return err instanceof Error ? err.message : String(err);
27220
+ }
27221
+
27222
+ // src/commands/agents.ts
27223
+ var import_promises5 = require("fs/promises");
27224
+ var import_node_path6 = require("path");
27225
+ var import_yaml3 = require("yaml");
27226
+ var DEFINITION_KEYS = /* @__PURE__ */ new Set(["description", "authority", "model", "system", "tools", "limits", "budget"]);
27227
+ async function readDefinition(path4, name) {
27228
+ let raw;
27229
+ try {
27230
+ raw = await (0, import_promises5.readFile)(path4, "utf8");
27231
+ } catch (err) {
27232
+ return { error: `could not read --file '${shortPath(path4, 300)}': ${quotedBlock(err instanceof Error ? err.message : String(err))}` };
27233
+ }
27234
+ let parsed;
27235
+ try {
27236
+ const ext = (0, import_node_path6.extname)(path4).toLowerCase();
27237
+ parsed = ext === ".yaml" || ext === ".yml" ? (0, import_yaml3.parse)(raw, { logLevel: "error" }) : JSON.parse(raw);
27238
+ } catch (err) {
27239
+ return { error: `--file '${shortPath(path4, 300)}' is not valid ${/\.ya?ml$/i.test(path4) ? "YAML" : "JSON"}: ${quotedBlock(err instanceof Error ? err.message : String(err))}` };
27240
+ }
27241
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
27242
+ return { error: `--file '${shortPath(path4, 300)}' must hold one agent definition (an object with at least a 'model').` };
27243
+ }
27244
+ const entries = Object.entries(parsed);
27245
+ const unknown = entries.map(([k]) => k).filter((k) => !DEFINITION_KEYS.has(k) && k !== "name");
27246
+ if (unknown.length > 0) {
27247
+ return {
27248
+ error: `--file '${shortPath(path4, 300)}' has ${unknown.length === 1 ? "a key" : "keys"} an agent definition does not use: ${unknown.map((k) => `'${singleLine(k, 80)}'`).join(", ")}. The fields are ${[...DEFINITION_KEYS].join(", ")}.`
27249
+ };
27250
+ }
27251
+ const declaredName = parsed.name;
27252
+ if (declaredName !== void 0 && typeof declaredName !== "string") {
27253
+ return { error: `--file '${shortPath(path4, 300)}' has a 'name' that is not a string.` };
27254
+ }
27255
+ if (declaredName !== void 0 && name !== void 0 && declaredName !== name) {
27256
+ return {
27257
+ error: `--file '${shortPath(path4, 300)}' names the agent '${singleLine(String(declaredName), 80)}', not '${name}'. Remove 'name' from the file or make them match.`
27258
+ };
27259
+ }
27260
+ const { name: _name, ...definition } = parsed;
27261
+ void _name;
27262
+ if (typeof definition.model !== "string" || definition.model.trim().length === 0) {
27263
+ return { error: `--file '${shortPath(path4, 300)}' needs a 'model': the id of the model the agent runs on.` };
27264
+ }
27265
+ if (singleLine(definition.model, definition.model.length + 1) !== definition.model) {
27266
+ return { error: `--file '${shortPath(path4, 300)}' has a 'model' with a line break or a control character in it; a model id is plain text on one line.` };
27267
+ }
27268
+ return { definition, ...declaredName !== void 0 ? { declaredName } : {} };
27269
+ }
27270
+ function withHint(hint) {
27271
+ return hint ? ` ${hint}
27272
+ ` : "";
27273
+ }
27274
+ function toolsLine(a) {
27275
+ if (!a.tools) return void 0;
27276
+ if (a.tools.length === 0) return "(none)";
27277
+ return a.tools.map((t) => t.allowWrites ? `${t.name} (writes)` : t.name).join(", ");
27278
+ }
27279
+ function oneLine(value, maxLength = 200) {
27280
+ return value === void 0 ? void 0 : singleLine(value, maxLength);
27281
+ }
27282
+ function indentedBlock(value, maxLength = 1e5) {
27283
+ if (value === void 0) return void 0;
27284
+ return value.split(/\r\n|[\r\n\u0085\u2028\u2029]/).map((line, i) => {
27285
+ const clean = stripControlCharacters(line.replace(/\t/g, " "), maxLength);
27286
+ return i === 0 || clean === "" ? clean : ` ${clean}`;
27287
+ }).join("\n").trimEnd();
27288
+ }
27289
+ function renderAgent(a) {
27290
+ return formatKeyValues([
27291
+ ["id", oneLine(a.id)],
27292
+ ["name", oneLine(a.name)],
27293
+ ["status", oneLine(a.status)],
27294
+ ["version", oneLine(a.version === void 0 ? void 0 : String(a.version))],
27295
+ ["latestVersion", oneLine(a.latestVersion === void 0 ? void 0 : String(a.latestVersion))],
27296
+ ["model", oneLine(a.model)],
27297
+ ["authority", oneLine(a.authority)],
27298
+ ["description", oneLine(a.description, 1e3)],
27299
+ ["tools", oneLine(toolsLine(a), 5e3)],
27300
+ ["limits", oneLine(a.limits === void 0 ? void 0 : JSON.stringify(a.limits), 1e3)],
27301
+ ["budget", oneLine(a.budget === void 0 ? void 0 : JSON.stringify(a.budget), 1e3)],
27302
+ ["provisionedBy", oneLine(a.provisionedBy)],
27303
+ ["createdAt", oneLine(a.createdAt)],
27304
+ ["system", indentedBlock(a.system)]
27305
+ ]);
27306
+ }
27307
+ async function runAgentsList(opts, deps = {}) {
27308
+ return withAuth(opts, deps, async (auth) => {
27309
+ const client = auth.client;
27310
+ try {
27311
+ const rows = await drainPages(
27312
+ (startFrom) => client.agents.listAgents({
27313
+ contextId: opts.context,
27314
+ limit: PAGE_LIMIT,
27315
+ ...opts.includeRetired ? { includeRetired: true } : {},
27316
+ ...startFrom ? { startFrom } : {}
27317
+ })
27318
+ );
27319
+ if (opts.json) {
27320
+ process.stdout.write(`${formatJson(rows)}
27321
+ `);
27322
+ return 0;
27323
+ }
27324
+ if (rows.length === 0) {
27325
+ process.stdout.write(`
27326
+ No agents in '${opts.context}'.
27327
+ `);
27328
+ return 0;
27329
+ }
27330
+ const summaryOnly = rows.every((a) => a.model === void 0);
27331
+ process.stdout.write(
27332
+ "\n" + formatTable(rows, [
27333
+ { header: "name", get: (a) => oneLine(a.name) },
27334
+ { header: "version", get: (a) => oneLine(a.latestVersion === void 0 ? void 0 : String(a.latestVersion)) },
27335
+ { header: "status", get: (a) => oneLine(a.status) },
27336
+ { header: "model", get: (a) => oneLine(a.model) },
27337
+ { header: "id", get: (a) => oneLine(a.id) }
27338
+ ]) + "\n" + (summaryOnly ? "\n (A summary: this credential may start runs but cannot read agent definitions, which needs the 'agents:r' scope.)\n" : "")
25879
27339
  );
25880
27340
  return 0;
25881
27341
  } catch (err) {
25882
- process.stderr.write(formatCliError(err, { action: "scripts list failed" }));
27342
+ process.stderr.write(formatCliError(sanitizedAgentError(err), { action: "agents list failed" }) + withHint(agentErrorHint(err, opts.context, "'agents:r'")));
25883
27343
  return 1;
25884
27344
  }
25885
27345
  });
25886
27346
  }
25887
- async function runScriptsGet(opts, deps = {}) {
27347
+ async function runAgentsGet(opts, deps = {}) {
25888
27348
  return withAuth(opts, deps, async (auth) => {
25889
27349
  const client = auth.client;
25890
27350
  try {
25891
- const script = await client.scripts.getScript({ id: opts.id });
27351
+ const agent = await client.agents.getAgent({
27352
+ id: opts.id,
27353
+ contextId: opts.context,
27354
+ ...opts.version !== void 0 ? { version: opts.version } : {}
27355
+ });
25892
27356
  if (opts.json) {
25893
- process.stdout.write(`${formatJson(script)}
27357
+ process.stdout.write(`${formatJson(agent)}
25894
27358
  `);
25895
27359
  return 0;
25896
27360
  }
25897
27361
  process.stdout.write(`
25898
- ${renderScript(script, { source: true })}
27362
+ ${renderAgent(agent)}
25899
27363
  `);
25900
27364
  return 0;
25901
27365
  } catch (err) {
25902
- process.stderr.write(formatCliError(err, { action: "scripts get failed" }));
27366
+ process.stderr.write(formatCliError(sanitizedAgentError(err), { action: "agents get failed" }) + withHint(agentErrorHint(err, opts.context, "'agents:r'")));
25903
27367
  return 1;
25904
27368
  }
25905
27369
  });
25906
27370
  }
25907
- async function runScriptsDelete(opts, deps = {}) {
25908
- process.stderr.write(
25909
- `
25910
- \u26A0 Deleting script version '${opts.id}' is permanent \u2014 the stored source is the only copy.
25911
- Other versions of the same name are unaffected.
25912
- The platform refuses this delete if a trigger rule still references this version \u2014
25913
- directly, or via 'latest' if this is the name's current newest version \u2014 and names the
25914
- offending rule instead of deleting. If no rule references it, the delete proceeds.
25915
- And if this is the name's LATEST version, its identity is REUSED: the next push of that
25916
- name recomputes the same version number and the same id, so anything holding a pinned
25917
- '${opts.id}' or a name@version reference will silently resolve to different source.
25918
-
25919
- `
25920
- );
25921
- if (!opts.force) {
25922
- const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
25923
- if (!isTty) {
25924
- process.stderr.write(
25925
- ` \u2716 scripts delete is irreversible and needs an interactive confirmation.
25926
- Re-run with --force to delete '${opts.id}' without a prompt.
27371
+ async function runAgentsCreate(opts, deps = {}) {
27372
+ if (!AGENT_NAME_RE.test(opts.name)) {
27373
+ process.stderr.write(
27374
+ ` \u2716 '${opts.name}' is not a valid agent name \u2014 use 1-64 characters of letters, digits, underscores ('_') and hyphens ('-').
25927
27375
  `
25928
- );
25929
- return 2;
25930
- }
25931
- const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
25932
- const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
25933
- return { prompt: (q) => rl.question(q), close: () => rl.close() };
25934
- })();
27376
+ );
27377
+ return 2;
27378
+ }
27379
+ const read = await readDefinition(opts.file, opts.name);
27380
+ if ("error" in read) {
27381
+ process.stderr.write(` \u2716 ${read.error}
27382
+ `);
27383
+ return 2;
27384
+ }
27385
+ return withAuth(opts, deps, async (auth) => {
27386
+ const client = auth.client;
25935
27387
  try {
25936
- const typed = await promptCtx.prompt(` Type the version id ('${opts.id}') to confirm: `);
25937
- if (typed.trim() !== opts.id) {
25938
- process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
27388
+ const agent = await client.agents.createAgent({
27389
+ contextId: opts.context,
27390
+ body: { ...read.definition, name: opts.name }
27391
+ });
27392
+ if (opts.json) {
27393
+ process.stdout.write(`${formatJson(agent)}
25939
27394
  `);
25940
- return 1;
27395
+ return 0;
25941
27396
  }
25942
- } finally {
25943
- promptCtx.close();
27397
+ if (agent.created === false) {
27398
+ process.stdout.write(
27399
+ `
27400
+ = agent '${opts.name}' already exists at version ${oneLine(String(agent.version ?? agent.latestVersion))} \u2014 nothing was written.
27401
+ To change it, write a new version: vectros agents version ${oneLine(agent.id)} --context ${opts.context} --file <definition>
27402
+ `
27403
+ );
27404
+ return 0;
27405
+ }
27406
+ process.stdout.write(`
27407
+ \u2714 created agent '${opts.name}' at version ${oneLine(String(agent.version ?? 1))} in '${opts.context}'
27408
+ ${renderAgent(agent)}
27409
+ `);
27410
+ return 0;
27411
+ } catch (err) {
27412
+ process.stderr.write(
27413
+ formatCliError(sanitizedAgentError(err), { action: `agents create '${opts.name}' failed` }) + withHint(agentErrorHint(err, opts.context, "'agents:c'"))
27414
+ );
27415
+ return 1;
25944
27416
  }
27417
+ });
27418
+ }
27419
+ async function runAgentsVersion(opts, deps = {}) {
27420
+ const read = await readDefinition(opts.file);
27421
+ if ("error" in read) {
27422
+ process.stderr.write(` \u2716 ${read.error}
27423
+ `);
27424
+ return 2;
25945
27425
  }
25946
27426
  return withAuth(opts, deps, async (auth) => {
25947
27427
  const client = auth.client;
25948
27428
  try {
25949
- await client.scripts.deleteScript({ id: opts.id });
27429
+ const agent = await client.agents.updateAgent({
27430
+ id: opts.id,
27431
+ contextId: opts.context,
27432
+ ...opts.restore ? { restore: true } : {},
27433
+ body: {
27434
+ ...read.definition,
27435
+ ...read.declaredName !== void 0 ? { name: read.declaredName } : {},
27436
+ ...opts.expectedVersion !== void 0 ? { expectedVersion: opts.expectedVersion } : {}
27437
+ }
27438
+ });
25950
27439
  if (opts.json) {
25951
- process.stdout.write(`${formatJson({ id: opts.id, deleted: true })}
27440
+ process.stdout.write(`${formatJson(agent)}
25952
27441
  `);
25953
27442
  return 0;
25954
27443
  }
25955
- process.stdout.write(` \u2714 Deleted script version '${opts.id}' from '${opts.context}'.
25956
- `);
27444
+ process.stdout.write(
27445
+ `
27446
+ \u2714 wrote version ${oneLine(String(agent.version ?? agent.latestVersion))} of agent '${oneLine(agent.name ?? opts.id)}' in '${opts.context}'
27447
+ ${renderAgent(agent)}
27448
+ `
27449
+ );
25957
27450
  return 0;
25958
27451
  } catch (err) {
25959
- process.stderr.write(formatCliError(err, { action: "scripts delete failed" }));
27452
+ process.stderr.write(
27453
+ formatCliError(sanitizedAgentError(err), { action: `agents version '${opts.id}' failed` }) + withHint(agentErrorHint(err, opts.context, "'agents:u'"))
27454
+ );
25960
27455
  return 1;
25961
27456
  }
25962
27457
  });
25963
27458
  }
25964
- function errText(err) {
25965
- return err instanceof Error ? err.message : String(err);
25966
- }
25967
27459
 
25968
27460
  // src/commands/assume.ts
25969
27461
  function parseAssumeScopes(raw) {
@@ -26224,9 +27716,11 @@ function withTimeout(p, ms) {
26224
27716
  }
26225
27717
 
26226
27718
  // src/commands/blueprint-test.ts
26227
- var import_node_crypto4 = require("crypto");
27719
+ var import_node_crypto5 = require("crypto");
27720
+ var import_node_fs6 = require("fs");
26228
27721
 
26229
27722
  // src/harness.ts
27723
+ var import_node_crypto4 = require("crypto");
26230
27724
  var errMsg = (e) => e instanceof Error ? e.message : String(e);
26231
27725
  var isNotFound2 = (e) => typeof e === "object" && e !== null && e.statusCode === 404;
26232
27726
  var SNAPSHOT_PAGE_SIZE = 100;
@@ -26433,7 +27927,7 @@ async function runAssertions(client, bootstrapClient, deps, blueprint, tenantId,
26433
27927
  }
26434
27928
  return assertions;
26435
27929
  }
26436
- async function runTeardown(client, bootstrapClient, blueprint, machineId, snapshot, result) {
27930
+ async function runTeardown(client, bootstrapClient, blueprint, machineId, snapshot, result, runAgentNames, createdAgentIds, seenAgentNames, attemptedAgentNames, refusedAgentNames) {
26437
27931
  const steps = [];
26438
27932
  const keyName = result?.keyName ?? keyNameFor(blueprint.name, machineId);
26439
27933
  try {
@@ -26451,6 +27945,7 @@ async function runTeardown(client, bootstrapClient, blueprint, machineId, snapsh
26451
27945
  } catch (e) {
26452
27946
  steps.push({ step: `revoke key ${keyName}`, ok: false, error: errMsg(e) });
26453
27947
  }
27948
+ steps.push(...await teardownAgents(client, blueprint, runAgentNames, createdAgentIds, seenAgentNames, attemptedAgentNames, refusedAgentNames, result));
26454
27949
  if (!result) return steps;
26455
27950
  for (const id of result.seededRecordIds) {
26456
27951
  try {
@@ -26644,19 +28139,247 @@ async function runTeardown(client, bootstrapClient, blueprint, machineId, snapsh
26644
28139
  }
26645
28140
  return steps;
26646
28141
  }
28142
+ var AGENT_RUN_SUFFIX_LENGTH = 10;
28143
+ var MAX_TESTABLE_AGENT_NAME_LENGTH = 64 - AGENT_RUN_SUFFIX_LENGTH;
28144
+ function agentRunSuffix(machineId) {
28145
+ return `-t${(0, import_node_crypto4.createHash)("sha256").update(machineId).digest("hex").slice(0, 8)}`;
28146
+ }
28147
+ function agentNamesProblem(blueprint) {
28148
+ const tooLong = Object.keys(blueprint.agents ?? {}).filter((name) => name.length > MAX_TESTABLE_AGENT_NAME_LENGTH);
28149
+ if (tooLong.length === 0) return void 0;
28150
+ return `blueprint-test gives each run its own agent names by adding ${AGENT_RUN_SUFFIX_LENGTH} characters, so an agent name can be at most ${MAX_TESTABLE_AGENT_NAME_LENGTH} characters here. Too long: ${tooLong.map((n) => `'${n}'`).join(", ")}.`;
28151
+ }
28152
+ function scopeAgentsToRun(blueprint, machineId) {
28153
+ const declared = Object.entries(blueprint.agents ?? {});
28154
+ if (declared.length === 0) return { blueprint, names: {} };
28155
+ const problem = agentNamesProblem(blueprint);
28156
+ if (problem) throw new Error(problem);
28157
+ const suffix = agentRunSuffix(machineId);
28158
+ const names = /* @__PURE__ */ Object.create(null);
28159
+ const agents = /* @__PURE__ */ Object.create(null);
28160
+ for (const [name, definition] of declared) {
28161
+ names[name] = `${name}${suffix}`;
28162
+ agents[names[name]] = definition;
28163
+ }
28164
+ return { blueprint: { ...blueprint, agents }, names };
28165
+ }
28166
+ function isAgentRemovalFailure(step) {
28167
+ return !step.ok && /^(retire|purge) agent /.test(step.step);
28168
+ }
28169
+ async function teardownAgents(client, blueprint, runNames, createdIds, seenAgentNames, attemptedAgentNames, refusedAgentNames, result) {
28170
+ const steps = [];
28171
+ if (runNames.size === 0) return steps;
28172
+ const found = new Map(createdIds);
28173
+ const notOurs = new Set(seenAgentNames);
28174
+ for (const a of result?.agentActions ?? []) {
28175
+ if (!runNames.has(a.name)) continue;
28176
+ if (a.action === "created") found.set(a.name, a.id);
28177
+ else {
28178
+ notOurs.add(a.name);
28179
+ steps.push({ step: `retire agent ${a.name}`, ok: true, skipped: true, error: `skipped \u2014 this run did not create it (${a.action})` });
28180
+ }
28181
+ }
28182
+ if (!result) {
28183
+ for (const name of [...seenAgentNames].sort()) {
28184
+ steps.push({ step: `retire agent ${name}`, ok: true, skipped: true, error: "skipped \u2014 it existed before this run created anything" });
28185
+ }
28186
+ }
28187
+ const lost = new Set(
28188
+ [...attemptedAgentNames].filter((n) => runNames.has(n) && !found.has(n) && !notOurs.has(n) && !refusedAgentNames.has(n))
28189
+ );
28190
+ if (lost.size > 0 && !result) {
28191
+ let listed = false;
28192
+ try {
28193
+ let startFrom;
28194
+ for (; ; ) {
28195
+ const page = await client.agents.listAgents({ contextId: blueprint.contextId, limit: SNAPSHOT_PAGE_SIZE, startFrom });
28196
+ for (const row of page.data ?? []) {
28197
+ if (!row.name || !row.id || !lost.has(row.name)) continue;
28198
+ if (row.provisionedBy === blueprint.name || row.provisionedBy === void 0) found.set(row.name, row.id);
28199
+ }
28200
+ if (!page.nextCursor) break;
28201
+ startFrom = page.nextCursor;
28202
+ }
28203
+ listed = true;
28204
+ } catch (e) {
28205
+ if (isNotFound2(e)) {
28206
+ steps.push({ step: "list agents", ok: true, skipped: true, error: "skipped \u2014 the context does not exist, so nothing was created" });
28207
+ } else {
28208
+ steps.push({
28209
+ step: "list agents",
28210
+ ok: false,
28211
+ error: `could not list agents, so an agent created before a failure may remain: ${errMsg(e)}`
28212
+ });
28213
+ }
28214
+ }
28215
+ if (listed) {
28216
+ for (const name of [...lost].filter((n) => !found.has(n)).sort()) {
28217
+ steps.push({
28218
+ step: `check agent ${name}`,
28219
+ ok: true,
28220
+ skipped: true,
28221
+ error: "could not confirm whether it was created (its create got no definite answer and the listing does not show it); check with `vectros agents list --context " + blueprint.contextId + " --include-retired`"
28222
+ });
28223
+ }
28224
+ }
28225
+ }
28226
+ for (const [name, id] of [...found].sort(([a], [b]) => a.localeCompare(b))) {
28227
+ try {
28228
+ await client.agents.deleteAgent({ id, contextId: blueprint.contextId });
28229
+ steps.push({ step: `retire agent ${name}`, ok: true });
28230
+ } catch (e) {
28231
+ steps.push({
28232
+ step: `retire agent ${name}`,
28233
+ ok: false,
28234
+ error: `the agent is still active and can be run; retire it with DELETE /v1/agents/${id} (needs agents:d): ${errMsg(e)}`
28235
+ });
28236
+ continue;
28237
+ }
28238
+ try {
28239
+ await client.agents.deleteAgent({ id, contextId: blueprint.contextId, purge: true });
28240
+ steps.push({ step: `purge agent ${name}`, ok: true });
28241
+ } catch (e) {
28242
+ steps.push({ step: `purge agent ${name}`, ok: false, error: `retired, but its content was not erased; erase it with DELETE /v1/agents/${id}?purge=true (needs agents:d): ${errMsg(e)}` });
28243
+ }
28244
+ }
28245
+ return steps;
28246
+ }
28247
+ function onlyAttempted(names, attempted) {
28248
+ const kept = Object.entries(names).filter(([, runName]) => attempted.has(runName));
28249
+ return kept.length > 0 ? Object.fromEntries(kept) : void 0;
28250
+ }
28251
+ var HarnessInterrupted = class extends Error {
28252
+ constructor(signal) {
28253
+ super(`interrupted by ${signal}`);
28254
+ this.signal = signal;
28255
+ }
28256
+ signal;
28257
+ };
28258
+ function watchForInterrupt(deps, run) {
28259
+ const source = deps.signals ?? process;
28260
+ const say = (text) => {
28261
+ try {
28262
+ (deps.notify ?? (() => {
28263
+ }))(text);
28264
+ } catch {
28265
+ }
28266
+ };
28267
+ const forceExit = deps.forceExit ?? ((code) => process.exit(code));
28268
+ let first;
28269
+ let toreDown = false;
28270
+ let standing = () => ({ created: /* @__PURE__ */ new Map(), inFlight: [] });
28271
+ const heard = (signal) => {
28272
+ if (first !== void 0) {
28273
+ try {
28274
+ const { created, inFlight } = standing();
28275
+ const lines = [...created].map(([name, rawId]) => {
28276
+ try {
28277
+ const id = singleLine(String(rawId), 200);
28278
+ return ` ${name}: retire it with DELETE /v1/agents/${id} (needs agents:d), then erase it with DELETE /v1/agents/${id}?purge=true
28279
+ `;
28280
+ } catch {
28281
+ return ` ${name}: its id could not be shown; find it with \`vectros agents list --context ${run.contextId} --include-retired\`
28282
+ `;
28283
+ }
28284
+ });
28285
+ const pending = inFlight.map((name) => ` ${name}: a create was sent and not answered; find it with \`vectros agents list --context ${run.contextId} --include-retired\`
28286
+ `);
28287
+ say(
28288
+ `
28289
+ \u2716 ${signal} again: leaving before teardown finished.
28290
+ ` + (lines.length > 0 ? ` Agents this run created, which may still be active:
28291
+ ${lines.join("")}` : "") + (pending.length > 0 ? ` Creates that may have gone through:
28292
+ ${pending.join("")}` : "") + (lines.length + pending.length === 0 ? " This run had not created, or begun to create, an agent.\n" : "") + ` Not listed: anything else the run wrote in '${run.contextId}' (schemas, roles, the access profile, a scoped key named '${run.keyName}').
28293
+ `
28294
+ );
28295
+ } catch {
28296
+ }
28297
+ forceExit(first === "SIGTERM" || signal === "SIGTERM" ? 143 : 130);
28298
+ return;
28299
+ }
28300
+ first = signal;
28301
+ say(
28302
+ run.keep ? `
28303
+ \u26A0 ${signal}: no further agents will be written; any other step running or still to come (a key mint, seed records) runs to its end. --keep was given, so nothing will be torn down. Send it again to leave now.
28304
+ ` : toreDown ? `
28305
+ \u26A0 ${signal}: teardown is already running and will finish. Send it again to leave now.
28306
+ ` : `
28307
+ \u26A0 ${signal}: no further agents will be written; any other step running or still to come (a key mint, seed records) runs to its end, then what this run created is torn down. Send it again to leave now.
28308
+ `
28309
+ );
28310
+ };
28311
+ const onInt = () => heard("SIGINT");
28312
+ const onTerm = () => heard("SIGTERM");
28313
+ source.on("SIGINT", onInt);
28314
+ source.on("SIGTERM", onTerm);
28315
+ return {
28316
+ get signal() {
28317
+ return first;
28318
+ },
28319
+ throwIfInterrupted() {
28320
+ if (first !== void 0) throw new HarnessInterrupted(first);
28321
+ },
28322
+ setAgentStanding(fn) {
28323
+ standing = fn;
28324
+ },
28325
+ tearingDown() {
28326
+ toreDown = true;
28327
+ },
28328
+ dispose() {
28329
+ source.off("SIGINT", onInt);
28330
+ source.off("SIGTERM", onTerm);
28331
+ }
28332
+ };
28333
+ }
26647
28334
  async function runBlueprintHarness(client, input, deps) {
28335
+ const interrupt = watchForInterrupt(deps, {
28336
+ keep: input.keep ?? false,
28337
+ contextId: input.blueprint.contextId,
28338
+ keyName: keyNameFor(input.blueprint.name, input.machineId)
28339
+ });
28340
+ try {
28341
+ return await runHarness(client, input, deps, interrupt);
28342
+ } catch (err) {
28343
+ if (interrupt.signal !== void 0) {
28344
+ let carried = err;
28345
+ try {
28346
+ carried = Object.assign(err !== null && typeof err === "object" ? err : new Error(String(err)), { interruptedBy: interrupt.signal });
28347
+ } catch {
28348
+ const shape = err;
28349
+ let message = "unknown error";
28350
+ try {
28351
+ message = typeof shape?.message === "string" ? shape.message : extractServerError(err).message;
28352
+ } catch {
28353
+ }
28354
+ carried = Object.assign(new Error(message, { cause: err }), {
28355
+ interruptedBy: interrupt.signal,
28356
+ ...shape && typeof shape === "object" && "statusCode" in shape ? { statusCode: shape.statusCode } : {},
28357
+ ...shape && typeof shape === "object" && "body" in shape ? { body: shape.body } : {}
28358
+ });
28359
+ }
28360
+ throw carried;
28361
+ }
28362
+ throw err;
28363
+ } finally {
28364
+ interrupt.dispose();
28365
+ }
28366
+ }
28367
+ async function runHarness(client, input, deps, interrupt) {
26648
28368
  const { tenantId, machineId, keep = false, log, remintForContext, remintBootstrapForContext } = input;
28369
+ const scoped = scopeAgentsToRun(input.blueprint, machineId);
28370
+ const runBlueprintInput = scoped.blueprint;
28371
+ const agentNames = Object.keys(scoped.names).length > 0 ? scoped.names : void 0;
26649
28372
  const readExistingKey = async () => void 0;
26650
28373
  const persistMintedKey = async () => {
26651
28374
  };
26652
28375
  const provisioned = await provisionContext(client, {
26653
- blueprint: input.blueprint,
28376
+ blueprint: runBlueprintInput,
26654
28377
  remintBootstrapForContext,
26655
28378
  readExistingKey,
26656
28379
  log
26657
28380
  });
26658
28381
  const blueprint = await resolveApplyTimeIdentities(
26659
- input.blueprint,
28382
+ runBlueprintInput,
26660
28383
  provisioned,
26661
28384
  client,
26662
28385
  log
@@ -26699,10 +28422,19 @@ async function runBlueprintHarness(client, input, deps) {
26699
28422
  existingIssuerIds: issuerSnapshot.existingIssuerIds,
26700
28423
  failed: contextSnapshot.failed || issuerSnapshot.failed
26701
28424
  };
28425
+ const createdAgentIds = /* @__PURE__ */ new Map();
28426
+ const seenAgentNames = /* @__PURE__ */ new Set();
28427
+ const attemptedAgentNames = /* @__PURE__ */ new Set();
28428
+ const refusedAgentNames = /* @__PURE__ */ new Set();
26702
28429
  let result;
26703
28430
  let assertions = [];
26704
28431
  let applyError;
26705
- try {
28432
+ interrupt.setAgentStanding(() => ({
28433
+ created: createdAgentIds,
28434
+ inFlight: [...attemptedAgentNames].filter((n) => !createdAgentIds.has(n) && !seenAgentNames.has(n) && !refusedAgentNames.has(n))
28435
+ }));
28436
+ const applyAndAssert = (async () => {
28437
+ interrupt.throwIfInterrupted();
26706
28438
  result = await runLoader(client, {
26707
28439
  blueprint,
26708
28440
  tenantId,
@@ -26713,6 +28445,13 @@ async function runBlueprintHarness(client, input, deps) {
26713
28445
  provisioned,
26714
28446
  remintForContext,
26715
28447
  remintBootstrapForContext,
28448
+ onAgentCreated: ({ name, id }) => createdAgentIds.set(name, id),
28449
+ onAgentSeen: ({ name }) => seenAgentNames.add(name),
28450
+ onAgentAttempt: (name) => {
28451
+ if (interrupt.signal !== void 0) return false;
28452
+ attemptedAgentNames.add(name);
28453
+ },
28454
+ onAgentRefused: (name) => refusedAgentNames.add(name),
26716
28455
  // CREATED-ONLY, and this is the seam that keeps it true for triggers. The loader's step 5d
26717
28456
  // reconciles triggers by ABSENCE — it deletes rules this blueprint provisioned and no longer
26718
28457
  // declares. That is right for a real bootstrap run, which owns its context; it is wrong here.
@@ -26730,6 +28469,7 @@ async function runBlueprintHarness(client, input, deps) {
26730
28469
  // fresh, collision-free name, exactly as the harness already expects for schemas/roles/issuers.
26731
28470
  });
26732
28471
  log.debug({ component: "harness", contextId: result.contextId, keyName: result.keyName }, "applied");
28472
+ interrupt.throwIfInterrupted();
26733
28473
  assertions = await runAssertions(
26734
28474
  await remint(blueprint.contextId),
26735
28475
  hasIssuers || hasNamespaces ? await remintBootstrap(blueprint.contextId) : client,
@@ -26738,10 +28478,12 @@ async function runBlueprintHarness(client, input, deps) {
26738
28478
  tenantId,
26739
28479
  result
26740
28480
  );
26741
- } catch (e) {
28481
+ })().catch((e) => {
26742
28482
  applyError = errMsg(e);
26743
28483
  log.warn({ component: "harness", err: applyError }, "apply phase failed");
26744
- }
28484
+ });
28485
+ await applyAndAssert;
28486
+ interrupt.tearingDown();
26745
28487
  let teardown = [];
26746
28488
  if (!keep) {
26747
28489
  let tdClient = client;
@@ -26756,9 +28498,22 @@ async function runBlueprintHarness(client, input, deps) {
26756
28498
  } catch {
26757
28499
  }
26758
28500
  }
26759
- teardown = await runTeardown(tdClient, tdBootstrapClient, blueprint, machineId, snapshot, result);
28501
+ teardown = await runTeardown(
28502
+ tdClient,
28503
+ tdBootstrapClient,
28504
+ blueprint,
28505
+ machineId,
28506
+ snapshot,
28507
+ result,
28508
+ new Set(Object.values(scoped.names)),
28509
+ createdAgentIds,
28510
+ seenAgentNames,
28511
+ attemptedAgentNames,
28512
+ refusedAgentNames
28513
+ );
26760
28514
  }
26761
- const passed = applyError === void 0 && assertions.length > 0 && assertions.every((a) => a.ok);
28515
+ const shownAgentNames = agentNames && interrupt.signal ? onlyAttempted(agentNames, attemptedAgentNames) : agentNames;
28516
+ const passed = applyError === void 0 && assertions.length > 0 && assertions.every((a) => a.ok) && !teardown.some(isAgentRemovalFailure) && interrupt.signal === void 0;
26762
28517
  return {
26763
28518
  passed,
26764
28519
  applied: result ? {
@@ -26772,7 +28527,9 @@ async function runBlueprintHarness(client, input, deps) {
26772
28527
  assertions,
26773
28528
  teardown,
26774
28529
  kept: keep,
26775
- error: applyError
28530
+ error: applyError,
28531
+ ...shownAgentNames ? { agentNames: shownAgentNames } : {},
28532
+ ...interrupt.signal ? { interrupted: interrupt.signal } : {}
26776
28533
  };
26777
28534
  }
26778
28535
 
@@ -26786,6 +28543,47 @@ async function resolveTestBlueprint(fileOrName) {
26786
28543
  }
26787
28544
  return validateBlueprint(raw);
26788
28545
  }
28546
+ function noticeIoForFd(fd, maxWaitMs = 2e3) {
28547
+ return {
28548
+ write: (bytes, offset) => (0, import_node_fs6.writeSync)(fd, bytes, offset),
28549
+ sleep: (ms) => void Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms),
28550
+ maxWaitMs
28551
+ };
28552
+ }
28553
+ var stderrIo = noticeIoForFd(2);
28554
+ function writeNotice(text, io = stderrIo) {
28555
+ const bytes = Buffer.from(text);
28556
+ const giveUp = () => {
28557
+ try {
28558
+ io.write(Buffer.from("\n [notice truncated]\n"), 0);
28559
+ } catch {
28560
+ }
28561
+ };
28562
+ let done = 0;
28563
+ let waited = 0;
28564
+ while (done < bytes.length) {
28565
+ try {
28566
+ const wrote = io.write(bytes, done);
28567
+ if (wrote > 0) {
28568
+ done += wrote;
28569
+ continue;
28570
+ }
28571
+ if (waited >= io.maxWaitMs) return giveUp();
28572
+ io.sleep(10);
28573
+ waited += 10;
28574
+ } catch (err) {
28575
+ if (err.code !== "EAGAIN") return;
28576
+ if (waited >= io.maxWaitMs) return giveUp();
28577
+ io.sleep(10);
28578
+ waited += 10;
28579
+ }
28580
+ }
28581
+ }
28582
+ function harnessExitCode(report) {
28583
+ if (report.interrupted === "SIGINT") return 130;
28584
+ if (report.interrupted === "SIGTERM") return 143;
28585
+ return report.passed ? 0 : 1;
28586
+ }
26789
28587
  function renderReport(blueprint, env, report) {
26790
28588
  const lines = [];
26791
28589
  lines.push("");
@@ -26796,12 +28594,16 @@ function renderReport(blueprint, env, report) {
26796
28594
  ` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed(s)` : "")
26797
28595
  );
26798
28596
  } else {
26799
- lines.push(` \u2716 apply failed \u2014 ${report.error ?? "unknown error"}`);
28597
+ lines.push(` \u2716 apply failed \u2014 ${singleLine(report.error ?? "unknown error", 600)}`);
28598
+ }
28599
+ if (report.agentNames && Object.keys(report.agentNames).length > 0) {
28600
+ lines.push(" Agents ran under names of their own, so a repeat run never meets this run's agents:");
28601
+ for (const [declared, ran] of Object.entries(report.agentNames)) lines.push(` ${declared} \u2192 ${ran}`);
26800
28602
  }
26801
28603
  if (report.assertions.length) {
26802
28604
  lines.push("");
26803
28605
  lines.push(" Assertions:");
26804
- for (const a of report.assertions) lines.push(` ${a.ok ? "\u2714" : "\u2716"} ${a.name}: ${a.detail}`);
28606
+ for (const a of report.assertions) lines.push(` ${a.ok ? "\u2714" : "\u2716"} ${singleLine(a.name, 200)}: ${singleLine(a.detail, 600)}`);
26805
28607
  }
26806
28608
  if (report.kept) {
26807
28609
  lines.push("");
@@ -26811,11 +28613,19 @@ function renderReport(blueprint, env, report) {
26811
28613
  lines.push(" Teardown:");
26812
28614
  for (const t of report.teardown) {
26813
28615
  const mark = !t.ok ? "\u2716" : t.skipped ? "\u21B7" : "\u2714";
26814
- lines.push(` ${mark} ${t.step}${t.error ? ` \u2014 ${t.error}` : ""}`);
28616
+ lines.push(` ${mark} ${singleLine(t.step, 300)}${t.error ? ` \u2014 ${singleLine(t.error, 600)}` : ""}`);
26815
28617
  }
26816
28618
  const tdFail = report.teardown.filter((t) => !t.ok);
26817
28619
  if (tdFail.length) lines.push(` \u26A0 ${tdFail.length} teardown step(s) failed \u2014 manual cleanup may be needed.`);
26818
28620
  }
28621
+ if (report.interrupted) {
28622
+ lines.push(
28623
+ report.kept ? ` \u2716 Stopped by ${report.interrupted}: no further agents were written, and teardown was skipped (--keep), so what this run created is still in place. The run does not pass.` : ` \u2716 Stopped by ${report.interrupted}: no further agents were written${report.assertions.length === 0 ? " and the assertions were skipped" : ""}, and what this run created was torn down as after any failure. The run does not pass.`
28624
+ );
28625
+ }
28626
+ if (report.teardown.some(isAgentRemovalFailure)) {
28627
+ lines.push(" \u2716 An agent could not be retired or purged, so this run fails: it is still active or still holds its content.");
28628
+ }
26819
28629
  lines.push("");
26820
28630
  lines.push(report.passed ? " RESULT: PASS" : " RESULT: FAIL");
26821
28631
  lines.push("");
@@ -26844,8 +28654,15 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
26844
28654
  `);
26845
28655
  return 1;
26846
28656
  }
28657
+ const agentNamesIssue = agentNamesProblem(blueprint);
28658
+ if (agentNamesIssue) {
28659
+ process.stderr.write(` \u2716 ${agentNamesIssue}
28660
+ Nothing was changed.
28661
+ `);
28662
+ return 2;
28663
+ }
26847
28664
  return withAuth({ ...opts, provisioning: true }, deps, async (auth, log) => {
26848
- const machineId = `harness-${Date.now()}-${(0, import_node_crypto4.randomUUID)().slice(0, 8)}`;
28665
+ const machineId = `harness-${Date.now()}-${(0, import_node_crypto5.randomUUID)().slice(0, 8)}`;
26849
28666
  try {
26850
28667
  const tenantId = requireTenantId(auth);
26851
28668
  const client = auth.client;
@@ -26885,10 +28702,10 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
26885
28702
  environment: auth.baseUrl
26886
28703
  });
26887
28704
  };
26888
- const report = await runBlueprintHarness(
28705
+ const report = await (deps.runHarness ?? runBlueprintHarness)(
26889
28706
  client,
26890
28707
  { blueprint, tenantId, machineId, keep: opts.keep, log, remintForContext, remintBootstrapForContext },
26891
- { pingWithKey }
28708
+ { pingWithKey, notify: deps.notify ?? ((text) => writeNotice(text)) }
26892
28709
  );
26893
28710
  if (opts.json) {
26894
28711
  const envelope = {
@@ -26902,10 +28719,16 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
26902
28719
  process.stdout.write(`${renderReport(blueprint, auth.env, report)}
26903
28720
  `);
26904
28721
  }
26905
- return report.passed ? 0 : 1;
28722
+ return harnessExitCode(report);
26906
28723
  } catch (err) {
26907
- process.stderr.write(formatCliError(err, { action: "blueprint-test failed" }));
26908
- return 1;
28724
+ process.stderr.write(formatCliError(sanitizedAgentError(err), { action: "blueprint-test failed" }));
28725
+ const by = err?.interruptedBy;
28726
+ if (by && opts.json) {
28727
+ const failed = { passed: false, assertions: [], teardown: [], kept: Boolean(opts.keep), error: singleLine(sanitizedAgentError(err).message, 600), interrupted: by };
28728
+ process.stdout.write(`${formatJson({ blueprint: { name: blueprint.name, version: blueprint.version, contextId: blueprint.contextId }, env: auth.env, ...failed })}
28729
+ `);
28730
+ }
28731
+ return by === "SIGINT" ? 130 : by === "SIGTERM" ? 143 : 1;
26909
28732
  }
26910
28733
  });
26911
28734
  }
@@ -27089,9 +28912,9 @@ function emitJoined(opts) {
27089
28912
 
27090
28913
  // src/build-info.ts
27091
28914
  var BUILD_INFO = {
27092
- cli: true ? "0.26.0" : "dev",
27093
- sdk: true ? "0.48.0" : "dev",
27094
- blueprints: true ? "0.20.3" : "dev"
28915
+ cli: true ? "0.27.0" : "dev",
28916
+ sdk: true ? "0.49.0" : "dev",
28917
+ blueprints: true ? "0.21.0" : "dev"
27095
28918
  };
27096
28919
  function formatBuildInfo() {
27097
28920
  return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
@@ -27128,6 +28951,17 @@ function parseTenantFlag(raw) {
27128
28951
  }
27129
28952
  return raw;
27130
28953
  }
28954
+ var POSITIVE_INT_INVALID = /* @__PURE__ */ Symbol("parsePositiveInt:invalid");
28955
+ function parsePositiveInt(raw, flag) {
28956
+ if (raw === void 0) return void 0;
28957
+ if (!/^[1-9][0-9]{0,8}$/.test(raw)) {
28958
+ process.stderr.write(` \u2716 ${flag} must be a positive whole number (got '${raw}')
28959
+ `);
28960
+ process.exitCode = 2;
28961
+ return POSITIVE_INT_INVALID;
28962
+ }
28963
+ return Number(raw);
28964
+ }
27131
28965
  function toCommonOptions(flags) {
27132
28966
  return {
27133
28967
  env: flags.env,
@@ -27182,6 +29016,7 @@ function buildProgram(deps = {}) {
27182
29016
  registerDomainsCommands(program, deps);
27183
29017
  registerNamespaceCommands(program, deps);
27184
29018
  registerScriptsCommands(program, deps);
29019
+ registerAgentsCommands(program, deps);
27185
29020
  registerAssumeCommand(program, deps);
27186
29021
  registerJoinCommand(program, deps);
27187
29022
  registerKeyringCommands(program);
@@ -27637,6 +29472,57 @@ function registerScriptsCommands(program, deps) {
27637
29472
  }
27638
29473
  );
27639
29474
  }
29475
+ function registerAgentsCommands(program, deps) {
29476
+ const agents = program.command("agents").description("Manage stored agents (named, versioned model + prompt + tool whitelist definitions).");
29477
+ const list = agents.command("list").description("List the agents in a context, each at its newest version.").requiredOption("--context <c>", "app context to read").option("--include-retired", "also list retired agents").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
29478
+ addCommonAuthOptions(list).action(
29479
+ async (flags) => {
29480
+ const tenant = parseTenantFlag(flags.tenant);
29481
+ if (tenant === TENANT_FLAG_INVALID) return;
29482
+ process.exitCode = await runAgentsList(
29483
+ { ...toCommonOptions(flags), context: flags.context, includeRetired: Boolean(flags.includeRetired), tenant },
29484
+ deps
29485
+ );
29486
+ }
29487
+ );
29488
+ const get = agents.command("get").description("Show one agent at its newest version, or at an earlier one with --at-version.").argument("<id>", "the agent's id (see `agents list`)").requiredOption("--context <c>", "app context to read").option("--at-version <n>", "show an earlier version number (the newest when omitted)").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
29489
+ addCommonAuthOptions(get).action(
29490
+ async (id, flags) => {
29491
+ const tenant = parseTenantFlag(flags.tenant);
29492
+ if (tenant === TENANT_FLAG_INVALID) return;
29493
+ const version2 = parsePositiveInt(flags.atVersion, "--at-version");
29494
+ if (version2 === POSITIVE_INT_INVALID) return;
29495
+ process.exitCode = await runAgentsGet(
29496
+ { ...toCommonOptions(flags), id, context: flags.context, version: version2, tenant },
29497
+ deps
29498
+ );
29499
+ }
29500
+ );
29501
+ const create = agents.command("create").description("Store an agent at version 1 from a definition file (an existing name is returned unchanged).").argument("<name>", "agent name: 1-64 chars of letters, digits, '_' and '-'").requiredOption("--context <c>", "app context to create it in").requiredOption("--file <path>", "JSON or YAML file holding the definition (model, system, tools, limits, budget)").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
29502
+ addCommonAuthOptions(create).action(
29503
+ async (name, flags) => {
29504
+ const tenant = parseTenantFlag(flags.tenant);
29505
+ if (tenant === TENANT_FLAG_INVALID) return;
29506
+ process.exitCode = await runAgentsCreate(
29507
+ { ...toCommonOptions(flags), name, context: flags.context, file: flags.file, tenant },
29508
+ deps
29509
+ );
29510
+ }
29511
+ );
29512
+ const version = agents.command("version").description("Write the next version of an agent from a definition file (a full replacement: omitted fields take their defaults).").argument("<id>", "the agent's id (see `agents list`)").requiredOption("--context <c>", "app context the agent is in").requiredOption("--file <path>", "JSON or YAML file holding the new definition").option("--expected-version <n>", "refuse the write if the agent has a newer version than this one").option("--restore", "bring a retired agent back, with this definition as its next version").option("--tenant <tenant>", "target tenant: 'test' or 'live' (default live)");
29513
+ addCommonAuthOptions(version).action(
29514
+ async (id, flags) => {
29515
+ const tenant = parseTenantFlag(flags.tenant);
29516
+ if (tenant === TENANT_FLAG_INVALID) return;
29517
+ const expectedVersion = parsePositiveInt(flags.expectedVersion, "--expected-version");
29518
+ if (expectedVersion === POSITIVE_INT_INVALID) return;
29519
+ process.exitCode = await runAgentsVersion(
29520
+ { ...toCommonOptions(flags), id, context: flags.context, file: flags.file, expectedVersion, restore: Boolean(flags.restore), tenant },
29521
+ deps
29522
+ );
29523
+ }
29524
+ );
29525
+ }
27640
29526
  function registerRoleCommands(program, deps) {
27641
29527
  const role = program.command("role").description("Manage context-scoped roles (reusable scope rules; bind via `access grant --role`).");
27642
29528
  const create = role.command("create").description("Create (idempotent by roleId) a single-clause role from --actions.").requiredOption("--context <c>", "app context").requiredOption("--role-id <id>", "role id (3-31 chars; lowercase letter + lowercase/digit/dash)").requiredOption("--name <name>", "human-readable name").requiredOption("--actions <csv>", "allowed actions, comma-separated (e.g. records:cru,search:r)").option(
@@ -27962,10 +29848,10 @@ async function main() {
27962
29848
  await buildProgram().parseAsync(process.argv);
27963
29849
  }
27964
29850
  main().catch((err) => {
27965
- process.stderr.write(`fatal: ${err instanceof Error ? err.message : String(err)}
29851
+ process.stderr.write(`fatal: ${errorText(err)}
27966
29852
  `);
27967
29853
  if (process.env.VECTROS_MCP_DEBUG && err instanceof Error && err.stack) {
27968
- process.stderr.write(`${err.stack}
29854
+ process.stderr.write(`${cleanBlock(err.stack)}
27969
29855
  `);
27970
29856
  }
27971
29857
  process.exitCode = 2;