@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.mjs CHANGED
@@ -526,8 +526,8 @@ var require_graceful_fs = __commonJS({
526
526
  fs5.createReadStream = createReadStream3;
527
527
  fs5.createWriteStream = createWriteStream;
528
528
  var fs$readFile = fs5.readFile;
529
- fs5.readFile = readFile4;
530
- function readFile4(path4, options, cb) {
529
+ fs5.readFile = readFile5;
530
+ function readFile5(path4, options, cb) {
531
531
  if (typeof options === "function")
532
532
  cb = options, options = null;
533
533
  return go$readFile(path4, options, cb);
@@ -1619,7 +1619,7 @@ var require_proper_lockfile = __commonJS({
1619
1619
  // src/cli-bootstrap.ts
1620
1620
  import * as readline2 from "readline/promises";
1621
1621
 
1622
- // ../../node_modules/@vectros-ai/sdk/dist/index.mjs
1622
+ // node_modules/@vectros-ai/sdk/dist/index.mjs
1623
1623
  var __defProp2 = Object.defineProperty;
1624
1624
  var __export = (target, all) => {
1625
1625
  for (var name in all)
@@ -1640,6 +1640,7 @@ __export(api_exports, {
1640
1640
  ContentTooLargeError: () => ContentTooLargeError,
1641
1641
  DocumentAskRequest: () => DocumentAskRequest,
1642
1642
  DocumentContextEvent: () => DocumentContextEvent,
1643
+ DocumentExtraction: () => DocumentExtraction,
1643
1644
  DocumentLookupRequest: () => DocumentLookupRequest,
1644
1645
  DocumentPatchRequest: () => DocumentPatchRequest,
1645
1646
  DocumentRequest: () => DocumentRequest,
@@ -1692,6 +1693,7 @@ __export(api_exports, {
1692
1693
  UserExistsResponse: () => UserExistsResponse,
1693
1694
  UserRequest: () => UserRequest,
1694
1695
  UserResponse: () => UserResponse,
1696
+ agents: () => agents_exports,
1695
1697
  auth: () => auth_exports,
1696
1698
  compliance: () => compliance_exports,
1697
1699
  documents: () => documents_exports,
@@ -1957,6 +1959,7 @@ var UnprocessableEntityError = class extends VectrosError {
1957
1959
  this.name = this.constructor.name;
1958
1960
  }
1959
1961
  };
1962
+ var agents_exports = {};
1960
1963
  var auth_exports = {};
1961
1964
  var ErasureRequest;
1962
1965
  ((ErasureRequest2) => {
@@ -2161,6 +2164,18 @@ var DocumentContextEvent;
2161
2164
  DocumentContext: "document_context"
2162
2165
  };
2163
2166
  })(DocumentContextEvent || (DocumentContextEvent = {}));
2167
+ var DocumentExtraction;
2168
+ ((DocumentExtraction2) => {
2169
+ DocumentExtraction2.Outcome = {
2170
+ Ok: "OK",
2171
+ Truncated: "TRUNCATED",
2172
+ NoText: "NO_TEXT",
2173
+ Encrypted: "ENCRYPTED",
2174
+ Unsupported: "UNSUPPORTED",
2175
+ TooLarge: "TOO_LARGE",
2176
+ Unreadable: "UNREADABLE"
2177
+ };
2178
+ })(DocumentExtraction || (DocumentExtraction = {}));
2164
2179
  var DocumentRequest;
2165
2180
  ((DocumentRequest2) => {
2166
2181
  DocumentRequest2.IndexMode = {
@@ -4029,24 +4044,32 @@ function handleNonStatusCodeError(error, rawResponse, method, path4) {
4029
4044
  });
4030
4045
  }
4031
4046
  }
4032
- var AuthClient = class {
4047
+ var AgentsClient = class {
4033
4048
  constructor(options) {
4034
4049
  this._options = normalizeClientOptionsWithAuth(options);
4035
4050
  }
4036
4051
  /**
4037
- * 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`.
4052
+ * 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`.
4038
4053
  *
4039
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4054
+ * @param {Vectros.ListAgentsRequest} request
4055
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4040
4056
  *
4041
- * @throws {@link Vectros.InternalServerError}
4057
+ * @throws {@link Vectros.ForbiddenError}
4042
4058
  *
4043
4059
  * @example
4044
- * await client.auth.getJwks()
4060
+ * await client.agents.listAgents()
4045
4061
  */
4046
- getJwks(requestOptions) {
4047
- return HttpResponsePromise.fromPromise(this.__getJwks(requestOptions));
4062
+ listAgents(request = {}, requestOptions) {
4063
+ return HttpResponsePromise.fromPromise(this.__listAgents(request, requestOptions));
4048
4064
  }
4049
- async __getJwks(requestOptions) {
4065
+ async __listAgents(request = {}, requestOptions) {
4066
+ const { contextId, includeRetired, startFrom, limit } = request;
4067
+ const _queryParams = {
4068
+ contextId,
4069
+ includeRetired,
4070
+ startFrom,
4071
+ limit
4072
+ };
4050
4073
  const _authRequest = await this._options.authProvider.getAuthRequest();
4051
4074
  const _headers = mergeHeaders(
4052
4075
  _authRequest.headers,
@@ -4056,11 +4079,11 @@ var AuthClient = class {
4056
4079
  const _response = await fetcher({
4057
4080
  url: url_exports.join(
4058
4081
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4059
- "v1/auth/jwks"
4082
+ "v1/agents"
4060
4083
  ),
4061
4084
  method: "GET",
4062
4085
  headers: _headers,
4063
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4086
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4064
4087
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4065
4088
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4066
4089
  abortSignal: requestOptions?.abortSignal,
@@ -4072,8 +4095,8 @@ var AuthClient = class {
4072
4095
  }
4073
4096
  if (_response.error.reason === "status-code") {
4074
4097
  switch (_response.error.statusCode) {
4075
- case 500:
4076
- throw new InternalServerError(_response.error.body, _response.rawResponse);
4098
+ case 403:
4099
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4077
4100
  default:
4078
4101
  throw new VectrosError({
4079
4102
  statusCode: _response.error.statusCode,
@@ -4082,59 +4105,34 @@ var AuthClient = class {
4082
4105
  });
4083
4106
  }
4084
4107
  }
4085
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/jwks");
4108
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/agents");
4086
4109
  }
4087
4110
  /**
4088
- * 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.
4111
+ * 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`).
4089
4112
  *
4090
- * @param {Vectros.GetAccessLogRequest} request
4091
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4113
+ * @param {Vectros.CreateAgentRequest} request
4114
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4092
4115
  *
4093
4116
  * @throws {@link Vectros.BadRequestError}
4094
4117
  * @throws {@link Vectros.ForbiddenError}
4118
+ * @throws {@link Vectros.ConflictError}
4119
+ * @throws {@link Vectros.TooManyRequestsError}
4095
4120
  *
4096
4121
  * @example
4097
- * await client.auth.getAccessLog({
4098
- * subjectType: "user",
4099
- * subjectId: "user_abc123",
4100
- * contextId: "ctx_intake",
4101
- * action: "read",
4102
- * callerKeyId: "key_abc123",
4103
- * resourceType: "intake_form",
4104
- * resourceId: "rec_456"
4122
+ * await client.agents.createAgent({
4123
+ * body: {
4124
+ * model: "claude-sonnet-5-5"
4125
+ * }
4105
4126
  * })
4106
4127
  */
4107
- getAccessLog(request = {}, requestOptions) {
4108
- return HttpResponsePromise.fromPromise(this.__getAccessLog(request, requestOptions));
4128
+ createAgent(request, requestOptions) {
4129
+ return HttpResponsePromise.fromPromise(this.__createAgent(request, requestOptions));
4109
4130
  }
4110
- async __getAccessLog(request = {}, requestOptions) {
4111
- const {
4112
- subjectType,
4113
- subjectId,
4114
- contextId,
4115
- from: from_,
4116
- to,
4117
- action,
4118
- callerKeyId,
4119
- resourceType,
4120
- resourceId,
4121
- revealedSensitive,
4122
- startFrom,
4123
- limit
4124
- } = request;
4131
+ async __createAgent(request, requestOptions) {
4132
+ const { upsert, contextId, body: _body } = request;
4125
4133
  const _queryParams = {
4126
- subjectType,
4127
- subjectId,
4128
- contextId,
4129
- from: from_,
4130
- to,
4131
- action,
4132
- callerKeyId,
4133
- resourceType,
4134
- resourceId,
4135
- revealedSensitive,
4136
- startFrom,
4137
- limit
4134
+ upsert,
4135
+ contextId
4138
4136
  };
4139
4137
  const _authRequest = await this._options.authProvider.getAuthRequest();
4140
4138
  const _headers = mergeHeaders(
@@ -4145,11 +4143,14 @@ var AuthClient = class {
4145
4143
  const _response = await fetcher({
4146
4144
  url: url_exports.join(
4147
4145
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4148
- "v1/admin/access-log"
4146
+ "v1/agents"
4149
4147
  ),
4150
- method: "GET",
4148
+ method: "POST",
4151
4149
  headers: _headers,
4150
+ contentType: "application/json",
4152
4151
  queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4152
+ requestType: "json",
4153
+ body: _body,
4153
4154
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4154
4155
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4155
4156
  abortSignal: requestOptions?.abortSignal,
@@ -4165,6 +4166,10 @@ var AuthClient = class {
4165
4166
  throw new BadRequestError(_response.error.body, _response.rawResponse);
4166
4167
  case 403:
4167
4168
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4169
+ case 409:
4170
+ throw new ConflictError(_response.error.body, _response.rawResponse);
4171
+ case 429:
4172
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4168
4173
  default:
4169
4174
  throw new VectrosError({
4170
4175
  statusCode: _response.error.statusCode,
@@ -4173,22 +4178,31 @@ var AuthClient = class {
4173
4178
  });
4174
4179
  }
4175
4180
  }
4176
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/access-log");
4181
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/agents");
4177
4182
  }
4178
4183
  /**
4179
- * 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.
4184
+ * Returns the agent at its latest version, or at `version`. Requires the `agents:r` scope.
4180
4185
  *
4181
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4186
+ * @param {Vectros.GetAgentRequest} request
4187
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4182
4188
  *
4183
4189
  * @throws {@link Vectros.ForbiddenError}
4190
+ * @throws {@link Vectros.NotFoundError}
4184
4191
  *
4185
4192
  * @example
4186
- * await client.auth.listScopedKeys()
4193
+ * await client.agents.getAgent({
4194
+ * id: "id"
4195
+ * })
4187
4196
  */
4188
- listScopedKeys(requestOptions) {
4189
- return HttpResponsePromise.fromPromise(this.__listScopedKeys(requestOptions));
4197
+ getAgent(request, requestOptions) {
4198
+ return HttpResponsePromise.fromPromise(this.__getAgent(request, requestOptions));
4190
4199
  }
4191
- async __listScopedKeys(requestOptions) {
4200
+ async __getAgent(request, requestOptions) {
4201
+ const { id, contextId, version } = request;
4202
+ const _queryParams = {
4203
+ contextId,
4204
+ version
4205
+ };
4192
4206
  const _authRequest = await this._options.authProvider.getAuthRequest();
4193
4207
  const _headers = mergeHeaders(
4194
4208
  _authRequest.headers,
@@ -4198,11 +4212,11 @@ var AuthClient = class {
4198
4212
  const _response = await fetcher({
4199
4213
  url: url_exports.join(
4200
4214
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4201
- "v1/admin/keys/scoped"
4215
+ `v1/agents/${url_exports.encodePathParam(id)}`
4202
4216
  ),
4203
4217
  method: "GET",
4204
4218
  headers: _headers,
4205
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4219
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4206
4220
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4207
4221
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4208
4222
  abortSignal: requestOptions?.abortSignal,
@@ -4216,6 +4230,8 @@ var AuthClient = class {
4216
4230
  switch (_response.error.statusCode) {
4217
4231
  case 403:
4218
4232
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4233
+ case 404:
4234
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4219
4235
  default:
4220
4236
  throw new VectrosError({
4221
4237
  statusCode: _response.error.statusCode,
@@ -4224,13 +4240,13 @@ var AuthClient = class {
4224
4240
  });
4225
4241
  }
4226
4242
  }
4227
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped");
4243
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/agents/{id}");
4228
4244
  }
4229
4245
  /**
4230
- * 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.
4246
+ * 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.
4231
4247
  *
4232
- * @param {Vectros.CreateScopedKeyRequest} request
4233
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4248
+ * @param {Vectros.UpdateAgentRequest} request
4249
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4234
4250
  *
4235
4251
  * @throws {@link Vectros.BadRequestError}
4236
4252
  * @throws {@link Vectros.ForbiddenError}
@@ -4239,17 +4255,22 @@ var AuthClient = class {
4239
4255
  * @throws {@link Vectros.TooManyRequestsError}
4240
4256
  *
4241
4257
  * @example
4242
- * await client.auth.createScopedKey({
4243
- * keyName: "research-bot production",
4244
- * tenantId: "ten_live_xxx",
4245
- * contextId: "myapp",
4246
- * userId: "alice-001"
4258
+ * await client.agents.updateAgent({
4259
+ * id: "id",
4260
+ * body: {
4261
+ * model: "claude-sonnet-5-5"
4262
+ * }
4247
4263
  * })
4248
4264
  */
4249
- createScopedKey(request, requestOptions) {
4250
- return HttpResponsePromise.fromPromise(this.__createScopedKey(request, requestOptions));
4265
+ updateAgent(request, requestOptions) {
4266
+ return HttpResponsePromise.fromPromise(this.__updateAgent(request, requestOptions));
4251
4267
  }
4252
- async __createScopedKey(request, requestOptions) {
4268
+ async __updateAgent(request, requestOptions) {
4269
+ const { id, restore, contextId, body: _body } = request;
4270
+ const _queryParams = {
4271
+ restore,
4272
+ contextId
4273
+ };
4253
4274
  const _authRequest = await this._options.authProvider.getAuthRequest();
4254
4275
  const _headers = mergeHeaders(
4255
4276
  _authRequest.headers,
@@ -4259,14 +4280,14 @@ var AuthClient = class {
4259
4280
  const _response = await fetcher({
4260
4281
  url: url_exports.join(
4261
4282
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4262
- "v1/admin/keys/scoped"
4283
+ `v1/agents/${url_exports.encodePathParam(id)}`
4263
4284
  ),
4264
- method: "POST",
4285
+ method: "PUT",
4265
4286
  headers: _headers,
4266
4287
  contentType: "application/json",
4267
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4288
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4268
4289
  requestType: "json",
4269
- body: request,
4290
+ body: _body,
4270
4291
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4271
4292
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4272
4293
  abortSignal: requestOptions?.abortSignal,
@@ -4296,27 +4317,35 @@ var AuthClient = class {
4296
4317
  });
4297
4318
  }
4298
4319
  }
4299
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/admin/keys/scoped");
4320
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/agents/{id}");
4300
4321
  }
4301
4322
  /**
4302
- * 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.
4323
+ * 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.
4303
4324
  *
4304
- * @param {Vectros.GetScopedKeyRequest} request
4305
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4325
+ * @param {Vectros.DeleteAgentRequest} request
4326
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4306
4327
  *
4328
+ * @throws {@link Vectros.BadRequestError}
4307
4329
  * @throws {@link Vectros.ForbiddenError}
4308
4330
  * @throws {@link Vectros.NotFoundError}
4331
+ * @throws {@link Vectros.ConflictError}
4332
+ * @throws {@link Vectros.TooManyRequestsError}
4309
4333
  *
4310
4334
  * @example
4311
- * await client.auth.getScopedKey({
4312
- * keyId: "keyId"
4335
+ * await client.agents.deleteAgent({
4336
+ * id: "id"
4313
4337
  * })
4314
4338
  */
4315
- getScopedKey(request, requestOptions) {
4316
- return HttpResponsePromise.fromPromise(this.__getScopedKey(request, requestOptions));
4339
+ deleteAgent(request, requestOptions) {
4340
+ return HttpResponsePromise.fromPromise(this.__deleteAgent(request, requestOptions));
4317
4341
  }
4318
- async __getScopedKey(request, requestOptions) {
4319
- const { keyId } = request;
4342
+ async __deleteAgent(request, requestOptions) {
4343
+ const { id, purge, version, contextId } = request;
4344
+ const _queryParams = {
4345
+ purge,
4346
+ version,
4347
+ contextId
4348
+ };
4320
4349
  const _authRequest = await this._options.authProvider.getAuthRequest();
4321
4350
  const _headers = mergeHeaders(
4322
4351
  _authRequest.headers,
@@ -4326,11 +4355,11 @@ var AuthClient = class {
4326
4355
  const _response = await fetcher({
4327
4356
  url: url_exports.join(
4328
4357
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4329
- `v1/admin/keys/scoped/${url_exports.encodePathParam(keyId)}`
4358
+ `v1/agents/${url_exports.encodePathParam(id)}`
4330
4359
  ),
4331
- method: "GET",
4360
+ method: "DELETE",
4332
4361
  headers: _headers,
4333
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4362
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4334
4363
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4335
4364
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4336
4365
  abortSignal: requestOptions?.abortSignal,
@@ -4342,10 +4371,16 @@ var AuthClient = class {
4342
4371
  }
4343
4372
  if (_response.error.reason === "status-code") {
4344
4373
  switch (_response.error.statusCode) {
4374
+ case 400:
4375
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4345
4376
  case 403:
4346
4377
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4347
4378
  case 404:
4348
4379
  throw new NotFoundError(_response.error.body, _response.rawResponse);
4380
+ case 409:
4381
+ throw new ConflictError(_response.error.body, _response.rawResponse);
4382
+ case 429:
4383
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4349
4384
  default:
4350
4385
  throw new VectrosError({
4351
4386
  statusCode: _response.error.statusCode,
@@ -4354,28 +4389,33 @@ var AuthClient = class {
4354
4389
  });
4355
4390
  }
4356
4391
  }
4357
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped/{keyId}");
4392
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "DELETE", "/v1/agents/{id}");
4358
4393
  }
4359
4394
  /**
4360
- * 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.
4395
+ * 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.
4361
4396
  *
4362
- * @param {Vectros.RevokeScopedKeyRequest} request
4363
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4397
+ * @param {Vectros.ListAgentVersionsRequest} request
4398
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4364
4399
  *
4365
4400
  * @throws {@link Vectros.ForbiddenError}
4366
4401
  * @throws {@link Vectros.NotFoundError}
4367
- * @throws {@link Vectros.TooManyRequestsError}
4368
4402
  *
4369
4403
  * @example
4370
- * await client.auth.revokeScopedKey({
4371
- * keyId: "keyId"
4404
+ * await client.agents.listAgentVersions({
4405
+ * id: "id"
4372
4406
  * })
4373
4407
  */
4374
- revokeScopedKey(request, requestOptions) {
4375
- return HttpResponsePromise.fromPromise(this.__revokeScopedKey(request, requestOptions));
4408
+ listAgentVersions(request, requestOptions) {
4409
+ return HttpResponsePromise.fromPromise(this.__listAgentVersions(request, requestOptions));
4376
4410
  }
4377
- async __revokeScopedKey(request, requestOptions) {
4378
- const { keyId } = request;
4411
+ async __listAgentVersions(request, requestOptions) {
4412
+ const { id, contextId, includeContent, startFrom, limit } = request;
4413
+ const _queryParams = {
4414
+ contextId,
4415
+ includeContent,
4416
+ startFrom,
4417
+ limit
4418
+ };
4379
4419
  const _authRequest = await this._options.authProvider.getAuthRequest();
4380
4420
  const _headers = mergeHeaders(
4381
4421
  _authRequest.headers,
@@ -4385,11 +4425,11 @@ var AuthClient = class {
4385
4425
  const _response = await fetcher({
4386
4426
  url: url_exports.join(
4387
4427
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4388
- `v1/admin/keys/scoped/${url_exports.encodePathParam(keyId)}`
4428
+ `v1/agents/${url_exports.encodePathParam(id)}/versions`
4389
4429
  ),
4390
- method: "DELETE",
4430
+ method: "GET",
4391
4431
  headers: _headers,
4392
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4432
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4393
4433
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4394
4434
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4395
4435
  abortSignal: requestOptions?.abortSignal,
@@ -4397,7 +4437,7 @@ var AuthClient = class {
4397
4437
  logging: this._options.logging
4398
4438
  });
4399
4439
  if (_response.ok) {
4400
- return { data: void 0, rawResponse: _response.rawResponse };
4440
+ return { data: _response.body, rawResponse: _response.rawResponse };
4401
4441
  }
4402
4442
  if (_response.error.reason === "status-code") {
4403
4443
  switch (_response.error.statusCode) {
@@ -4405,8 +4445,6 @@ var AuthClient = class {
4405
4445
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4406
4446
  case 404:
4407
4447
  throw new NotFoundError(_response.error.body, _response.rawResponse);
4408
- case 429:
4409
- throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4410
4448
  default:
4411
4449
  throw new VectrosError({
4412
4450
  statusCode: _response.error.statusCode,
@@ -4415,45 +4453,32 @@ var AuthClient = class {
4415
4453
  });
4416
4454
  }
4417
4455
  }
4418
- return handleNonStatusCodeError(
4419
- _response.error,
4420
- _response.rawResponse,
4421
- "DELETE",
4422
- "/v1/admin/keys/scoped/{keyId}"
4423
- );
4456
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/agents/{id}/versions");
4424
4457
  }
4425
4458
  /**
4426
- * 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.
4459
+ * 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.
4427
4460
  *
4428
- * @param {Vectros.GetAdminLogsRequest} request
4429
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4461
+ * @param {Vectros.CancelRunRequest} request
4462
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4430
4463
  *
4431
4464
  * @throws {@link Vectros.BadRequestError}
4465
+ * @throws {@link Vectros.UnauthorizedError}
4432
4466
  * @throws {@link Vectros.ForbiddenError}
4467
+ * @throws {@link Vectros.NotFoundError}
4468
+ * @throws {@link Vectros.TooManyRequestsError}
4433
4469
  *
4434
4470
  * @example
4435
- * await client.auth.getAdminLogs({
4436
- * startTime: "startTime",
4437
- * resource: "documents",
4438
- * method: "POST",
4439
- * keyId: "key_abc123",
4440
- * contextId: "ctx_intake"
4471
+ * await client.agents.cancelRun({
4472
+ * id: "id"
4441
4473
  * })
4442
4474
  */
4443
- getAdminLogs(request, requestOptions) {
4444
- return HttpResponsePromise.fromPromise(this.__getAdminLogs(request, requestOptions));
4475
+ cancelRun(request, requestOptions) {
4476
+ return HttpResponsePromise.fromPromise(this.__cancelRun(request, requestOptions));
4445
4477
  }
4446
- async __getAdminLogs(request, requestOptions) {
4447
- const { startTime, endTime, resource, method, keyId, contextId, errorsOnly, limit } = request;
4478
+ async __cancelRun(request, requestOptions) {
4479
+ const { id, contextId } = request;
4448
4480
  const _queryParams = {
4449
- startTime,
4450
- endTime,
4451
- resource,
4452
- method,
4453
- keyId,
4454
- contextId,
4455
- errorsOnly,
4456
- limit
4481
+ contextId
4457
4482
  };
4458
4483
  const _authRequest = await this._options.authProvider.getAuthRequest();
4459
4484
  const _headers = mergeHeaders(
@@ -4464,9 +4489,9 @@ var AuthClient = class {
4464
4489
  const _response = await fetcher({
4465
4490
  url: url_exports.join(
4466
4491
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4467
- "v1/admin/logs"
4492
+ `v1/runs/${url_exports.encodePathParam(id)}/cancel`
4468
4493
  ),
4469
- method: "GET",
4494
+ method: "POST",
4470
4495
  headers: _headers,
4471
4496
  queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4472
4497
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
@@ -4482,8 +4507,14 @@ var AuthClient = class {
4482
4507
  switch (_response.error.statusCode) {
4483
4508
  case 400:
4484
4509
  throw new BadRequestError(_response.error.body, _response.rawResponse);
4510
+ case 401:
4511
+ throw new UnauthorizedError(_response.error.body, _response.rawResponse);
4485
4512
  case 403:
4486
4513
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4514
+ case 404:
4515
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4516
+ case 429:
4517
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4487
4518
  default:
4488
4519
  throw new VectrosError({
4489
4520
  statusCode: _response.error.statusCode,
@@ -4492,30 +4523,31 @@ var AuthClient = class {
4492
4523
  });
4493
4524
  }
4494
4525
  }
4495
- return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/logs");
4526
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/runs/{id}/cancel");
4496
4527
  }
4497
4528
  /**
4498
- * 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.
4529
+ * 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.
4499
4530
  *
4500
- * @param {Vectros.ListAccessProfilesRequest} request
4501
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4531
+ * @param {Vectros.GetRunRequest} request
4532
+ * @param {AgentsClient.RequestOptions} requestOptions - Request-specific configuration.
4502
4533
  *
4534
+ * @throws {@link Vectros.BadRequestError}
4535
+ * @throws {@link Vectros.UnauthorizedError}
4503
4536
  * @throws {@link Vectros.ForbiddenError}
4504
4537
  * @throws {@link Vectros.NotFoundError}
4505
4538
  *
4506
4539
  * @example
4507
- * await client.auth.listAccessProfiles({
4508
- * contextId: "myapp"
4540
+ * await client.agents.getRun({
4541
+ * id: "id"
4509
4542
  * })
4510
4543
  */
4511
- listAccessProfiles(request, requestOptions) {
4512
- return HttpResponsePromise.fromPromise(this.__listAccessProfiles(request, requestOptions));
4544
+ getRun(request, requestOptions) {
4545
+ return HttpResponsePromise.fromPromise(this.__getRun(request, requestOptions));
4513
4546
  }
4514
- async __listAccessProfiles(request, requestOptions) {
4515
- const { contextId, startFrom, limit } = request;
4547
+ async __getRun(request, requestOptions) {
4548
+ const { id, contextId } = request;
4516
4549
  const _queryParams = {
4517
- startFrom,
4518
- limit
4550
+ contextId
4519
4551
  };
4520
4552
  const _authRequest = await this._options.authProvider.getAuthRequest();
4521
4553
  const _headers = mergeHeaders(
@@ -4526,7 +4558,7 @@ var AuthClient = class {
4526
4558
  const _response = await fetcher({
4527
4559
  url: url_exports.join(
4528
4560
  await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4529
- `v1/app-contexts/${url_exports.encodePathParam(contextId)}/profiles`
4561
+ `v1/runs/${url_exports.encodePathParam(id)}`
4530
4562
  ),
4531
4563
  method: "GET",
4532
4564
  headers: _headers,
@@ -4542,6 +4574,10 @@ var AuthClient = class {
4542
4574
  }
4543
4575
  if (_response.error.reason === "status-code") {
4544
4576
  switch (_response.error.statusCode) {
4577
+ case 400:
4578
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4579
+ case 401:
4580
+ throw new UnauthorizedError(_response.error.body, _response.rawResponse);
4545
4581
  case 403:
4546
4582
  throw new ForbiddenError(_response.error.body, _response.rawResponse);
4547
4583
  case 404:
@@ -4554,31 +4590,635 @@ var AuthClient = class {
4554
4590
  });
4555
4591
  }
4556
4592
  }
4557
- return handleNonStatusCodeError(
4558
- _response.error,
4559
- _response.rawResponse,
4560
- "GET",
4561
- "/v1/app-contexts/{contextId}/profiles"
4562
- );
4593
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/runs/{id}");
4563
4594
  }
4564
4595
  /**
4565
- * 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.
4566
- *
4567
- * @param {Vectros.CreateAccessProfileRequest} request
4568
- * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4569
- *
4570
- * @throws {@link Vectros.BadRequestError}
4571
- * @throws {@link Vectros.ForbiddenError}
4572
- * @throws {@link Vectros.NotFoundError}
4573
- * @throws {@link Vectros.ConflictError}
4574
- * @throws {@link Vectros.TooManyRequestsError}
4575
- *
4576
- * @example
4596
+ * 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.
4597
+ */
4598
+ createRun(request, requestOptions) {
4599
+ return HttpResponsePromise.fromPromise(this.__createRun(request, requestOptions));
4600
+ }
4601
+ async __createRun(request, requestOptions) {
4602
+ const { "Idempotency-Key": idempotencyKey, ..._body } = request;
4603
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4604
+ const _headers = mergeHeaders(
4605
+ _authRequest.headers,
4606
+ this._options?.headers,
4607
+ mergeOnlyDefinedHeaders({ "Idempotency-Key": idempotencyKey }),
4608
+ requestOptions?.headers
4609
+ );
4610
+ const _response = await fetcher({
4611
+ url: url_exports.join(
4612
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4613
+ "v1/runs"
4614
+ ),
4615
+ method: "POST",
4616
+ headers: _headers,
4617
+ contentType: "application/json",
4618
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4619
+ requestType: "json",
4620
+ body: _body,
4621
+ responseType: "sse",
4622
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4623
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4624
+ abortSignal: requestOptions?.abortSignal,
4625
+ fetchFn: this._options?.fetch,
4626
+ logging: this._options.logging
4627
+ });
4628
+ if (_response.ok) {
4629
+ return {
4630
+ data: new Stream({
4631
+ stream: _response.body,
4632
+ parse: (data) => data,
4633
+ signal: requestOptions?.abortSignal,
4634
+ eventShape: {
4635
+ type: "sse"
4636
+ }
4637
+ }),
4638
+ rawResponse: _response.rawResponse
4639
+ };
4640
+ }
4641
+ if (_response.error.reason === "status-code") {
4642
+ switch (_response.error.statusCode) {
4643
+ case 400:
4644
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4645
+ case 401:
4646
+ throw new UnauthorizedError(_response.error.body, _response.rawResponse);
4647
+ case 402:
4648
+ throw new PaymentRequiredError(_response.error.body, _response.rawResponse);
4649
+ case 403:
4650
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4651
+ case 404:
4652
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4653
+ case 409:
4654
+ throw new ConflictError(_response.error.body, _response.rawResponse);
4655
+ case 413:
4656
+ throw new ContentTooLargeError(_response.error.body, _response.rawResponse);
4657
+ case 422:
4658
+ throw new UnprocessableEntityError(_response.error.body, _response.rawResponse);
4659
+ case 429:
4660
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4661
+ case 503:
4662
+ throw new ServiceUnavailableError(_response.error.body, _response.rawResponse);
4663
+ default:
4664
+ throw new VectrosError({
4665
+ statusCode: _response.error.statusCode,
4666
+ body: _response.error.body,
4667
+ rawResponse: _response.rawResponse
4668
+ });
4669
+ }
4670
+ }
4671
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/runs");
4672
+ }
4673
+ };
4674
+ var AuthClient = class {
4675
+ constructor(options) {
4676
+ this._options = normalizeClientOptionsWithAuth(options);
4677
+ }
4678
+ /**
4679
+ * 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`.
4680
+ *
4681
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4682
+ *
4683
+ * @throws {@link Vectros.InternalServerError}
4684
+ *
4685
+ * @example
4686
+ * await client.auth.getJwks()
4687
+ */
4688
+ getJwks(requestOptions) {
4689
+ return HttpResponsePromise.fromPromise(this.__getJwks(requestOptions));
4690
+ }
4691
+ async __getJwks(requestOptions) {
4692
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4693
+ const _headers = mergeHeaders(
4694
+ _authRequest.headers,
4695
+ this._options?.headers,
4696
+ requestOptions?.headers
4697
+ );
4698
+ const _response = await fetcher({
4699
+ url: url_exports.join(
4700
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4701
+ "v1/auth/jwks"
4702
+ ),
4703
+ method: "GET",
4704
+ headers: _headers,
4705
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4706
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4707
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4708
+ abortSignal: requestOptions?.abortSignal,
4709
+ fetchFn: this._options?.fetch,
4710
+ logging: this._options.logging
4711
+ });
4712
+ if (_response.ok) {
4713
+ return { data: _response.body, rawResponse: _response.rawResponse };
4714
+ }
4715
+ if (_response.error.reason === "status-code") {
4716
+ switch (_response.error.statusCode) {
4717
+ case 500:
4718
+ throw new InternalServerError(_response.error.body, _response.rawResponse);
4719
+ default:
4720
+ throw new VectrosError({
4721
+ statusCode: _response.error.statusCode,
4722
+ body: _response.error.body,
4723
+ rawResponse: _response.rawResponse
4724
+ });
4725
+ }
4726
+ }
4727
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/auth/jwks");
4728
+ }
4729
+ /**
4730
+ * 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.
4731
+ *
4732
+ * @param {Vectros.GetAccessLogRequest} request
4733
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4734
+ *
4735
+ * @throws {@link Vectros.BadRequestError}
4736
+ * @throws {@link Vectros.ForbiddenError}
4737
+ *
4738
+ * @example
4739
+ * await client.auth.getAccessLog({
4740
+ * subjectType: "user",
4741
+ * subjectId: "user_abc123",
4742
+ * contextId: "ctx_intake",
4743
+ * action: "read",
4744
+ * callerKeyId: "key_abc123",
4745
+ * resourceType: "intake_form",
4746
+ * resourceId: "rec_456"
4747
+ * })
4748
+ */
4749
+ getAccessLog(request = {}, requestOptions) {
4750
+ return HttpResponsePromise.fromPromise(this.__getAccessLog(request, requestOptions));
4751
+ }
4752
+ async __getAccessLog(request = {}, requestOptions) {
4753
+ const {
4754
+ subjectType,
4755
+ subjectId,
4756
+ contextId,
4757
+ from: from_,
4758
+ to,
4759
+ action,
4760
+ callerKeyId,
4761
+ resourceType,
4762
+ resourceId,
4763
+ revealedSensitive,
4764
+ startFrom,
4765
+ limit
4766
+ } = request;
4767
+ const _queryParams = {
4768
+ subjectType,
4769
+ subjectId,
4770
+ contextId,
4771
+ from: from_,
4772
+ to,
4773
+ action,
4774
+ callerKeyId,
4775
+ resourceType,
4776
+ resourceId,
4777
+ revealedSensitive,
4778
+ startFrom,
4779
+ limit
4780
+ };
4781
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4782
+ const _headers = mergeHeaders(
4783
+ _authRequest.headers,
4784
+ this._options?.headers,
4785
+ requestOptions?.headers
4786
+ );
4787
+ const _response = await fetcher({
4788
+ url: url_exports.join(
4789
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4790
+ "v1/admin/access-log"
4791
+ ),
4792
+ method: "GET",
4793
+ headers: _headers,
4794
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
4795
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4796
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4797
+ abortSignal: requestOptions?.abortSignal,
4798
+ fetchFn: this._options?.fetch,
4799
+ logging: this._options.logging
4800
+ });
4801
+ if (_response.ok) {
4802
+ return { data: _response.body, rawResponse: _response.rawResponse };
4803
+ }
4804
+ if (_response.error.reason === "status-code") {
4805
+ switch (_response.error.statusCode) {
4806
+ case 400:
4807
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4808
+ case 403:
4809
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4810
+ default:
4811
+ throw new VectrosError({
4812
+ statusCode: _response.error.statusCode,
4813
+ body: _response.error.body,
4814
+ rawResponse: _response.rawResponse
4815
+ });
4816
+ }
4817
+ }
4818
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/access-log");
4819
+ }
4820
+ /**
4821
+ * 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.
4822
+ *
4823
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4824
+ *
4825
+ * @throws {@link Vectros.ForbiddenError}
4826
+ *
4827
+ * @example
4828
+ * await client.auth.listScopedKeys()
4829
+ */
4830
+ listScopedKeys(requestOptions) {
4831
+ return HttpResponsePromise.fromPromise(this.__listScopedKeys(requestOptions));
4832
+ }
4833
+ async __listScopedKeys(requestOptions) {
4834
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4835
+ const _headers = mergeHeaders(
4836
+ _authRequest.headers,
4837
+ this._options?.headers,
4838
+ requestOptions?.headers
4839
+ );
4840
+ const _response = await fetcher({
4841
+ url: url_exports.join(
4842
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4843
+ "v1/admin/keys/scoped"
4844
+ ),
4845
+ method: "GET",
4846
+ headers: _headers,
4847
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4848
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4849
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4850
+ abortSignal: requestOptions?.abortSignal,
4851
+ fetchFn: this._options?.fetch,
4852
+ logging: this._options.logging
4853
+ });
4854
+ if (_response.ok) {
4855
+ return { data: _response.body, rawResponse: _response.rawResponse };
4856
+ }
4857
+ if (_response.error.reason === "status-code") {
4858
+ switch (_response.error.statusCode) {
4859
+ case 403:
4860
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4861
+ default:
4862
+ throw new VectrosError({
4863
+ statusCode: _response.error.statusCode,
4864
+ body: _response.error.body,
4865
+ rawResponse: _response.rawResponse
4866
+ });
4867
+ }
4868
+ }
4869
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped");
4870
+ }
4871
+ /**
4872
+ * 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.
4873
+ *
4874
+ * @param {Vectros.CreateScopedKeyRequest} request
4875
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4876
+ *
4877
+ * @throws {@link Vectros.BadRequestError}
4878
+ * @throws {@link Vectros.ForbiddenError}
4879
+ * @throws {@link Vectros.NotFoundError}
4880
+ * @throws {@link Vectros.ConflictError}
4881
+ * @throws {@link Vectros.TooManyRequestsError}
4882
+ *
4883
+ * @example
4884
+ * await client.auth.createScopedKey({
4885
+ * keyName: "research-bot production",
4886
+ * tenantId: "ten_live_xxx",
4887
+ * contextId: "myapp",
4888
+ * userId: "alice-001"
4889
+ * })
4890
+ */
4891
+ createScopedKey(request, requestOptions) {
4892
+ return HttpResponsePromise.fromPromise(this.__createScopedKey(request, requestOptions));
4893
+ }
4894
+ async __createScopedKey(request, requestOptions) {
4895
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4896
+ const _headers = mergeHeaders(
4897
+ _authRequest.headers,
4898
+ this._options?.headers,
4899
+ requestOptions?.headers
4900
+ );
4901
+ const _response = await fetcher({
4902
+ url: url_exports.join(
4903
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4904
+ "v1/admin/keys/scoped"
4905
+ ),
4906
+ method: "POST",
4907
+ headers: _headers,
4908
+ contentType: "application/json",
4909
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4910
+ requestType: "json",
4911
+ body: request,
4912
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4913
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4914
+ abortSignal: requestOptions?.abortSignal,
4915
+ fetchFn: this._options?.fetch,
4916
+ logging: this._options.logging
4917
+ });
4918
+ if (_response.ok) {
4919
+ return { data: _response.body, rawResponse: _response.rawResponse };
4920
+ }
4921
+ if (_response.error.reason === "status-code") {
4922
+ switch (_response.error.statusCode) {
4923
+ case 400:
4924
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
4925
+ case 403:
4926
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4927
+ case 404:
4928
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4929
+ case 409:
4930
+ throw new ConflictError(_response.error.body, _response.rawResponse);
4931
+ case 429:
4932
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
4933
+ default:
4934
+ throw new VectrosError({
4935
+ statusCode: _response.error.statusCode,
4936
+ body: _response.error.body,
4937
+ rawResponse: _response.rawResponse
4938
+ });
4939
+ }
4940
+ }
4941
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/admin/keys/scoped");
4942
+ }
4943
+ /**
4944
+ * 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.
4945
+ *
4946
+ * @param {Vectros.GetScopedKeyRequest} request
4947
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
4948
+ *
4949
+ * @throws {@link Vectros.ForbiddenError}
4950
+ * @throws {@link Vectros.NotFoundError}
4951
+ *
4952
+ * @example
4953
+ * await client.auth.getScopedKey({
4954
+ * keyId: "keyId"
4955
+ * })
4956
+ */
4957
+ getScopedKey(request, requestOptions) {
4958
+ return HttpResponsePromise.fromPromise(this.__getScopedKey(request, requestOptions));
4959
+ }
4960
+ async __getScopedKey(request, requestOptions) {
4961
+ const { keyId } = request;
4962
+ const _authRequest = await this._options.authProvider.getAuthRequest();
4963
+ const _headers = mergeHeaders(
4964
+ _authRequest.headers,
4965
+ this._options?.headers,
4966
+ requestOptions?.headers
4967
+ );
4968
+ const _response = await fetcher({
4969
+ url: url_exports.join(
4970
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
4971
+ `v1/admin/keys/scoped/${url_exports.encodePathParam(keyId)}`
4972
+ ),
4973
+ method: "GET",
4974
+ headers: _headers,
4975
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
4976
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
4977
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
4978
+ abortSignal: requestOptions?.abortSignal,
4979
+ fetchFn: this._options?.fetch,
4980
+ logging: this._options.logging
4981
+ });
4982
+ if (_response.ok) {
4983
+ return { data: _response.body, rawResponse: _response.rawResponse };
4984
+ }
4985
+ if (_response.error.reason === "status-code") {
4986
+ switch (_response.error.statusCode) {
4987
+ case 403:
4988
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
4989
+ case 404:
4990
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
4991
+ default:
4992
+ throw new VectrosError({
4993
+ statusCode: _response.error.statusCode,
4994
+ body: _response.error.body,
4995
+ rawResponse: _response.rawResponse
4996
+ });
4997
+ }
4998
+ }
4999
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/keys/scoped/{keyId}");
5000
+ }
5001
+ /**
5002
+ * 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.
5003
+ *
5004
+ * @param {Vectros.RevokeScopedKeyRequest} request
5005
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
5006
+ *
5007
+ * @throws {@link Vectros.ForbiddenError}
5008
+ * @throws {@link Vectros.NotFoundError}
5009
+ * @throws {@link Vectros.TooManyRequestsError}
5010
+ *
5011
+ * @example
5012
+ * await client.auth.revokeScopedKey({
5013
+ * keyId: "keyId"
5014
+ * })
5015
+ */
5016
+ revokeScopedKey(request, requestOptions) {
5017
+ return HttpResponsePromise.fromPromise(this.__revokeScopedKey(request, requestOptions));
5018
+ }
5019
+ async __revokeScopedKey(request, requestOptions) {
5020
+ const { keyId } = request;
5021
+ const _authRequest = await this._options.authProvider.getAuthRequest();
5022
+ const _headers = mergeHeaders(
5023
+ _authRequest.headers,
5024
+ this._options?.headers,
5025
+ requestOptions?.headers
5026
+ );
5027
+ const _response = await fetcher({
5028
+ url: url_exports.join(
5029
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
5030
+ `v1/admin/keys/scoped/${url_exports.encodePathParam(keyId)}`
5031
+ ),
5032
+ method: "DELETE",
5033
+ headers: _headers,
5034
+ queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
5035
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5036
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5037
+ abortSignal: requestOptions?.abortSignal,
5038
+ fetchFn: this._options?.fetch,
5039
+ logging: this._options.logging
5040
+ });
5041
+ if (_response.ok) {
5042
+ return { data: void 0, rawResponse: _response.rawResponse };
5043
+ }
5044
+ if (_response.error.reason === "status-code") {
5045
+ switch (_response.error.statusCode) {
5046
+ case 403:
5047
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5048
+ case 404:
5049
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
5050
+ case 429:
5051
+ throw new TooManyRequestsError(_response.error.body, _response.rawResponse);
5052
+ default:
5053
+ throw new VectrosError({
5054
+ statusCode: _response.error.statusCode,
5055
+ body: _response.error.body,
5056
+ rawResponse: _response.rawResponse
5057
+ });
5058
+ }
5059
+ }
5060
+ return handleNonStatusCodeError(
5061
+ _response.error,
5062
+ _response.rawResponse,
5063
+ "DELETE",
5064
+ "/v1/admin/keys/scoped/{keyId}"
5065
+ );
5066
+ }
5067
+ /**
5068
+ * 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.
5069
+ *
5070
+ * @param {Vectros.GetAdminLogsRequest} request
5071
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
5072
+ *
5073
+ * @throws {@link Vectros.BadRequestError}
5074
+ * @throws {@link Vectros.ForbiddenError}
5075
+ *
5076
+ * @example
5077
+ * await client.auth.getAdminLogs({
5078
+ * startTime: "startTime",
5079
+ * resource: "documents",
5080
+ * method: "POST",
5081
+ * keyId: "key_abc123",
5082
+ * contextId: "ctx_intake"
5083
+ * })
5084
+ */
5085
+ getAdminLogs(request, requestOptions) {
5086
+ return HttpResponsePromise.fromPromise(this.__getAdminLogs(request, requestOptions));
5087
+ }
5088
+ async __getAdminLogs(request, requestOptions) {
5089
+ const { startTime, endTime, resource, method, keyId, contextId, errorsOnly, limit } = request;
5090
+ const _queryParams = {
5091
+ startTime,
5092
+ endTime,
5093
+ resource,
5094
+ method,
5095
+ keyId,
5096
+ contextId,
5097
+ errorsOnly,
5098
+ limit
5099
+ };
5100
+ const _authRequest = await this._options.authProvider.getAuthRequest();
5101
+ const _headers = mergeHeaders(
5102
+ _authRequest.headers,
5103
+ this._options?.headers,
5104
+ requestOptions?.headers
5105
+ );
5106
+ const _response = await fetcher({
5107
+ url: url_exports.join(
5108
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
5109
+ "v1/admin/logs"
5110
+ ),
5111
+ method: "GET",
5112
+ headers: _headers,
5113
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5114
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5115
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5116
+ abortSignal: requestOptions?.abortSignal,
5117
+ fetchFn: this._options?.fetch,
5118
+ logging: this._options.logging
5119
+ });
5120
+ if (_response.ok) {
5121
+ return { data: _response.body, rawResponse: _response.rawResponse };
5122
+ }
5123
+ if (_response.error.reason === "status-code") {
5124
+ switch (_response.error.statusCode) {
5125
+ case 400:
5126
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
5127
+ case 403:
5128
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5129
+ default:
5130
+ throw new VectrosError({
5131
+ statusCode: _response.error.statusCode,
5132
+ body: _response.error.body,
5133
+ rawResponse: _response.rawResponse
5134
+ });
5135
+ }
5136
+ }
5137
+ return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/admin/logs");
5138
+ }
5139
+ /**
5140
+ * 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.
5141
+ *
5142
+ * @param {Vectros.ListAccessProfilesRequest} request
5143
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
5144
+ *
5145
+ * @throws {@link Vectros.ForbiddenError}
5146
+ * @throws {@link Vectros.NotFoundError}
5147
+ *
5148
+ * @example
5149
+ * await client.auth.listAccessProfiles({
5150
+ * contextId: "myapp"
5151
+ * })
5152
+ */
5153
+ listAccessProfiles(request, requestOptions) {
5154
+ return HttpResponsePromise.fromPromise(this.__listAccessProfiles(request, requestOptions));
5155
+ }
5156
+ async __listAccessProfiles(request, requestOptions) {
5157
+ const { contextId, startFrom, limit } = request;
5158
+ const _queryParams = {
5159
+ startFrom,
5160
+ limit
5161
+ };
5162
+ const _authRequest = await this._options.authProvider.getAuthRequest();
5163
+ const _headers = mergeHeaders(
5164
+ _authRequest.headers,
5165
+ this._options?.headers,
5166
+ requestOptions?.headers
5167
+ );
5168
+ const _response = await fetcher({
5169
+ url: url_exports.join(
5170
+ await Supplier.get(this._options.baseUrl) ?? await Supplier.get(this._options.environment),
5171
+ `v1/app-contexts/${url_exports.encodePathParam(contextId)}/profiles`
5172
+ ),
5173
+ method: "GET",
5174
+ headers: _headers,
5175
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
5176
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
5177
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
5178
+ abortSignal: requestOptions?.abortSignal,
5179
+ fetchFn: this._options?.fetch,
5180
+ logging: this._options.logging
5181
+ });
5182
+ if (_response.ok) {
5183
+ return { data: _response.body, rawResponse: _response.rawResponse };
5184
+ }
5185
+ if (_response.error.reason === "status-code") {
5186
+ switch (_response.error.statusCode) {
5187
+ case 403:
5188
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
5189
+ case 404:
5190
+ throw new NotFoundError(_response.error.body, _response.rawResponse);
5191
+ default:
5192
+ throw new VectrosError({
5193
+ statusCode: _response.error.statusCode,
5194
+ body: _response.error.body,
5195
+ rawResponse: _response.rawResponse
5196
+ });
5197
+ }
5198
+ }
5199
+ return handleNonStatusCodeError(
5200
+ _response.error,
5201
+ _response.rawResponse,
5202
+ "GET",
5203
+ "/v1/app-contexts/{contextId}/profiles"
5204
+ );
5205
+ }
5206
+ /**
5207
+ * 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.
5208
+ *
5209
+ * @param {Vectros.CreateAccessProfileRequest} request
5210
+ * @param {AuthClient.RequestOptions} requestOptions - Request-specific configuration.
5211
+ *
5212
+ * @throws {@link Vectros.BadRequestError}
5213
+ * @throws {@link Vectros.ForbiddenError}
5214
+ * @throws {@link Vectros.NotFoundError}
5215
+ * @throws {@link Vectros.ConflictError}
5216
+ * @throws {@link Vectros.TooManyRequestsError}
5217
+ *
5218
+ * @example
4577
5219
  * await client.auth.createAccessProfile({
4578
5220
  * contextId: "contextId",
4579
- * body: {
4580
- * principalId: "usr_6ba7b810-9dad-11d1-80b4-00c04fd430c8"
4581
- * }
5221
+ * body: {}
4582
5222
  * })
4583
5223
  */
4584
5224
  createAccessProfile(request, requestOptions) {
@@ -4714,10 +5354,7 @@ var AuthClient = class {
4714
5354
  *
4715
5355
  * @example
4716
5356
  * await client.auth.createAppContext({
4717
- * body: {
4718
- * contextId: "myapp",
4719
- * name: "My Internal App"
4720
- * }
5357
+ * body: {}
4721
5358
  * })
4722
5359
  */
4723
5360
  createAppContext(request, requestOptions) {
@@ -4860,13 +5497,7 @@ var AuthClient = class {
4860
5497
  * @example
4861
5498
  * await client.auth.createRole({
4862
5499
  * contextId: "contextId",
4863
- * body: {
4864
- * roleId: "engineering-member",
4865
- * name: "Engineering Team Member",
4866
- * scopes: [{
4867
- * allowed_actions: ["records:cru", "search:r"]
4868
- * }]
4869
- * }
5500
+ * body: {}
4870
5501
  * })
4871
5502
  */
4872
5503
  createRole(request, requestOptions) {
@@ -5010,9 +5641,7 @@ var AuthClient = class {
5010
5641
  * await client.auth.updateAccessProfile({
5011
5642
  * contextId: "contextId",
5012
5643
  * principalId: "principalId",
5013
- * body: {
5014
- * principalId: "usr_6ba7b810-9dad-11d1-80b4-00c04fd430c8"
5015
- * }
5644
+ * body: {}
5016
5645
  * })
5017
5646
  */
5018
5647
  updateAccessProfile(request, requestOptions) {
@@ -5215,10 +5844,7 @@ var AuthClient = class {
5215
5844
  * @example
5216
5845
  * await client.auth.updateAppContext({
5217
5846
  * contextId: "contextId",
5218
- * body: {
5219
- * contextId: "myapp",
5220
- * name: "My Internal App"
5221
- * }
5847
+ * body: {}
5222
5848
  * })
5223
5849
  */
5224
5850
  updateAppContext(request, requestOptions) {
@@ -5423,13 +6049,7 @@ var AuthClient = class {
5423
6049
  * await client.auth.updateRole({
5424
6050
  * contextId: "contextId",
5425
6051
  * roleId: "roleId",
5426
- * body: {
5427
- * roleId: "engineering-member",
5428
- * name: "Engineering Team Member",
5429
- * scopes: [{
5430
- * allowed_actions: ["records:cru", "search:r"]
5431
- * }]
5432
- * }
6052
+ * body: {}
5433
6053
  * })
5434
6054
  */
5435
6055
  updateRole(request, requestOptions) {
@@ -6881,7 +7501,7 @@ var DocumentsClient = class {
6881
7501
  this._options = normalizeClientOptionsWithAuth(options);
6882
7502
  }
6883
7503
  /**
6884
- * 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.
7504
+ * 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.
6885
7505
  *
6886
7506
  * @param {Vectros.ListDocumentsRequest} request
6887
7507
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -6891,6 +7511,7 @@ var DocumentsClient = class {
6891
7511
  * userId: "550e8400-e29b-41d4-a716-446655440000",
6892
7512
  * scope: "group:eng-team",
6893
7513
  * folderId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
7514
+ * type: "investigation_report",
6894
7515
  * startFrom: "b3BhcXVlLWN1cnNvci1mcm9tLXRoZS1wcmV2aW91cy1wYWdl"
6895
7516
  * })
6896
7517
  */
@@ -6898,11 +7519,12 @@ var DocumentsClient = class {
6898
7519
  return HttpResponsePromise.fromPromise(this.__listDocuments(request, requestOptions));
6899
7520
  }
6900
7521
  async __listDocuments(request = {}, requestOptions) {
6901
- const { userId, scope, folderId, startFrom, limit } = request;
7522
+ const { userId, scope, folderId, type: type_, startFrom, limit } = request;
6902
7523
  const _queryParams = {
6903
7524
  userId,
6904
7525
  scope,
6905
7526
  folderId,
7527
+ type: type_,
6906
7528
  startFrom,
6907
7529
  limit
6908
7530
  };
@@ -6952,9 +7574,7 @@ var DocumentsClient = class {
6952
7574
  *
6953
7575
  * @example
6954
7576
  * await client.documents.ingestDocument({
6955
- * body: {
6956
- * title: "Patient Intake Form \u2014 Jane Doe"
6957
- * }
7577
+ * body: {}
6958
7578
  * })
6959
7579
  */
6960
7580
  ingestDocument(request, requestOptions) {
@@ -7084,9 +7704,7 @@ var DocumentsClient = class {
7084
7704
  * @example
7085
7705
  * await client.documents.updateDocument({
7086
7706
  * id: "id",
7087
- * body: {
7088
- * title: "Patient Intake Form \u2014 Jane Doe"
7089
- * }
7707
+ * body: {}
7090
7708
  * })
7091
7709
  */
7092
7710
  updateDocument(request, requestOptions) {
@@ -7484,11 +8102,12 @@ var DocumentsClient = class {
7484
8102
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/documents/{id}/download");
7485
8103
  }
7486
8104
  /**
7487
- * 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.
8105
+ * 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.
7488
8106
  *
7489
8107
  * @param {Vectros.GetDocumentTextRequest} request
7490
8108
  * @param {DocumentsClient.RequestOptions} requestOptions - Request-specific configuration.
7491
8109
  *
8110
+ * @throws {@link Vectros.BadRequestError}
7492
8111
  * @throws {@link Vectros.NotFoundError}
7493
8112
  *
7494
8113
  * @example
@@ -7500,7 +8119,11 @@ var DocumentsClient = class {
7500
8119
  return HttpResponsePromise.fromPromise(this.__getDocumentText(request, requestOptions));
7501
8120
  }
7502
8121
  async __getDocumentText(request, requestOptions) {
7503
- const { id } = request;
8122
+ const { id, offset, limit } = request;
8123
+ const _queryParams = {
8124
+ offset,
8125
+ limit
8126
+ };
7504
8127
  const _authRequest = await this._options.authProvider.getAuthRequest();
7505
8128
  const _headers = mergeHeaders(
7506
8129
  _authRequest.headers,
@@ -7514,7 +8137,7 @@ var DocumentsClient = class {
7514
8137
  ),
7515
8138
  method: "GET",
7516
8139
  headers: _headers,
7517
- queryString: url_exports.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
8140
+ queryString: url_exports.queryBuilder().addMany(_queryParams).mergeAdditional(requestOptions?.queryParams).build(),
7518
8141
  timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1e3,
7519
8142
  maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
7520
8143
  abortSignal: requestOptions?.abortSignal,
@@ -7526,6 +8149,8 @@ var DocumentsClient = class {
7526
8149
  }
7527
8150
  if (_response.error.reason === "status-code") {
7528
8151
  switch (_response.error.statusCode) {
8152
+ case 400:
8153
+ throw new BadRequestError(_response.error.body, _response.rawResponse);
7529
8154
  case 404:
7530
8155
  throw new NotFoundError(_response.error.body, _response.rawResponse);
7531
8156
  default:
@@ -7745,9 +8370,7 @@ var FoldersClient = class {
7745
8370
  *
7746
8371
  * @example
7747
8372
  * await client.folders.createFolder({
7748
- * body: {
7749
- * name: "Patient Records 2024"
7750
- * }
8373
+ * body: {}
7751
8374
  * })
7752
8375
  */
7753
8376
  createFolder(request, requestOptions) {
@@ -7875,9 +8498,7 @@ var FoldersClient = class {
7875
8498
  * @example
7876
8499
  * await client.folders.updateFolder({
7877
8500
  * id: "id",
7878
- * body: {
7879
- * name: "Patient Records 2024"
7880
- * }
8501
+ * body: {}
7881
8502
  * })
7882
8503
  */
7883
8504
  updateFolder(request, requestOptions) {
@@ -8010,9 +8631,7 @@ var FoldersClient = class {
8010
8631
  * @example
8011
8632
  * await client.folders.patchFolder({
8012
8633
  * id: "id",
8013
- * body: {
8014
- * name: "Patient Records 2024"
8015
- * }
8634
+ * body: {}
8016
8635
  * })
8017
8636
  */
8018
8637
  patchFolder(request, requestOptions) {
@@ -8227,9 +8846,7 @@ var IdentityClient = class {
8227
8846
  * await client.identity.createEntity({
8228
8847
  * namespace: "team",
8229
8848
  * contextId: "myapp",
8230
- * body: {
8231
- * externalId: "team_eng_platform"
8232
- * }
8849
+ * body: {}
8233
8850
  * })
8234
8851
  */
8235
8852
  createEntity(request, requestOptions) {
@@ -8365,9 +8982,7 @@ var IdentityClient = class {
8365
8982
  * namespace: "team",
8366
8983
  * id: "id",
8367
8984
  * contextId: "myapp",
8368
- * body: {
8369
- * externalId: "team_eng_platform"
8370
- * }
8985
+ * body: {}
8371
8986
  * })
8372
8987
  */
8373
8988
  updateEntity(request, requestOptions) {
@@ -8707,10 +9322,7 @@ var IdentityClient = class {
8707
9322
  * @example
8708
9323
  * await client.identity.updateNamespace({
8709
9324
  * namespace: "team",
8710
- * body: {
8711
- * namespace: "team",
8712
- * specificityRank: 1500
8713
- * }
9325
+ * body: {}
8714
9326
  * })
8715
9327
  */
8716
9328
  updateNamespace(request, requestOptions) {
@@ -8897,10 +9509,7 @@ var IdentityClient = class {
8897
9509
  *
8898
9510
  * @example
8899
9511
  * await client.identity.registerNamespace({
8900
- * body: {
8901
- * namespace: "team",
8902
- * specificityRank: 1500
8903
- * }
9512
+ * body: {}
8904
9513
  * })
8905
9514
  */
8906
9515
  registerNamespace(request, requestOptions) {
@@ -9033,9 +9642,7 @@ var IdentityClient = class {
9033
9642
  *
9034
9643
  * @example
9035
9644
  * await client.identity.createUser({
9036
- * body: {
9037
- * externalId: "usr_12345"
9038
- * }
9645
+ * body: {}
9039
9646
  * })
9040
9647
  */
9041
9648
  createUser(request, requestOptions) {
@@ -9154,6 +9761,7 @@ var IdentityClient = class {
9154
9761
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
9155
9762
  *
9156
9763
  * @throws {@link Vectros.BadRequestError}
9764
+ * @throws {@link Vectros.ForbiddenError}
9157
9765
  * @throws {@link Vectros.NotFoundError}
9158
9766
  * @throws {@link Vectros.ConflictError}
9159
9767
  * @throws {@link Vectros.TooManyRequestsError}
@@ -9161,9 +9769,7 @@ var IdentityClient = class {
9161
9769
  * @example
9162
9770
  * await client.identity.updateUser({
9163
9771
  * id: "550e8400-e29b-41d4-a716-446655440000",
9164
- * body: {
9165
- * externalId: "usr_12345"
9166
- * }
9772
+ * body: {}
9167
9773
  * })
9168
9774
  */
9169
9775
  updateUser(request, requestOptions) {
@@ -9201,6 +9807,8 @@ var IdentityClient = class {
9201
9807
  switch (_response.error.statusCode) {
9202
9808
  case 400:
9203
9809
  throw new BadRequestError(_response.error.body, _response.rawResponse);
9810
+ case 403:
9811
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
9204
9812
  case 404:
9205
9813
  throw new NotFoundError(_response.error.body, _response.rawResponse);
9206
9814
  case 409:
@@ -9223,6 +9831,7 @@ var IdentityClient = class {
9223
9831
  * @param {Vectros.DeleteUserRequest} request
9224
9832
  * @param {IdentityClient.RequestOptions} requestOptions - Request-specific configuration.
9225
9833
  *
9834
+ * @throws {@link Vectros.ForbiddenError}
9226
9835
  * @throws {@link Vectros.NotFoundError}
9227
9836
  * @throws {@link Vectros.ConflictError}
9228
9837
  * @throws {@link Vectros.TooManyRequestsError}
@@ -9262,6 +9871,8 @@ var IdentityClient = class {
9262
9871
  }
9263
9872
  if (_response.error.reason === "status-code") {
9264
9873
  switch (_response.error.statusCode) {
9874
+ case 403:
9875
+ throw new ForbiddenError(_response.error.body, _response.rawResponse);
9265
9876
  case 404:
9266
9877
  throw new NotFoundError(_response.error.body, _response.rawResponse);
9267
9878
  case 409:
@@ -9466,7 +10077,7 @@ var InferenceClient = class {
9466
10077
  this._options = normalizeClientOptionsWithAuth(options);
9467
10078
  }
9468
10079
  /**
9469
- * 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`.
10080
+ * 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`.
9470
10081
  *
9471
10082
  * @param {InferenceClient.RequestOptions} requestOptions - Request-specific configuration.
9472
10083
  *
@@ -9510,7 +10121,7 @@ var InferenceClient = class {
9510
10121
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "GET", "/v1/models");
9511
10122
  }
9512
10123
  /**
9513
- * 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.
10124
+ * 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.
9514
10125
  */
9515
10126
  chatInference(request, requestOptions) {
9516
10127
  return HttpResponsePromise.fromPromise(this.__chatInference(request, requestOptions));
@@ -9582,7 +10193,7 @@ var InferenceClient = class {
9582
10193
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "POST", "/v1/chat");
9583
10194
  }
9584
10195
  /**
9585
- * 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.
10196
+ * 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.
9586
10197
  */
9587
10198
  documentAsk(request, requestOptions) {
9588
10199
  return HttpResponsePromise.fromPromise(this.__documentAsk(request, requestOptions));
@@ -10717,8 +11328,7 @@ var SchemasClient = class {
10717
11328
  * }],
10718
11329
  * capabilities: {
10719
11330
  * "auditHistory": true
10720
- * },
10721
- * allowedSurfaces: ["record"]
11331
+ * }
10722
11332
  * }
10723
11333
  * })
10724
11334
  */
@@ -10848,11 +11458,7 @@ var SchemasClient = class {
10848
11458
  * @example
10849
11459
  * await client.schemas.updateSchema({
10850
11460
  * id: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
10851
- * body: {
10852
- * typeName: "intake_form",
10853
- * displayName: "Client Intake Form",
10854
- * allowedSurfaces: ["record"]
10855
- * }
11461
+ * body: {}
10856
11462
  * })
10857
11463
  */
10858
11464
  updateSchema(request, requestOptions) {
@@ -11392,7 +11998,7 @@ var ScriptsClient = class {
11392
11998
  return handleNonStatusCodeError(_response.error, _response.rawResponse, "PUT", "/v1/scripts/{id}");
11393
11999
  }
11394
12000
  /**
11395
- * 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.
12001
+ * 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.
11396
12002
  *
11397
12003
  * @param {Vectros.DeleteScriptRequest} request
11398
12004
  * @param {ScriptsClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -11656,17 +12262,7 @@ var TriggersClient = class {
11656
12262
  *
11657
12263
  * @example
11658
12264
  * await client.triggers.createTrigger({
11659
- * body: {
11660
- * name: "on-intake-create",
11661
- * firingSource: {
11662
- * schemaId: "sch_9f8e7d6c",
11663
- * event: "CREATE"
11664
- * },
11665
- * scriptRef: {
11666
- * name: "notify-assignee",
11667
- * version: "3"
11668
- * }
11669
- * }
12265
+ * body: {}
11670
12266
  * })
11671
12267
  */
11672
12268
  createTrigger(request, requestOptions) {
@@ -11795,17 +12391,7 @@ var TriggersClient = class {
11795
12391
  * @example
11796
12392
  * await client.triggers.updateTrigger({
11797
12393
  * id: "id",
11798
- * body: {
11799
- * name: "on-intake-create",
11800
- * firingSource: {
11801
- * schemaId: "sch_9f8e7d6c",
11802
- * event: "CREATE"
11803
- * },
11804
- * scriptRef: {
11805
- * name: "notify-assignee",
11806
- * version: "3"
11807
- * }
11808
- * }
12394
+ * body: {}
11809
12395
  * })
11810
12396
  */
11811
12397
  updateTrigger(request, requestOptions) {
@@ -11930,6 +12516,9 @@ var VectrosClient = class {
11930
12516
  get auth() {
11931
12517
  return this._auth ?? (this._auth = new AuthClient(this._options));
11932
12518
  }
12519
+ get agents() {
12520
+ return this._agents ?? (this._agents = new AgentsClient(this._options));
12521
+ }
11933
12522
  get documents() {
11934
12523
  return this._documents ?? (this._documents = new DocumentsClient(this._options));
11935
12524
  }
@@ -11997,7 +12586,11 @@ var logging;
11997
12586
  import { z } from "zod";
11998
12587
  import { z as z2 } from "zod";
11999
12588
  var CONTEXT_ID_RE = /^[a-z][a-z0-9-]{2,30}$/;
12000
- 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;
12589
+ var FORGERY_CHARS_RE = (
12590
+ // eslint-disable-next-line no-control-regex -- matching control characters is what this pattern is for
12591
+ /[\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
12592
+ );
12593
+ var FORGERY_CHARS_GLOBAL_RE = new RegExp(FORGERY_CHARS_RE.source, "gu");
12001
12594
  function safeDisplayString(base) {
12002
12595
  return base.refine((v) => !FORGERY_CHARS_RE.test(v), {
12003
12596
  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"
@@ -12164,7 +12757,8 @@ var BlueprintLookupFieldSchema = z.union([
12164
12757
  // or a declared field on this schema. Locked at create. Valid on a
12165
12758
  // composite lookup too — it orders WITHIN a group when a query supplies
12166
12759
  // fewer values than the lookup declares, not across the whole result.
12167
- sortBy: z.string().min(1).optional(),
12760
+ // Printed by the CLI's lint findings, so held to the displayed-text rule like the other names here.
12761
+ sortBy: safeDisplayString(z.string().min(1)).optional(),
12168
12762
  // Opt a field past the fixed fast-index budget into a higher-cost
12169
12763
  // secondary index. No effect on a field that fits within the budget.
12170
12764
  allowOverflow: z.boolean().optional()
@@ -12381,7 +12975,7 @@ var BlueprintAccessProfileSchema = z.object({
12381
12975
  // this file, and so a future preview enhancement that starts showing these values (a very
12382
12976
  // plausible one, given the existing renderPlanDataScope precedent) doesn't silently reopen
12383
12977
  // this exact class on a field that looks, byte-for-byte, like every other one already closed.
12384
- dataScope: z.record(z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
12978
+ dataScope: z.record(z.string(), z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
12385
12979
  // Optional identity overrides for the service principal's profile — scope
12386
12980
  // values its key STAMPS onto everything it writes (a credential can only
12387
12981
  // stamp ownership its identity carries). Keys are the namespaced `scope:<ns>`
@@ -12394,7 +12988,7 @@ var BlueprintAccessProfileSchema = z.object({
12394
12988
  // below (`lintIdentityOverrides`, placeholder-aware — see that function's
12395
12989
  // docstring); this `.min(1)` is only the shape-level "non-blank" guard on
12396
12990
  // the value, not the grammar check.
12397
- identityOverrides: z.record(z.string().min(1)).optional(),
12991
+ identityOverrides: z.record(z.string(), z.string().min(1)).optional(),
12398
12992
  // Optional capabilities granted to the blueprint's own service-principal
12399
12993
  // profile. DECISION: allowed here, not role-clause-only. On the platform,
12400
12994
  // an access profile's scopes are themselves a list of clauses — the exact
@@ -12458,7 +13052,7 @@ var BlueprintAccessProfileSchema = z.object({
12458
13052
  });
12459
13053
  var BlueprintRoleClauseSchema = z.object({
12460
13054
  allowedActions: z.array(safeDisplayString(z.string().min(1))).min(1),
12461
- dataScope: z.record(z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
13055
+ dataScope: z.record(z.string(), z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
12462
13056
  dataScopeRef: z.string().min(1).optional(),
12463
13057
  capabilities: GrantedCapabilitiesSchema,
12464
13058
  assignableRoles: AssignableRolesSchema
@@ -12472,11 +13066,12 @@ var BlueprintRoleClauseSchema = z.object({
12472
13066
  }
12473
13067
  });
12474
13068
  var BlueprintFragmentsSchema = z.record(
12475
- z.record(z.array(z.union([safeDisplayString(z.string().min(1)), z.null()])))
13069
+ z.string(),
13070
+ z.record(z.string(), z.array(z.union([safeDisplayString(z.string().min(1)), z.null()])))
12476
13071
  );
12477
13072
  var BlueprintRolesSchema = z.record(safeDisplayString(z.string().min(1)), z.array(BlueprintRoleClauseSchema).min(1));
12478
- var BlueprintRoleAssumableSchema = z.record(z.array(safeDisplayString(z.string().min(1))).min(1));
12479
- var BlueprintRoleAssumableMapSchema = z.record(BlueprintRoleAssumableSchema);
13073
+ var BlueprintRoleAssumableSchema = z.record(z.string(), z.array(safeDisplayString(z.string().min(1))).min(1));
13074
+ var BlueprintRoleAssumableMapSchema = z.record(z.string(), BlueprintRoleAssumableSchema);
12480
13075
  var BlueprintTriggerFiringSourceSchema = z.object({
12481
13076
  schemaName: z.string().min(1),
12482
13077
  event: z.enum(["CREATE", "UPDATE", "DELETE"])
@@ -12510,7 +13105,7 @@ var BlueprintTriggerEntrySchema = z.object({
12510
13105
  scriptRef: BlueprintTriggerScriptRefSchema,
12511
13106
  allowedActions: z.array(safeDisplayString(z.string().min(1))).min(1).optional(),
12512
13107
  roleIds: z.array(z.string().min(1)).min(1).optional(),
12513
- dataScope: z.record(z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
13108
+ dataScope: z.record(z.string(), z.array(z.union([safeDisplayString(z.string().min(1)), z.null()]))).optional(),
12514
13109
  capabilities: GrantedCapabilitiesSchema,
12515
13110
  manifest: BlueprintTriggerManifestSchema,
12516
13111
  /**
@@ -12590,7 +13185,113 @@ var BlueprintScriptEntrySchema = z.object({
12590
13185
  message: `'declaredInputContract' exceeds the platform's ${SCRIPT_INPUT_CONTRACT_MAX_BYTES.toLocaleString("en-US")}-byte cap (measured in UTF-8 bytes, not characters)`
12591
13186
  }).optional()
12592
13187
  }).strict();
12593
- var BlueprintScriptsSchema = z.record(BlueprintScriptEntrySchema);
13188
+ var BlueprintScriptsSchema = z.record(z.string(), BlueprintScriptEntrySchema);
13189
+ var BLUEPRINT_FORMAT_VERSION = 2;
13190
+ var AGENT_LIMITS = {
13191
+ descriptionChars: 500,
13192
+ modelChars: 100,
13193
+ systemChars: 32e3,
13194
+ tools: 50,
13195
+ maxSteps: 25,
13196
+ minTokensPerCall: 16,
13197
+ maxTokensPerCall: 8192,
13198
+ maxToolCalls: 50,
13199
+ maxWrites: 1e3,
13200
+ maxInferenceCents: 500
13201
+ };
13202
+ var AGENT_TOOL_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
13203
+ var BlueprintAgentToolSchema = z.object({
13204
+ /** The tool's name in the platform's tool catalog. Checked against the catalog when the blueprint is applied. */
13205
+ name: z.string().regex(AGENT_TOOL_NAME_RE, {
13206
+ message: "a tool name is 1-64 characters of letters, digits, underscores ('_') and hyphens ('-')"
13207
+ }),
13208
+ /** Required `true` for a tool that creates, changes or deletes data. */
13209
+ allowWrites: z.boolean().optional()
13210
+ }).strict();
13211
+ var BlueprintAgentEntrySchema = z.object({
13212
+ description: z.string().max(AGENT_LIMITS.descriptionChars).optional(),
13213
+ /**
13214
+ * Whose identity the agent's tool calls run with. `CALLER` (the caller who starts the run) is the only value the platform accepts
13215
+ * today. Checked here for its SHAPE only, never against a list: the set will grow, and the platform refuses a value it does not
13216
+ * support when the blueprint is applied, so an older copy of this package never refuses a value a newer platform accepts.
13217
+ */
13218
+ authority: z.string().regex(/^[A-Z][A-Z_]{0,31}$/, { message: "authority is an upper-case name such as 'CALLER'" }).optional(),
13219
+ /** The model the agent runs on, by its id in the model catalog. */
13220
+ // Shown in the plan preview, so held to the same rule as every other displayed text.
13221
+ model: safeDisplayString(z.string().min(1).max(AGENT_LIMITS.modelChars)),
13222
+ /** The system prompt. */
13223
+ system: z.string().max(AGENT_LIMITS.systemChars).optional(),
13224
+ /** The tool whitelist. */
13225
+ tools: z.array(BlueprintAgentToolSchema).max(AGENT_LIMITS.tools).optional(),
13226
+ limits: z.object({
13227
+ maxSteps: z.number().int().min(1).max(AGENT_LIMITS.maxSteps).optional(),
13228
+ maxTokensPerCall: z.number().int().min(AGENT_LIMITS.minTokensPerCall).max(AGENT_LIMITS.maxTokensPerCall).optional(),
13229
+ maxToolCalls: z.number().int().min(0).max(AGENT_LIMITS.maxToolCalls).optional(),
13230
+ maxWrites: z.number().int().min(0).max(AGENT_LIMITS.maxWrites).optional()
13231
+ }).strict().optional(),
13232
+ budget: z.object({
13233
+ /** US cents per run, up to 500. The platform also enforces a minimum and refuses a lower value when the blueprint is applied. */
13234
+ maxInferenceCents: z.number().int().min(1).max(AGENT_LIMITS.maxInferenceCents).optional()
13235
+ }).strict().optional()
13236
+ }).strict();
13237
+ var BlueprintAgentsSchema = z.record(safeDisplayString(z.string()), BlueprintAgentEntrySchema);
13238
+ function assertSupportedFormatVersion(input) {
13239
+ if (input === null || typeof input !== "object") return;
13240
+ const declared = input.formatVersion;
13241
+ if (typeof declared === "number" && Number.isInteger(declared) && declared > BLUEPRINT_FORMAT_VERSION) {
13242
+ throw new BlueprintValidationError(
13243
+ `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.`,
13244
+ [{ path: "formatVersion", message: `unsupported format version ${declared}` }]
13245
+ );
13246
+ }
13247
+ }
13248
+ function lintAgents(value, ctx) {
13249
+ const bp = value;
13250
+ const agents = bp.agents;
13251
+ if (!agents) return;
13252
+ if ((bp.formatVersion ?? 1) < 2) {
13253
+ ctx.addIssue({
13254
+ code: z.ZodIssueCode.custom,
13255
+ path: ["formatVersion"],
13256
+ message: "an 'agents' section needs 'formatVersion: 2' (so a tool that reads an older format refuses the file instead of misreading it)"
13257
+ });
13258
+ }
13259
+ for (const [name, agent] of Object.entries(agents)) {
13260
+ if (!SCRIPT_NAME_RE.test(name)) {
13261
+ ctx.addIssue({
13262
+ code: z.ZodIssueCode.custom,
13263
+ path: ["agents", name],
13264
+ 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.`
13265
+ });
13266
+ }
13267
+ const tools = agent?.tools ?? [];
13268
+ const seen = /* @__PURE__ */ new Set();
13269
+ tools.forEach((tool, i) => {
13270
+ if (seen.has(tool.name)) {
13271
+ ctx.addIssue({
13272
+ code: z.ZodIssueCode.custom,
13273
+ path: ["agents", name, "tools", i, "name"],
13274
+ message: `tool '${displaySafe(tool.name, 80)}' is listed twice`
13275
+ });
13276
+ }
13277
+ seen.add(tool.name);
13278
+ });
13279
+ if (tools.some((t) => t.allowWrites === true) && (agent?.limits?.maxWrites ?? 0) < 1) {
13280
+ ctx.addIssue({
13281
+ code: z.ZodIssueCode.custom,
13282
+ path: ["agents", name, "limits", "maxWrites"],
13283
+ message: "limits.maxWrites must be at least 1 when a tool has allowWrites: true"
13284
+ });
13285
+ }
13286
+ if (tools.length > 0 && agent?.limits?.maxToolCalls === 0) {
13287
+ ctx.addIssue({
13288
+ code: z.ZodIssueCode.custom,
13289
+ path: ["agents", name, "limits", "maxToolCalls"],
13290
+ message: "limits.maxToolCalls must be at least 1 when tools are listed"
13291
+ });
13292
+ }
13293
+ }
13294
+ }
12594
13295
  var IDENTITY_NAMESPACE_RE = /^[a-z][a-z0-9_-]{1,31}$/;
12595
13296
  var FORBIDDEN_IDENTITY_NAMESPACES = /* @__PURE__ */ new Set(["record", "document", "entity", "user"]);
12596
13297
  var IdentityKindSchema = z.string().min(1).superRefine((val, ctx) => {
@@ -12618,7 +13319,7 @@ var IdentityDeclSchema = z.object({
12618
13319
  // sibling fields) — it does reach a platform API payload as an entity's `name`.
12619
13320
  externalId: safeDisplayString(z.string().min(1)),
12620
13321
  displayName: safeDisplayString(z.string().min(1)).optional(),
12621
- metadata: z.record(z.unknown()).optional()
13322
+ metadata: z.record(z.string(), z.unknown()).optional()
12622
13323
  }).strict();
12623
13324
  var IDENTITY_NAME_RE = /^[A-Za-z_]\w*$/;
12624
13325
  var IdentitiesDeclSchema = z.record(
@@ -12764,8 +13465,7 @@ var BlueprintIssuerSchema = z.object({
12764
13465
  */
12765
13466
  capturedClaims: z.array(
12766
13467
  // `safeDisplayString` (a `.refine`) then the blank-name `.refine` — both ordered AFTER
12767
- // `.max`, since `.refine` returns a ZodEffects, which has no `.max` (`.refine` itself
12768
- // chains fine onto a ZodEffects — it's on the ZodType base, not ZodString-specific).
13468
+ // `.max`, by the same last-call convention.
12769
13469
  // `.min(1)` would be a LENGTH check and would admit a whitespace-only name while the
12770
13470
  // message claims "non-blank"; the platform rejects on isBlank(). Note the value is
12771
13471
  // REJECTED, not trimmed — it reaches the wire exactly as authored, matching how the
@@ -12978,7 +13678,7 @@ var RecordSeedSchema = z.object({
12978
13678
  surface: z.literal("record"),
12979
13679
  ...SeedCommonShape,
12980
13680
  /** The record payload, validated against the bound schema. */
12981
- fields: z.record(z.unknown())
13681
+ fields: z.record(z.string(), z.unknown())
12982
13682
  }).strict();
12983
13683
  var DocumentSeedSchema = z.object({
12984
13684
  surface: z.literal("document"),
@@ -12988,7 +13688,7 @@ var DocumentSeedSchema = z.object({
12988
13688
  /** Raw text content to ingest + index — REQUIRED and non-empty (the platform rejects a blank ingest). */
12989
13689
  text: z.string().min(1),
12990
13690
  /** Optional structured payload bound to the schema (the document's `fields`). */
12991
- fields: z.record(z.unknown()).optional()
13691
+ fields: z.record(z.string(), z.unknown()).optional()
12992
13692
  }).strict();
12993
13693
  var BlueprintSeedRecordSchema = z.discriminatedUnion("surface", [
12994
13694
  RecordSeedSchema,
@@ -13613,6 +14313,8 @@ var BLUEPRINT_FIELD_PHASES = Object.freeze({
13613
14313
  // principal/AccessProfile pairs instead of one), so it applies in the identical in-context phase,
13614
14314
  // under the same re-minted context-confined token.
13615
14315
  triggers: "in-context",
14316
+ // Written with the same in-context, context-confined token as scripts and triggers.
14317
+ agents: "in-context",
13616
14318
  // Setting it requires the platform provisioning capability, which only the bootstrap credential
13617
14319
  // carries — the same reasoning as issuers/namespaces above. It is applied to the app context
13618
14320
  // itself, which the bootstrap phase already creates, so there is nothing for the in-context pass
@@ -13739,7 +14441,7 @@ var BlueprintSchema = z.object({
13739
14441
  // safeDisplayString: rendered raw into buildPlan's context step (`identity projection:
13740
14442
  // [...]`) — same rendered-plan-preview shape as `capturedClaims` (its structural pair);
13741
14443
  // the same ordering rule applies (safeDisplayString's own `.refine` before the blank-name `.refine` below,
13742
- // both after `.max` since `.refine` returns a ZodEffects with no `.max`).
14444
+ // both after `.max`, by the last-call convention).
13743
14445
  safeDisplayString(
13744
14446
  z.string().max(MAX_CAPTURED_CLAIM_NAME_LENGTH, {
13745
14447
  message: `an identityProjectionClaims entry must be at most ${MAX_CAPTURED_CLAIM_NAME_LENGTH} characters`
@@ -13770,7 +14472,16 @@ var BlueprintSchema = z.object({
13770
14472
  * composes) are live authority, not a declaration awaiting a future release. Author the grant as
13771
14473
  * narrowly as you would any other credential's.
13772
14474
  */
13773
- triggers: BlueprintTriggersSchema.optional()
14475
+ triggers: BlueprintTriggersSchema.optional(),
14476
+ /**
14477
+ * The blueprint format version; absent means 1. A blueprint with an `agents` section declares 2.
14478
+ */
14479
+ formatVersion: z.number().int().min(1).max(BLUEPRINT_FORMAT_VERSION).optional(),
14480
+ /**
14481
+ * Optional stored agents: agentName -> definition. A run of an agent calls tools with the identity
14482
+ * and permissions of the caller who starts the run, further limited by the agent's tool whitelist.
14483
+ */
14484
+ agents: BlueprintAgentsSchema.optional()
13774
14485
  }).strict().superRefine((bp, ctx) => {
13775
14486
  lintSelfTokenPlacement(bp, ctx);
13776
14487
  lintIdentityRefs(bp, ctx);
@@ -13789,17 +14500,46 @@ var BlueprintSchema = z.object({
13789
14500
  lintTriggers(bp, ctx);
13790
14501
  lintTriggerFields(bp, ctx);
13791
14502
  lintScripts(bp, ctx);
14503
+ lintAgents(bp, ctx);
13792
14504
  });
14505
+ function displaySafe(text, maxLength = Number.POSITIVE_INFINITY) {
14506
+ const shown = Array.from(text.replace(FORGERY_CHARS_GLOBAL_RE, "?"));
14507
+ return shown.length > maxLength ? `${shown.slice(0, maxLength).join("")}...` : shown.join("");
14508
+ }
14509
+ function displaySafeInline(text, maxLength = Number.POSITIVE_INFINITY) {
14510
+ return displaySafe(text.replace(/\r\n|[\r\n\u0085\u2028\u2029]/g, " "), maxLength);
14511
+ }
14512
+ function displaySafeLines(text) {
14513
+ return text.replace(/\r\n/g, "\n").split("\n").map((line) => displaySafe(line)).join("\n");
14514
+ }
14515
+ function neutralizeIssues(issues) {
14516
+ return issues.map((i) => ({ path: displaySafe(i.path), message: displaySafe(i.message) }));
14517
+ }
14518
+ function quotedKeysSafe(issue) {
14519
+ return displaySafe(issue.message, issue.code === "unrecognized_keys" ? 300 : Number.POSITIVE_INFINITY);
14520
+ }
13793
14521
  function formatIssuePath(path4) {
13794
14522
  let out = "";
13795
14523
  for (const seg of path4) {
13796
14524
  if (typeof seg === "number") out += `[${seg}]`;
13797
- else out += out.length ? `.${seg}` : seg;
14525
+ else out += out.length ? `.${displaySafe(String(seg))}` : displaySafe(String(seg));
13798
14526
  }
13799
14527
  return out.length ? out : "(root)";
13800
14528
  }
14529
+ function flattenZodIssues(error) {
14530
+ const out = [];
14531
+ for (const i of error.issues) {
14532
+ const nested = i.code === "invalid_key" ? i.issues : void 0;
14533
+ if (nested && nested.length > 0) {
14534
+ for (const n of nested) out.push({ path: [...i.path, ...n.path], message: quotedKeysSafe(n) });
14535
+ } else {
14536
+ out.push({ path: [...i.path], message: quotedKeysSafe(i) });
14537
+ }
14538
+ }
14539
+ return out;
14540
+ }
13801
14541
  function toBlueprintIssues(error) {
13802
- return error.issues.map((i) => ({ path: formatIssuePath(i.path), message: i.message }));
14542
+ return flattenZodIssues(error).map((i) => ({ path: formatIssuePath(i.path), message: i.message }));
13803
14543
  }
13804
14544
  function renderIssues(issues) {
13805
14545
  return issues.map((i) => ` \u2022 ${i.path}: ${i.message}`).join("\n");
@@ -13812,12 +14552,13 @@ var BlueprintValidationError = class extends Error {
13812
14552
  */
13813
14553
  issues;
13814
14554
  constructor(message, issues = []) {
13815
- super(message);
14555
+ super(displaySafeLines(message));
13816
14556
  this.name = "BlueprintValidationError";
13817
- this.issues = issues;
14557
+ this.issues = neutralizeIssues(issues);
13818
14558
  }
13819
14559
  };
13820
14560
  function parseBlueprint(input) {
14561
+ assertSupportedFormatVersion(input);
13821
14562
  const result = BlueprintSchema.safeParse(input);
13822
14563
  if (!result.success) {
13823
14564
  const issues = toBlueprintIssues(result.error);
@@ -13833,9 +14574,9 @@ var IDENTITY_TOKEN_RE = /\$\{\{\s*identities\.([A-Za-z_]\w*)\s*\}\}/g;
13833
14574
  var BlueprintIdentityError = class extends Error {
13834
14575
  issues;
13835
14576
  constructor(message, issues = []) {
13836
- super(message);
14577
+ super(displaySafeLines(message));
13837
14578
  this.name = "BlueprintIdentityError";
13838
- this.issues = issues;
14579
+ this.issues = neutralizeIssues(issues);
13839
14580
  }
13840
14581
  };
13841
14582
  function collectIdentityReferences(value) {
@@ -13873,8 +14614,8 @@ async function resolveBlueprintIdentities(raw, resolve) {
13873
14614
  if (!parsed.success) {
13874
14615
  throw new BlueprintIdentityError(
13875
14616
  "Blueprint identities block is invalid",
13876
- parsed.error.issues.map((i) => ({
13877
- path: i.path.length ? `identities.${i.path.join(".")}` : "identities",
14617
+ flattenZodIssues(parsed.error).map((i) => ({
14618
+ path: i.path.length ? `identities.${i.path.map(String).join(".")}` : "identities",
13878
14619
  message: i.message
13879
14620
  }))
13880
14621
  );
@@ -13883,7 +14624,7 @@ async function resolveBlueprintIdentities(raw, resolve) {
13883
14624
  const undeclared = refs.filter((name) => !(name in declared));
13884
14625
  if (undeclared.length) {
13885
14626
  throw new BlueprintIdentityError(
13886
- `Blueprint references undeclared identities: ${undeclared.join(", ")}`,
14627
+ `Blueprint references undeclared identities: ${undeclared.map((n) => displaySafe(n, 80)).join(", ")}`,
13887
14628
  undeclared.map((name) => ({
13888
14629
  path: `identities.${name}`,
13889
14630
  message: `'\${{ identities.${name} }}' is referenced but not declared in the 'identities' block`
@@ -13900,8 +14641,11 @@ async function resolveBlueprintIdentities(raw, resolve) {
13900
14641
  idMap[name] = id;
13901
14642
  } catch (err) {
13902
14643
  throw new BlueprintIdentityError(
13903
- `Failed to resolve identity '${name}' (${decl.kind} externalId=${decl.externalId}): ${err instanceof Error ? err.message : String(err)}`,
13904
- [{ path: `identities.${name}`, message: err instanceof Error ? err.message : String(err) }]
14644
+ `Failed to resolve identity '${displaySafe(name, 80)}' (${decl.kind} externalId=${displaySafe(decl.externalId, 80)}): ${displaySafeInline(
14645
+ err instanceof Error ? err.message : String(err),
14646
+ 500
14647
+ )}`,
14648
+ [{ path: `identities.${name}`, message: displaySafeInline(err instanceof Error ? err.message : String(err), 500) }]
13905
14649
  );
13906
14650
  }
13907
14651
  }
@@ -13914,13 +14658,13 @@ var InputDeclSchema = z2.object({
13914
14658
  default: z2.union([z2.string(), z2.number(), z2.boolean()]).optional(),
13915
14659
  description: z2.string().optional()
13916
14660
  }).strict();
13917
- var InputsDeclSchema = z2.record(InputDeclSchema);
14661
+ var InputsDeclSchema = z2.record(z2.string(), InputDeclSchema);
13918
14662
  var BlueprintInputError = class extends Error {
13919
14663
  issues;
13920
14664
  constructor(message, issues = []) {
13921
- super(message);
14665
+ super(displaySafeLines(message));
13922
14666
  this.name = "BlueprintInputError";
13923
- this.issues = issues;
14667
+ this.issues = neutralizeIssues(issues);
13924
14668
  }
13925
14669
  };
13926
14670
  function renderIssues2(issues) {
@@ -14112,7 +14856,7 @@ function resolveBlueprintInputs(raw, supplied = {}) {
14112
14856
  const parsed = InputsDeclSchema.safeParse(rawInputs);
14113
14857
  if (!parsed.success) {
14114
14858
  for (const i of parsed.error.issues) {
14115
- const p = i.path.length ? `inputs.${i.path.join(".")}` : "inputs";
14859
+ const p = i.path.length ? `inputs.${i.path.map(String).join(".")}` : "inputs";
14116
14860
  issues.push({ path: p, message: i.message });
14117
14861
  }
14118
14862
  } else {
@@ -14149,8 +14893,9 @@ function resolveBlueprintInputs(raw, supplied = {}) {
14149
14893
  const ctx = { declared, values, builtins, issues };
14150
14894
  const substituted = substituteValue(body, ctx, "");
14151
14895
  if (issues.length) {
14896
+ const clean = neutralizeIssues(issues);
14152
14897
  throw new BlueprintInputError(`Blueprint variable resolution failed:
14153
- ${renderIssues2(issues)}`, issues);
14898
+ ${renderIssues2(clean)}`, clean);
14154
14899
  }
14155
14900
  return substituted;
14156
14901
  }
@@ -15755,6 +16500,48 @@ function createLogger2(ctx = {}) {
15755
16500
  );
15756
16501
  }
15757
16502
 
16503
+ // src/terminal-text.ts
16504
+ var STRIPPED = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
16505
+ function stripControlCharacters(value, maxLength = 200) {
16506
+ const characters = Array.from(value.replace(STRIPPED, ""));
16507
+ return characters.length > maxLength ? `${characters.slice(0, maxLength).join("")}...` : characters.join("");
16508
+ }
16509
+ function singleLine(value, maxLength = 200) {
16510
+ return stripControlCharacters(value.replace(/\r\n|[\r\n\u0085\u2028\u2029\t\v\f]/g, " "), maxLength);
16511
+ }
16512
+ function cleanBlock(value, maxLineLength = 2e3, limits = {}) {
16513
+ const maxLines = limits.maxLines ?? 200;
16514
+ const maxChars = limits.maxChars ?? 2e4;
16515
+ const lines = value.split(/\r\n|[\r\n\u0085\u2028\u2029]/);
16516
+ let cut = lines.length > maxLines;
16517
+ let text = lines.slice(0, maxLines).map((line) => stripControlCharacters(line.replace(/\t/g, " "), maxLineLength)).join("\n");
16518
+ if (text.length > maxChars) {
16519
+ text = text.slice(0, maxChars);
16520
+ cut = true;
16521
+ }
16522
+ return cut ? `${text}
16523
+ [truncated]` : text;
16524
+ }
16525
+ function shortPath(value, maxLength = 300) {
16526
+ const clean = Array.from(singleLine(value, Number.MAX_SAFE_INTEGER));
16527
+ if (clean.length <= maxLength) return clean.join("");
16528
+ const head = Math.floor((maxLength - 3) / 3);
16529
+ return `${clean.slice(0, head).join("")}...${clean.slice(clean.length - (maxLength - 3 - head)).join("")}`;
16530
+ }
16531
+ function errorText(err) {
16532
+ return cleanBlock(err instanceof Error ? err.message : String(err));
16533
+ }
16534
+ function quotedBlock(value, indent = " ") {
16535
+ return cleanBlock(value).split("\n").map((line, i) => i === 0 || line === "" ? line : `${indent}${line}`).join("\n");
16536
+ }
16537
+ function asText(value, fallback = "(unprintable)") {
16538
+ try {
16539
+ return String(value);
16540
+ } catch {
16541
+ return fallback;
16542
+ }
16543
+ }
16544
+
15758
16545
  // src/output.ts
15759
16546
  function formatJson(value) {
15760
16547
  return JSON.stringify(value, null, 2);
@@ -15796,19 +16583,19 @@ function extractServerError(err) {
15796
16583
  if (e.body && typeof e.body === "object") {
15797
16584
  const b = e.body;
15798
16585
  if (typeof b.message === "string" && b.message.trim()) serverMessage = b.message.trim();
15799
- if (typeof b.requestId === "string" && b.requestId.trim()) requestId = b.requestId.trim();
15800
- if (typeof b.errorCode === "string" && b.errorCode.trim()) errorCode = b.errorCode.trim();
16586
+ if (typeof b.requestId === "string" && b.requestId.trim()) requestId = singleLine(b.requestId.trim(), 100);
16587
+ if (typeof b.errorCode === "string" && b.errorCode.trim()) errorCode = singleLine(b.errorCode.trim(), 100);
15801
16588
  } else if (typeof e.body === "string" && e.body.trim()) {
15802
16589
  serverMessage = e.body.trim();
15803
16590
  }
15804
- if (serverMessage) return { statusCode, message: serverMessage, requestId, errorCode };
16591
+ if (serverMessage) return { statusCode, message: cleanBlock(serverMessage), requestId, errorCode };
15805
16592
  const message = typeof e.message === "string" ? e.message : "";
15806
16593
  const isSdkEnvelope = /\nStatus code:/.test(message);
15807
- if (message && !isSdkEnvelope) return { statusCode, message, requestId, errorCode };
16594
+ if (message && !isSdkEnvelope) return { statusCode, message: cleanBlock(message), requestId, errorCode };
15808
16595
  if (statusCode) return { statusCode, message: `HTTP ${statusCode}`, requestId, errorCode };
15809
- if (message) return { statusCode, message, requestId, errorCode };
16596
+ if (message) return { statusCode, message: cleanBlock(message), requestId, errorCode };
15810
16597
  }
15811
- return { message: String(err) };
16598
+ return { message: cleanBlock(String(err)) };
15812
16599
  }
15813
16600
  function formatError(err) {
15814
16601
  return extractServerError(err).message;
@@ -16026,6 +16813,33 @@ var PROVISION_OPTIONS = [
16026
16813
  ]
16027
16814
  }
16028
16815
  },
16816
+ {
16817
+ flag: "--allow-agent-adoption",
16818
+ stacked: true,
16819
+ modes: ["bootstrap", "apply"],
16820
+ help: {
16821
+ bootstrap: [
16822
+ "Required to write a blueprint's agent over an agent that already exists",
16823
+ "in this context but carries no blueprint mark (made by hand, or by a tool",
16824
+ "that set none) \u2014 without it, bootstrap refuses for that name, naming it,",
16825
+ "and writes nothing to the agent. Adopting marks the agent as this",
16826
+ "blueprint's and writes the declared definition as its next version if it",
16827
+ "differs, so every run using its latest version changes. Never needed for a",
16828
+ "brand-new agent, or one this blueprint already manages. Does NOT help if",
16829
+ "the agent is managed by a DIFFERENT blueprint \u2014 that is always refused."
16830
+ ],
16831
+ apply: [
16832
+ "Required to write a blueprint's agent over an agent that already exists",
16833
+ "in this context but carries no blueprint mark (made by hand, or by a tool",
16834
+ "that set none) \u2014 without it, apply refuses for that name, naming it,",
16835
+ "and writes nothing to the agent. Adopting marks the agent as this",
16836
+ "blueprint's and writes the declared definition as its next version if it",
16837
+ "differs, so every run using its latest version changes. Never needed for a",
16838
+ "brand-new agent, or one this blueprint already manages. Does NOT help if",
16839
+ "the agent is managed by a DIFFERENT blueprint \u2014 that is always refused."
16840
+ ]
16841
+ }
16842
+ },
16029
16843
  {
16030
16844
  flag: "--tenant",
16031
16845
  value: "test|live",
@@ -16167,16 +16981,24 @@ var PROVISION_OPTIONS = [
16167
16981
  "\u2014 e.g. a deliberate second machine for an integration you already own.",
16168
16982
  "Refused by default so an unexpected collision halts a CI run loudly",
16169
16983
  "instead of silently minting a credential bound to the wrong identity.",
16170
- "Never needed for --rotate or an ordinary same-machine re-apply."
16984
+ "Not needed for --rotate or an ordinary same-machine re-apply of the",
16985
+ "blueprint's own service principal (a trigger principal is the exception, below).",
16986
+ "Also required the same way when a trigger's service principal already exists",
16987
+ "and this blueprint did not mark it (made by an earlier release, or by hand).",
16988
+ "The first confirmation marks it as this blueprint's (needs users:u); then it is not asked again."
16171
16989
  ],
16172
16990
  apply: [
16173
16991
  "Required (with --yes, or with no TTY) when the blueprint's service principal",
16174
16992
  "already exists in your tenant. Apply never reads a local key record, so that is",
16175
16993
  "every apply once the principal exists, on any machine, including one that ran",
16176
- "bootstrap. It lets apply rewrite that principal's access profile (the",
16177
- "blueprint's own service principal only). In a pipeline this flag is STANDING",
16178
- "consent: it also waves through a blueprint whose servicePrincipal.externalId",
16179
- "collides with an unrelated identity, so keep the blueprint's source reviewed.",
16994
+ "bootstrap. It lets apply rewrite that principal's access profile. It also",
16995
+ "covers a trigger's service principal that already exists and that this",
16996
+ "blueprint did not mark (made by an earlier release, or by hand): the first",
16997
+ "confirmation marks it as this blueprint's (needs users:u; apply says if it",
16998
+ "could not), and it is not asked again. In a",
16999
+ "pipeline this flag is STANDING consent: it also waves through a blueprint whose",
17000
+ "servicePrincipal.externalId or trigger name collides with an unrelated identity,",
17001
+ "so keep the blueprint's source reviewed.",
16180
17002
  "Every run that proceeds prints which principal it reused and why."
16181
17003
  ]
16182
17004
  }
@@ -16492,13 +17314,6 @@ import { mkdir, readFile, writeFile, rm } from "fs/promises";
16492
17314
  import { homedir } from "os";
16493
17315
  import { join as join2 } from "path";
16494
17316
 
16495
- // src/terminal-text.ts
16496
- var STRIPPED = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
16497
- function stripControlCharacters(value, maxLength = 200) {
16498
- const characters = Array.from(value.replace(STRIPPED, ""));
16499
- return characters.length > maxLength ? `${characters.slice(0, maxLength).join("")}...` : characters.join("");
16500
- }
16501
-
16502
17317
  // src/pkce.ts
16503
17318
  import { createHash, randomBytes, timingSafeEqual } from "crypto";
16504
17319
 
@@ -16799,10 +17614,10 @@ function parseDocument(raw, filePath) {
16799
17614
  const ext = extname(filePath).toLowerCase();
16800
17615
  if (ext === ".yaml" || ext === ".yml") {
16801
17616
  try {
16802
- return parseYaml(raw);
17617
+ return parseYaml(raw, { logLevel: "error" });
16803
17618
  } catch (err) {
16804
17619
  if (err instanceof YAMLParseError) {
16805
- throw new Error(`Blueprint YAML is invalid in '${filePath}': ${err.message}`);
17620
+ throw new Error(`Blueprint YAML is invalid in '${shortPath(filePath, 300)}': ${quotedBlock(err.message)}`);
16806
17621
  }
16807
17622
  throw err;
16808
17623
  }
@@ -16811,7 +17626,7 @@ function parseDocument(raw, filePath) {
16811
17626
  return JSON.parse(raw);
16812
17627
  } catch (err) {
16813
17628
  throw new BlueprintValidationError(
16814
- `Blueprint is not valid JSON in '${filePath}': ${err instanceof Error ? err.message : String(err)}`
17629
+ `Blueprint is not valid JSON in '${shortPath(filePath, 300)}': ${quotedBlock(err instanceof Error ? err.message : String(err))}`
16815
17630
  );
16816
17631
  }
16817
17632
  }
@@ -16821,7 +17636,7 @@ async function loadBlueprintFile(filePath, supplied = {}) {
16821
17636
  raw = await readFile2(filePath, "utf8");
16822
17637
  } catch (err) {
16823
17638
  const msg = err instanceof Error ? err.message : String(err);
16824
- throw new Error(`Cannot read blueprint file '${filePath}': ${msg}`);
17639
+ throw new Error(`Cannot read blueprint file '${shortPath(filePath, 300)}': ${quotedBlock(msg)}`);
16825
17640
  }
16826
17641
  const parsed = parseDocument(raw, filePath);
16827
17642
  const resolved = resolveBlueprintInputs(parsed, supplied);
@@ -16843,11 +17658,11 @@ async function loadValuesFile(filePath) {
16843
17658
  try {
16844
17659
  raw = await readFile2(filePath, "utf8");
16845
17660
  } catch (err) {
16846
- throw new Error(`Cannot read --values file '${filePath}': ${err instanceof Error ? err.message : String(err)}`);
17661
+ throw new Error(`Cannot read --values file '${shortPath(filePath, 300)}': ${quotedBlock(err instanceof Error ? err.message : String(err))}`);
16847
17662
  }
16848
17663
  const parsed = parseDocument(raw, filePath);
16849
17664
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
16850
- throw new Error(`--values file '${filePath}' must be a map of name: value pairs.`);
17665
+ throw new Error(`--values file '${shortPath(filePath, 300)}' must be a map of name: value pairs.`);
16851
17666
  }
16852
17667
  return parsed;
16853
17668
  }
@@ -16866,7 +17681,8 @@ var DATA_PLANE_ALLOWLIST = [
16866
17681
  "documents",
16867
17682
  "folders",
16868
17683
  "inference",
16869
- "entities"
17684
+ "entities",
17685
+ "runs"
16870
17686
  ];
16871
17687
  var KNOWN_CONTROL_PLANE_RESOURCES = [
16872
17688
  "keys",
@@ -16908,7 +17724,10 @@ var KNOWN_CONTROL_PLANE_RESOURCES = [
16908
17724
  // no admitted resource has an `x` verb — adding it would instead make `records:x` pass the op
16909
17725
  // check and be admitted as data-plane.
16910
17726
  "scripts",
16911
- "triggers"
17727
+ "triggers",
17728
+ // Stored agent definitions: authoring one steers every run of it, so the platform treats it as
17729
+ // control-plane on every verb. Listed for MESSAGE QUALITY, as above.
17730
+ "agents"
16912
17731
  ];
16913
17732
  var PARTIALLY_ADMITTED_OPS = /* @__PURE__ */ new Map([
16914
17733
  ["profiles", /* @__PURE__ */ new Set(["r", "u", "d"])]
@@ -17146,6 +17965,9 @@ function assertBlueprintGates(blueprint) {
17146
17965
  }
17147
17966
  }
17148
17967
  for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
17968
+ const carried = entry.allowedActions ?? (entry.roleIds ?? []).flatMap((id) => (blueprint.roles?.[id] ?? []).flatMap((clause) => clause.allowedActions));
17969
+ const refused = triggerRefusedGrants(carried);
17970
+ if (refused.length > 0) throw withTriggerContext(new ScopeGateError(refused), triggerName);
17149
17971
  if (entry.allowedActions === void 0) continue;
17150
17972
  try {
17151
17973
  assertScopeGate(entry.allowedActions, entry.capabilities);
@@ -17184,6 +18006,20 @@ function assertScriptGrantsNameADeclaredScript(blueprint) {
17184
18006
  }
17185
18007
  if (rejected.length > 0) throw new ScopeGateError(rejected);
17186
18008
  }
18009
+ var TRIGGER_REFUSED_RESOURCES = /* @__PURE__ */ new Set(["runs"]);
18010
+ function triggerRefusedGrants(actions) {
18011
+ const out = [];
18012
+ for (const action of actions) {
18013
+ const resource = action.trim().split(":")[0];
18014
+ if (TRIGGER_REFUSED_RESOURCES.has(resource)) {
18015
+ out.push({
18016
+ action,
18017
+ 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`
18018
+ });
18019
+ }
18020
+ }
18021
+ return out;
18022
+ }
17187
18023
  function withTriggerContext(err, triggerName) {
17188
18024
  if (err instanceof ScopeGateError || err instanceof CapabilityGateError) {
17189
18025
  for (const r of err.rejected) r.source ??= `trigger '${triggerName}'`;
@@ -17481,6 +18317,45 @@ async function drainPages(fetch2) {
17481
18317
  return out;
17482
18318
  }
17483
18319
 
18320
+ // src/agent-api.ts
18321
+ var AGENT_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
18322
+ var APPLY_AGENT_SCOPES = "'agents:c', plus 'agents:u' and 'agents:r' when the agent already exists (changed or not)";
18323
+ function agentErrorHint(err, contextId, scopes) {
18324
+ const info = extractServerError(err);
18325
+ if (info.statusCode === 403) {
18326
+ 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.`;
18327
+ }
18328
+ if (info.statusCode === 400 && /already in use/i.test(info.message)) {
18329
+ 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'.";
18330
+ }
18331
+ if (info.errorCode === "AGENT_RETIRED" || info.statusCode === 409 && /\b(retired|purged)\b/i.test(info.message)) {
18332
+ 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.";
18333
+ }
18334
+ return "";
18335
+ }
18336
+ function agentRequestError(err, subject, contextId, scopes) {
18337
+ const info = extractServerError(err);
18338
+ const hint = agentErrorHint(err, contextId, scopes);
18339
+ const message = singleLine(info.message, 500);
18340
+ if (info.statusCode === 403) {
18341
+ return new Error(`${subject}: the platform refused the request (403). ${hint} Platform message: ${message}`, { cause: err });
18342
+ }
18343
+ return new Error(`${subject}: ${message}${hint ? ` ${hint}` : ""}`, { cause: err });
18344
+ }
18345
+ function sanitizedAgentError(err) {
18346
+ const info = extractServerError(err);
18347
+ const message = singleLine(info.message, 500);
18348
+ return Object.assign(new Error(message), {
18349
+ statusCode: info.statusCode,
18350
+ body: {
18351
+ message,
18352
+ requestId: info.requestId === void 0 ? void 0 : singleLine(info.requestId, 100),
18353
+ errorCode: info.errorCode === void 0 ? void 0 : singleLine(info.errorCode, 100)
18354
+ },
18355
+ cause: err
18356
+ });
18357
+ }
18358
+
17484
18359
  // src/shell-quote.ts
17485
18360
  function shellQuote(value) {
17486
18361
  if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) return value;
@@ -17494,6 +18369,19 @@ function actionOf(created) {
17494
18369
  function triggerPrincipalExternalId(blueprintName, triggerName) {
17495
18370
  return `${blueprintName}-trigger-${triggerName}`;
17496
18371
  }
18372
+ function triggerPrincipalProvenance(payload, blueprintName) {
18373
+ const marker = payload?.provisionedBy;
18374
+ if (marker === blueprintName) return { kind: "ours" };
18375
+ if (typeof marker === "string" && marker.length > 0) return { kind: "other-blueprint", markedBy: marker };
18376
+ return { kind: "unmarked" };
18377
+ }
18378
+ var SENSITIVE_MASK = "[redacted]";
18379
+ function payloadIsMasked(value) {
18380
+ if (value === SENSITIVE_MASK) return true;
18381
+ if (Array.isArray(value)) return value.some(payloadIsMasked);
18382
+ if (value !== null && typeof value === "object") return Object.values(value).some(payloadIsMasked);
18383
+ return false;
18384
+ }
17497
18385
  var SCRIPT_CONFLICT_RETRY_DELAY_MS = 750;
17498
18386
  function isVersionConflict(err) {
17499
18387
  return typeof err === "object" && err !== null && err.statusCode === 409;
@@ -17547,7 +18435,7 @@ function scriptMatches(stored, declared) {
17547
18435
  var ScriptOwnershipConflictError = class extends Error {
17548
18436
  constructor(name, owner, blueprintName) {
17549
18437
  super(
17550
- `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.`
18438
+ `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.`
17551
18439
  );
17552
18440
  this.name = "ScriptOwnershipConflictError";
17553
18441
  }
@@ -17660,6 +18548,86 @@ async function ensureScripts(args) {
17660
18548
  }
17661
18549
  return actions;
17662
18550
  }
18551
+ var AgentAdoptionNotAllowedError = class extends Error {
18552
+ constructor(agentName) {
18553
+ super(
18554
+ `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.)`
18555
+ );
18556
+ this.agentName = agentName;
18557
+ this.name = "AgentAdoptionNotAllowedError";
18558
+ }
18559
+ agentName;
18560
+ };
18561
+ var AgentOwnershipConflictError = class extends Error {
18562
+ constructor(agentName, markedBy, blueprintName) {
18563
+ super(
18564
+ `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.`
18565
+ );
18566
+ this.agentName = agentName;
18567
+ this.markedBy = markedBy;
18568
+ this.name = "AgentOwnershipConflictError";
18569
+ }
18570
+ agentName;
18571
+ markedBy;
18572
+ };
18573
+ function isDefiniteRefusal(err) {
18574
+ const status = err?.statusCode;
18575
+ return typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429;
18576
+ }
18577
+ async function ensureAgents(args) {
18578
+ const { ctxClient, agents, blueprintName, contextId, allowAgentAdoption, onAgentCreated, onAgentSeen, onAgentAttempt, onAgentRefused, log } = args;
18579
+ const actions = [];
18580
+ const trackedByCaller = onAgentAttempt !== void 0;
18581
+ const write = async (name, upsert) => {
18582
+ try {
18583
+ return await ctxClient.agents.createAgent(
18584
+ {
18585
+ contextId,
18586
+ ...upsert ? { upsert: true } : {},
18587
+ body: { ...agents[name], name, provisionedBy: blueprintName }
18588
+ },
18589
+ // The caller is keeping track of the agents it creates so that it can remove them. A create the client retried by
18590
+ // itself after a gateway error would come back as an agent that already existed, hiding that this call made it.
18591
+ trackedByCaller ? { maxRetries: 0 } : void 0
18592
+ );
18593
+ } catch (err) {
18594
+ if (isDefiniteRefusal(err)) onAgentRefused?.(name);
18595
+ throw agentRequestError(err, `Agent '${name}'`, contextId, APPLY_AGENT_SCOPES);
18596
+ }
18597
+ };
18598
+ const versionOf = (name, answer) => {
18599
+ const version = answer.version ?? answer.latestVersion;
18600
+ if (!answer.id || version === void 0) {
18601
+ throw new Error(`Agent '${name}': the platform's answer named no agent id or version.`);
18602
+ }
18603
+ return version;
18604
+ };
18605
+ for (const name of Object.keys(agents)) {
18606
+ if (onAgentAttempt?.(name) === false) break;
18607
+ const first = await write(name, false);
18608
+ if (!first.created) onAgentSeen?.({ name, id: first.id });
18609
+ const before = versionOf(name, first);
18610
+ if (first.created) {
18611
+ onAgentCreated?.({ name, id: first.id });
18612
+ actions.push({ name, action: "created", id: first.id, version: before });
18613
+ log.debug({ component: "loader", agentName: name, agentVersion: before }, "agent created at version 1");
18614
+ continue;
18615
+ }
18616
+ const owner = first.provisionedBy?.trim() || void 0;
18617
+ if (owner !== void 0 && owner !== blueprintName) throw new AgentOwnershipConflictError(name, owner, blueprintName);
18618
+ const adopting = owner === void 0;
18619
+ if (adopting && !allowAgentAdoption) throw new AgentAdoptionNotAllowedError(name);
18620
+ const second = await write(name, true);
18621
+ const after = versionOf(name, second);
18622
+ const action = adopting ? "adopted" : after > before ? "versioned" : "unchanged";
18623
+ actions.push({ name, action, id: first.id, version: after, previousVersion: before });
18624
+ log.debug(
18625
+ { component: "loader", agentName: name, agentVersionBefore: before, agentVersionAfter: after, action },
18626
+ adopting ? "agent adopted into this blueprint" : action === "versioned" ? "agent definition changed \u2014 next version written" : "agent already current"
18627
+ );
18628
+ }
18629
+ return actions;
18630
+ }
17663
18631
  async function pruneUndeclaredTriggers(args) {
17664
18632
  const { ctxClient, blueprintName, contextId, declaredNames, log } = args;
17665
18633
  const pruned = [];
@@ -17857,6 +18825,26 @@ var ExistingPrincipalNotConfirmedError = class extends Error {
17857
18825
  externalId;
17858
18826
  mintsKey;
17859
18827
  };
18828
+ var ExistingTriggerPrincipalNotConfirmedError = class extends Error {
18829
+ constructor(contextId, triggerName, userId, externalId, provenance, markedBy) {
18830
+ super(
18831
+ `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.`
18832
+ );
18833
+ this.contextId = contextId;
18834
+ this.triggerName = triggerName;
18835
+ this.userId = userId;
18836
+ this.externalId = externalId;
18837
+ this.provenance = provenance;
18838
+ this.markedBy = markedBy;
18839
+ this.name = "ExistingTriggerPrincipalNotConfirmedError";
18840
+ }
18841
+ contextId;
18842
+ triggerName;
18843
+ userId;
18844
+ externalId;
18845
+ provenance;
18846
+ markedBy;
18847
+ };
17860
18848
  function mapEnumValues(enumValues) {
17861
18849
  if (!enumValues) return void 0;
17862
18850
  return enumValues.map((value) => ({ value }));
@@ -17946,6 +18934,9 @@ var UnresolvedIdentitiesError = class extends Error {
17946
18934
  this.name = "UnresolvedIdentitiesError";
17947
18935
  }
17948
18936
  };
18937
+ function declaresAgents(blueprint) {
18938
+ return Object.keys(blueprint.agents ?? {}).length > 0;
18939
+ }
17949
18940
  function assertNoUnresolvedIdentities(blueprint) {
17950
18941
  const hasBlock = blueprint.identities && Object.keys(blueprint.identities).length > 0;
17951
18942
  const refs = collectIdentityReferences(blueprint);
@@ -18080,6 +19071,11 @@ async function runLoader(client, input) {
18080
19071
  readExistingKey,
18081
19072
  persistMintedKey,
18082
19073
  allowScriptAdoption = false,
19074
+ allowAgentAdoption = false,
19075
+ onAgentCreated,
19076
+ onAgentSeen,
19077
+ onAgentAttempt,
19078
+ onAgentRefused,
18083
19079
  log
18084
19080
  } = input;
18085
19081
  if (skipKey && rotate) {
@@ -18088,9 +19084,66 @@ async function runLoader(client, input) {
18088
19084
  assertNoUnresolvedIdentities(blueprint);
18089
19085
  const { contextAction, servicePrincipalUserId: userId, userAction, namespaceActions } = provisioned;
18090
19086
  assertBlueprintGates(blueprint);
19087
+ const triggerPrincipalIds = /* @__PURE__ */ new Map();
19088
+ const triggerPrincipalsToMark = /* @__PURE__ */ new Map();
19089
+ const pendingConfirmation = [];
19090
+ for (const triggerName of Object.keys(blueprint.triggers ?? {})) {
19091
+ const externalId = triggerPrincipalExternalId(blueprint.name, triggerName);
19092
+ const displayName = `Trigger: ${triggerName}`;
19093
+ const triggerUser = await client.identity.createUser({
19094
+ body: { externalId, type: "SERVICE", payload: { displayName, provisionedBy: blueprint.name } }
19095
+ });
19096
+ const triggerUserId = triggerUser.id;
19097
+ if (!triggerUserId) {
19098
+ throw new Error(`Trigger '${triggerName}' service-principal create returned no user id.`);
19099
+ }
19100
+ triggerPrincipalIds.set(triggerName, triggerUserId);
19101
+ if (triggerUser.created) continue;
19102
+ const provenance = triggerPrincipalProvenance(triggerUser.payload, blueprint.name);
19103
+ if (provenance.kind === "ours") continue;
19104
+ pendingConfirmation.push({
19105
+ triggerName,
19106
+ externalId,
19107
+ userId: triggerUserId,
19108
+ userType: triggerUser.type,
19109
+ provenance: provenance.kind,
19110
+ markedBy: provenance.kind === "other-blueprint" ? provenance.markedBy : void 0
19111
+ });
19112
+ }
19113
+ let askedTheOperator = false;
19114
+ for (const p of pendingConfirmation) {
19115
+ let proceed = true;
19116
+ if (input.confirmExistingTriggerPrincipal) {
19117
+ askedTheOperator = true;
19118
+ proceed = await input.confirmExistingTriggerPrincipal({
19119
+ contextId: blueprint.contextId,
19120
+ triggerName: p.triggerName,
19121
+ userId: p.userId,
19122
+ externalId: p.externalId,
19123
+ provenance: p.provenance,
19124
+ markedBy: p.markedBy,
19125
+ userType: p.userType
19126
+ });
19127
+ }
19128
+ if (!proceed) {
19129
+ throw new ExistingTriggerPrincipalNotConfirmedError(
19130
+ blueprint.contextId,
19131
+ p.triggerName,
19132
+ p.userId,
19133
+ p.externalId,
19134
+ p.provenance,
19135
+ p.markedBy
19136
+ );
19137
+ }
19138
+ if (input.confirmExistingTriggerPrincipal) {
19139
+ triggerPrincipalsToMark.set(p.triggerName, { externalId: p.externalId, userId: p.userId, userType: p.userType });
19140
+ }
19141
+ }
19142
+ const triggerPrincipalsReused = [];
18091
19143
  const needsContextPinnedBootstrapForIssuers = (blueprint.issuers ?? []).length > 0;
18092
- const ctxBootstrapClient = provisioned.ctxBootstrapClient ?? (needsContextPinnedBootstrapForIssuers ? input.remintBootstrapForContext ? await input.remintBootstrapForContext(blueprint.contextId) : client : void 0);
18093
- if (ctxBootstrapClient && !provisioned.ctxBootstrapClient && input.remintBootstrapForContext) {
19144
+ const staleAfterPrompt = askedTheOperator && needsContextPinnedBootstrapForIssuers && !!input.remintBootstrapForContext;
19145
+ const ctxBootstrapClient = (staleAfterPrompt ? await input.remintBootstrapForContext(blueprint.contextId) : void 0) ?? provisioned.ctxBootstrapClient ?? (needsContextPinnedBootstrapForIssuers ? input.remintBootstrapForContext ? await input.remintBootstrapForContext(blueprint.contextId) : client : void 0);
19146
+ if (ctxBootstrapClient && (staleAfterPrompt || !provisioned.ctxBootstrapClient) && input.remintBootstrapForContext) {
18094
19147
  log.debug({ component: "loader", contextId: blueprint.contextId }, "re-minted context-pinned bootstrap client (issuers)");
18095
19148
  }
18096
19149
  const issuerActions = [];
@@ -18272,16 +19325,9 @@ async function runLoader(client, input) {
18272
19325
  `Trigger '${triggerName}' names schema '${entry.firingSource.schemaName}', which has no resolved schemaId \u2014 the schema failed to provision.`
18273
19326
  );
18274
19327
  }
18275
- const triggerUser = await client.identity.createUser({
18276
- body: {
18277
- externalId: triggerPrincipalExternalId(blueprint.name, triggerName),
18278
- type: "SERVICE",
18279
- payload: { displayName: `Trigger: ${triggerName}` }
18280
- }
18281
- });
18282
- const triggerUserId = triggerUser.id;
19328
+ const triggerUserId = triggerPrincipalIds.get(triggerName);
18283
19329
  if (!triggerUserId) {
18284
- throw new Error(`Trigger '${triggerName}' service-principal create returned no user id.`);
19330
+ throw new Error(`Trigger '${triggerName}' has no resolved service principal.`);
18285
19331
  }
18286
19332
  const triggerPlaceholderProfileBody = {
18287
19333
  principalId: `usr_${triggerUserId}`,
@@ -18346,6 +19392,44 @@ async function runLoader(client, input) {
18346
19392
  { component: "loader", triggerName, principalId: `usr_${triggerUserId}` },
18347
19393
  "trigger access profile widened to its declared grant"
18348
19394
  );
19395
+ const toMark = triggerPrincipalsToMark.get(triggerName);
19396
+ if (toMark) {
19397
+ let reason;
19398
+ try {
19399
+ if (toMark.userType !== void 0 && toMark.userType !== "SERVICE") {
19400
+ reason = `it is a ${toMark.userType} user, not a service principal`;
19401
+ } else {
19402
+ const fresh = await ctxClient.identity.getUser({ id: toMark.userId });
19403
+ const payload = fresh.payload;
19404
+ if (fresh.type !== void 0 && fresh.type !== "SERVICE") {
19405
+ reason = `it is a ${fresh.type} user, not a service principal`;
19406
+ } else if (payloadIsMasked(payload)) {
19407
+ reason = "its payload is masked for this credential (a schema marks fields sensitive), so writing it back would replace real values";
19408
+ } else {
19409
+ await ctxClient.identity.createUser({
19410
+ upsert: true,
19411
+ body: {
19412
+ externalId: toMark.externalId,
19413
+ type: "SERVICE",
19414
+ payload: {
19415
+ ...payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : {},
19416
+ provisionedBy: blueprint.name
19417
+ }
19418
+ }
19419
+ });
19420
+ }
19421
+ }
19422
+ } catch (err) {
19423
+ reason = err instanceof Error ? err.message : String(err);
19424
+ }
19425
+ if (reason !== void 0) {
19426
+ log.warn(
19427
+ { component: "loader", triggerName, reason },
19428
+ "could not mark the confirmed trigger principal as this blueprint's; the next run will ask again"
19429
+ );
19430
+ }
19431
+ triggerPrincipalsReused.push({ triggerName, externalId: toMark.externalId, marked: reason === void 0, ...reason !== void 0 ? { reason } : {} });
19432
+ }
18349
19433
  }
18350
19434
  let triggerPrunes = [];
18351
19435
  let triggerPruneSkipped = [];
@@ -18368,6 +19452,18 @@ async function runLoader(client, input) {
18368
19452
  log
18369
19453
  }));
18370
19454
  }
19455
+ const agentActions = declaresAgents(blueprint) ? await ensureAgents({
19456
+ ctxClient,
19457
+ agents: blueprint.agents ?? {},
19458
+ blueprintName: blueprint.name,
19459
+ contextId: blueprint.contextId,
19460
+ allowAgentAdoption,
19461
+ onAgentCreated,
19462
+ onAgentSeen,
19463
+ onAgentAttempt,
19464
+ onAgentRefused,
19465
+ log
19466
+ }) : [];
18371
19467
  const keyName = skipKey ? "" : keyNameFor(blueprint.name, machineId);
18372
19468
  let keyAction = skipKey ? "skipped" : "created";
18373
19469
  let revokedKeyId;
@@ -18528,7 +19624,9 @@ async function runLoader(client, input) {
18528
19624
  schemaActions,
18529
19625
  roleActions,
18530
19626
  scriptActions,
19627
+ agentActions,
18531
19628
  triggerActions,
19629
+ triggerPrincipalsReused,
18532
19630
  triggerPrunes,
18533
19631
  triggerPruneSkipped,
18534
19632
  issuerActions,
@@ -18572,6 +19670,12 @@ function buildPlan(blueprint, opts = {}) {
18572
19670
  summary: `namespace '${ns.namespace}' (${ownerNote}${entityNote}${membershipNote}) [${BLUEPRINT_FIELD_PHASES.namespaces}-token phase]`
18573
19671
  });
18574
19672
  }
19673
+ for (const triggerName of Object.keys(blueprint.triggers ?? {})) {
19674
+ steps.push({
19675
+ kind: "principal",
19676
+ summary: `trigger '${triggerName}' principal '${triggerPrincipalExternalId(blueprint.name, triggerName)}' (SERVICE, marked as this blueprint's; an existing one without that mark needs confirmation)`
19677
+ });
19678
+ }
18575
19679
  for (const iss of blueprint.issuers ?? []) {
18576
19680
  const issuerUrl = sanitizeForPlanDisplay(iss.issuer);
18577
19681
  const jwksNote = `, jwksUri: ${sanitizeForPlanDisplay(iss.jwksUri)}`;
@@ -18643,10 +19747,6 @@ function buildPlan(blueprint, opts = {}) {
18643
19747
  });
18644
19748
  }
18645
19749
  for (const [triggerName, entry] of Object.entries(blueprint.triggers ?? {})) {
18646
- steps.push({
18647
- kind: "principal",
18648
- summary: `trigger '${triggerName}' principal '${blueprint.name}-trigger-${triggerName}' (SERVICE)`
18649
- });
18650
19750
  steps.push({
18651
19751
  kind: "profile",
18652
19752
  summary: `trigger '${triggerName}' profile: minimal placeholder grant (minted before the trigger row exists)`
@@ -18671,6 +19771,19 @@ function buildPlan(blueprint, opts = {}) {
18671
19771
  });
18672
19772
  }
18673
19773
  }
19774
+ for (const [agentName, entry] of Object.entries(blueprint.agents ?? {})) {
19775
+ const tools = (entry.tools ?? []).map((t) => t.allowWrites ? `${sanitizeForPlanDisplay(t.name, 64)} (writes)` : sanitizeForPlanDisplay(t.name, 64));
19776
+ const parts = [`model ${sanitizeForPlanDisplay(entry.model)}`, tools.length ? `tools [${tools.join(", ")}]` : "no tools"];
19777
+ if (entry.authority !== void 0) parts.push(`authority ${sanitizeForPlanDisplay(entry.authority, 32)}`);
19778
+ const limits = Object.entries(entry.limits ?? {}).filter(([, v]) => v !== void 0);
19779
+ if (limits.length) parts.push(`limits {${limits.map(([k, v]) => `${k} ${v}`).join(", ")}}`);
19780
+ if (entry.budget?.maxInferenceCents !== void 0) parts.push(`budget ${entry.budget.maxInferenceCents} cents per run`);
19781
+ if (entry.system) parts.push(`system prompt ${Array.from(entry.system).length} characters`);
19782
+ steps.push({
19783
+ kind: "agent",
19784
+ 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]`
19785
+ });
19786
+ }
18674
19787
  if (!opts.noKey) {
18675
19788
  steps.push({ kind: "key", summary: `key ${blueprint.name}-<machine> (mint 1 scoped ssk_*)` });
18676
19789
  }
@@ -18723,6 +19836,19 @@ function renderPlan(blueprint, steps, opts = {}) {
18723
19836
  ` and would break any trigger still referencing it. Remove one deliberately with`,
18724
19837
  ` 'vectros scripts delete', after 'vectros scripts list' shows you what is there.`
18725
19838
  );
19839
+ if (steps.some((s) => s.kind === "agent")) {
19840
+ legendLines.push(
19841
+ ` Counter-exception: an agent is never edited in place \u2014 a changed definition ADDS a new`,
19842
+ ` immutable version, and an unchanged one writes nothing. Agents are not reconciled by`,
19843
+ ` absence either: one this blueprint declared earlier and no longer does stays stored and`,
19844
+ ` runnable. Retire it deliberately through the API (DELETE /v1/agents/{id}).`,
19845
+ ` An agent that already exists and that nothing manages (made by hand) is REFUSED unless`,
19846
+ ` --allow-agent-adoption is passed: adopting it marks it as this blueprint's and gives it`,
19847
+ ` a new version if the definition differs. Another blueprint's agent is refused, and so`,
19848
+ ` is a retired one. Writing agents needs 'agents:c' on the applying credential, plus`,
19849
+ ` 'agents:u' and 'agents:r' for an agent that already exists.`
19850
+ );
19851
+ }
18726
19852
  if (blueprint.issuers?.some((i) => i.restrictedToDomain === void 0)) {
18727
19853
  legendLines.push(
18728
19854
  ` Note: a newly registered issuer without restrictedToDomain starts pending verification on platform`,
@@ -18797,7 +19923,7 @@ function displayChallengeValue(value) {
18797
19923
  return value === void 0 ? void 0 : printable(value) ?? WITHHELD;
18798
19924
  }
18799
19925
  function renderChallengeBlock(view, ctx, now) {
18800
- const id = view.issuerId ?? "(unknown)";
19926
+ const id = singleLine(String(view.issuerId ?? "(unknown)"), 100);
18801
19927
  const rawClaim = view.verificationClaim;
18802
19928
  const rawNonce = view.verificationNonce;
18803
19929
  const claim = rawClaim === void 0 ? "(not reported)" : printable(rawClaim) ?? WITHHELD;
@@ -18842,7 +19968,7 @@ function renderChallengeBlock(view, ctx, now) {
18842
19968
  `;
18843
19969
  }
18844
19970
  function renderExpiredBlock(view, ctx, rerun) {
18845
- const id = view.issuerId ?? "(unknown)";
19971
+ const id = singleLine(String(view.issuerId ?? "(unknown)"), 100);
18846
19972
  const expired = view.verificationExpiresAt === void 0 ? "(unknown)" : printable(view.verificationExpiresAt) ?? WITHHELD;
18847
19973
  const lines = [];
18848
19974
  lines.push(` \u2716 issuer '${id}' is STILL pending verification and its challenge EXPIRED at ${expired} (by this machine's clock).`);
@@ -20977,6 +22103,7 @@ function parseBootstrapArgs(argv) {
20977
22103
  noSeed: false,
20978
22104
  allowTenantWideNamespaces: false,
20979
22105
  allowScriptAdoption: false,
22106
+ allowAgentAdoption: false,
20980
22107
  requireVerifiedIssuers: false,
20981
22108
  client: "desktop",
20982
22109
  tenant: "live",
@@ -21045,6 +22172,9 @@ function parseBootstrapArgs(argv) {
21045
22172
  case "--allow-script-adoption":
21046
22173
  args.allowScriptAdoption = true;
21047
22174
  break;
22175
+ case "--allow-agent-adoption":
22176
+ args.allowAgentAdoption = true;
22177
+ break;
21048
22178
  case "--require-verified-issuers":
21049
22179
  args.requireVerifiedIssuers = true;
21050
22180
  break;
@@ -21102,6 +22232,38 @@ function buildConfirmExistingPrincipal(args, prompt, isTty) {
21102
22232
  return args.confirmExistingPrincipal;
21103
22233
  };
21104
22234
  }
22235
+ function buildConfirmExistingTriggerPrincipal(args, prompt, isTty, say = (text) => void process.stdout.write(text)) {
22236
+ const reusing = (d, how) => say(
22237
+ ` \u26A0 reusing existing trigger service principal '${d.externalId}' (trigger '${d.triggerName}') \u2014 proceeding because ${how}.
22238
+ `
22239
+ );
22240
+ return async (d) => {
22241
+ if (!args.yes && isTty) {
22242
+ const ans = await prompt(
22243
+ `
22244
+ \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.
22245
+ ` : d.provenance === "other-blueprint" ? `and is marked as provisioned by the blueprint '${singleLine(d.markedBy ?? "", 100)}', not this one.
22246
+ ` : `and carries no provenance marker (an earlier CLI release, or created by hand).
22247
+ `) + ` Continuing writes that identity's access profile to this blueprint's trigger grant, and marks it as
22248
+ this blueprint's. Expected when you re-apply a blueprint you already manage. If you don't recognize
22249
+ this identity, do NOT proceed: it may be a name collision with something you don't control.
22250
+ Proceed and use it? [y/N]: `
22251
+ );
22252
+ const ok = existingPrincipalConfirmed(ans);
22253
+ if (ok) reusing(d, "you confirmed it at the prompt");
22254
+ return ok;
22255
+ }
22256
+ if (args.confirmExistingPrincipal) reusing(d, "--confirm-existing-principal was passed");
22257
+ return args.confirmExistingPrincipal;
22258
+ };
22259
+ }
22260
+ function triggerPrincipalMarkLines(reused) {
22261
+ return (reused ?? []).map(
22262
+ (r) => r.marked ? ` \u2714 '${r.externalId}' is now marked as this blueprint's, so the next run will not ask again.
22263
+ ` : ` \u26A0 '${r.externalId}' was NOT marked as this blueprint's (${singleLine(asText(r.reason ?? "unknown reason"), 300)}), so the next run will ask again.
22264
+ `
22265
+ ).join("");
22266
+ }
21105
22267
  function seedSummaryLine(opts) {
21106
22268
  if (opts.noSeed) {
21107
22269
  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";
@@ -21112,7 +22274,7 @@ function seedSummaryLine(opts) {
21112
22274
  function seedIndexWarningBlock(failures) {
21113
22275
  if (failures.length === 0) return "";
21114
22276
  const lines = failures.map((f) => {
21115
- const reason = f.message ?? f.code ?? "indexing failed";
22277
+ const reason = singleLine(asText(f.message ?? f.code ?? "indexing failed"), 300);
21116
22278
  return ` \u2022 ${f.typeName}/${f.externalId} (${f.surface}): ${reason}
21117
22279
  `;
21118
22280
  });
@@ -21180,6 +22342,22 @@ function scriptTally(items) {
21180
22342
  if (unchanged) seg.push(`${unchanged} already current \u2014 nothing written`);
21181
22343
  return `${items.length} script${items.length === 1 ? "" : "s"} (${seg.join("; ")})`;
21182
22344
  }
22345
+ function agentTally(items) {
22346
+ if (items.length === 0) return null;
22347
+ const seg = items.map((i) => {
22348
+ switch (i.action) {
22349
+ case "created":
22350
+ return `'${i.name}' created at version ${i.version}`;
22351
+ case "unchanged":
22352
+ return `'${i.name}' unchanged at version ${i.version}`;
22353
+ case "versioned":
22354
+ return `'${i.name}' version ${i.previousVersion} -> ${i.version}`;
22355
+ case "adopted":
22356
+ 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})`;
22357
+ }
22358
+ });
22359
+ return `${items.length} agent${items.length === 1 ? "" : "s"} (${seg.join("; ")})`;
22360
+ }
21183
22361
  function applyOutcomeSummary(result, opts = {}) {
21184
22362
  const parts = [
21185
22363
  reconcileTally(result.schemaActions, "schema"),
@@ -21196,13 +22374,15 @@ function applyOutcomeSummary(result, opts = {}) {
21196
22374
  // wording applies unchanged; buildPlan's dry-run preview already discloses each trigger's own
21197
22375
  // steps individually, this is the apply-time tally counterpart.
21198
22376
  reconcileTally(result.triggerActions, "trigger"),
22377
+ // Agents — written after the triggers, so reported after them.
22378
+ agentTally(result.agentActions),
21199
22379
  `profile ${result.profileAction}`
21200
22380
  ].filter((p) => p !== null);
21201
22381
  const detail = parts.length ? ` \u2014 ${parts.join("; ")}` : "";
21202
22382
  const principalNote = (opts.principalNote ?? true) && result.userAction === "reconciled" ? ` \u26A0 service principal already existed in this tenant before this run (not newly created)
21203
22383
  ` : "";
21204
22384
  const pruneNotes = result.triggerPrunes.map(
21205
- (p) => ` \u26A0 trigger '${p.name}' removed from the blueprint \u2014 de-provisioned (rule + access profile deleted)
22385
+ (p) => ` \u26A0 trigger '${singleLine(asText(p.name), 100)}' removed from the blueprint \u2014 de-provisioned (rule + access profile deleted)
21206
22386
  ` + // Not a warning: 404 on the profile means it was already gone, so the end state is exactly
21207
22387
  // what a clean prune produces. Said out loud only so the absent second delete isn't read as
21208
22388
  // a silent skip. Every OTHER profile-delete failure lands in triggerPruneSkipped instead.
@@ -21210,9 +22390,9 @@ function applyOutcomeSummary(result, opts = {}) {
21210
22390
  `)
21211
22391
  ).join("");
21212
22392
  const pruneSkippedNotes = result.triggerPruneSkipped.map(
21213
- (s) => s.failed === false ? s.name ? ` \u2139 trigger '${s.name}' is not declared here and was left in place \u2014 ${s.reason}.
22393
+ (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}.
21214
22394
  ` : ` \u2139 a trigger rule was left in place \u2014 ${s.reason}.
21215
- ` : s.name ? ` \u26A0 trigger '${s.name}' is no longer declared but could NOT be removed \u2014 ${s.reason}. It is still live.
22395
+ ` : 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.
21216
22396
  ` : ` \u26A0 triggers could not be reconciled this run \u2014 ${s.reason}. A trigger removed from this blueprint may still be live.
21217
22397
  `
21218
22398
  ).join("");
@@ -21365,7 +22545,7 @@ async function runBootstrapCli(argv, deps = {}) {
21365
22545
  try {
21366
22546
  args = parseBootstrapArgs(argv);
21367
22547
  } catch (err) {
21368
- process.stderr.write(`${redactCredentials(err instanceof Error ? err.message : String(err))}
22548
+ process.stderr.write(`${redactCredentials(errorText(err))}
21369
22549
 
21370
22550
  ${BOOTSTRAP_HELP}
21371
22551
  `);
@@ -21497,7 +22677,7 @@ ${IDENTITIES_TO_ENSURE_HEADING}
21497
22677
  if (!keyring) {
21498
22678
  process.stderr.write(
21499
22679
  `
21500
- \u26A0 Could not open the keyring (${keyringOpenError instanceof Error ? keyringOpenError.message : String(keyringOpenError)}).
22680
+ \u26A0 Could not open the keyring (${errorText(keyringOpenError)}).
21501
22681
  Falling back to embedding the raw secret directly in the MCP config \u2014 this is NOT
21502
22682
  login-gated. Run \`vectros keyring doctor\` once it's fixed.
21503
22683
  `
@@ -21538,7 +22718,7 @@ ${IDENTITIES_TO_ENSURE_HEADING}
21538
22718
  } catch (err) {
21539
22719
  process.stderr.write(
21540
22720
  `
21541
- \u26A0 Could not store this key in the keyring (${err instanceof Error ? err.message : String(err)}).
22721
+ \u26A0 Could not store this key in the keyring (${errorText(err)}).
21542
22722
  Falling back to embedding the raw secret directly in the MCP config \u2014 this is NOT
21543
22723
  login-gated. Run \`vectros keyring doctor\` to see why the store failed.
21544
22724
  `
@@ -21570,10 +22750,12 @@ ${IDENTITIES_TO_ENSURE_HEADING}
21570
22750
  rotate: args.rotate,
21571
22751
  skipSeed: noSeed,
21572
22752
  allowScriptAdoption: args.allowScriptAdoption,
22753
+ allowAgentAdoption: args.allowAgentAdoption,
21573
22754
  readExistingKey,
21574
22755
  persistMintedKey,
21575
22756
  provisioned,
21576
22757
  remintBootstrapForContext,
22758
+ confirmExistingTriggerPrincipal: buildConfirmExistingTriggerPrincipal(args, promptCtx.prompt, isTty),
21577
22759
  remintForContext: buildRemintForContext({
21578
22760
  cognitoToken,
21579
22761
  apiBaseUrl: baseUrl,
@@ -21582,6 +22764,7 @@ ${IDENTITIES_TO_ENSURE_HEADING}
21582
22764
  }),
21583
22765
  log
21584
22766
  });
22767
+ process.stdout.write(triggerPrincipalMarkLines(result.triggerPrincipalsReused));
21585
22768
  const finishApply = () => finishIssuers(
21586
22769
  result.issuerActions,
21587
22770
  { context: blueprint.contextId, tenant: args.tenant, env: args.env, baseUrl: args.baseUrl },
@@ -21650,7 +22833,7 @@ ${codeAddHint}`);
21650
22833
  codeAddHint
21651
22834
  );
21652
22835
  } catch (err) {
21653
- process.stderr.write(` \u26A0 Could not merge config (${err instanceof Error ? err.message : String(err)}).
22836
+ process.stderr.write(` \u26A0 Could not merge config (${errorText(err)}).
21654
22837
  `);
21655
22838
  process.stdout.write(`
21656
22839
  Add this manually instead:
@@ -21662,24 +22845,37 @@ ${codeAddHint}`);
21662
22845
  } catch (err) {
21663
22846
  if (err instanceof SessionExpiredError) {
21664
22847
  process.stderr.write(`
21665
- \u2716 ${err.message}
22848
+ \u2716 ${errorText(err)}
21666
22849
  `);
21667
22850
  return 2;
21668
22851
  }
21669
22852
  if (err instanceof LostKeyError) {
21670
22853
  process.stderr.write(`
21671
- \u2716 ${err.message}
22854
+ \u2716 ${errorText(err)}
21672
22855
  `);
21673
22856
  return 3;
21674
22857
  }
21675
22858
  if (err instanceof ExistingPrincipalNotConfirmedError) {
21676
22859
  process.stderr.write(
21677
22860
  `
21678
- \u2716 ${err.message}
22861
+ \u2716 ${errorText(err)}
21679
22862
 
21680
22863
  If this is expected (a re-apply, or a deliberate second machine for an integration you already own),
21681
22864
  re-run with --confirm-existing-principal, or confirm interactively.
21682
22865
  If you don't recognize this identity, investigate the blueprint's servicePrincipal.externalId before re-running.
22866
+ `
22867
+ );
22868
+ return 3;
22869
+ }
22870
+ if (err instanceof ExistingTriggerPrincipalNotConfirmedError) {
22871
+ process.stderr.write(
22872
+ `
22873
+ \u2716 ${errorText(err)}
22874
+
22875
+ If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal,
22876
+ or confirm interactively; the principal is then marked as this blueprint's and not asked about again.
22877
+ If you don't recognize this identity, investigate the trigger's service principal first.
22878
+ No schema, role, access profile, trigger, key or seed record was written.
21683
22879
  `
21684
22880
  );
21685
22881
  return 3;
@@ -21687,7 +22883,7 @@ ${codeAddHint}`);
21687
22883
  if (err instanceof BlueprintIdentityError || err instanceof UnresolvedIdentitiesError) {
21688
22884
  process.stderr.write(
21689
22885
  `
21690
- \u2716 ${err.message}
22886
+ \u2716 ${errorText(err)}
21691
22887
  The app-context, service principal, and any declared namespaces were already provisioned. No schemas, profile, roles, key, or seed records were.
21692
22888
  `
21693
22889
  );
@@ -21696,7 +22892,7 @@ ${codeAddHint}`);
21696
22892
  if (err instanceof IssuerScopeNotAppliedError) {
21697
22893
  process.stderr.write(
21698
22894
  `
21699
- \u2716 ${err.message}
22895
+ \u2716 ${errorText(err)}
21700
22896
  The app-context, service principal, and any declared namespaces were already provisioned. No schemas, profile, roles, key, or seed records were.
21701
22897
  `
21702
22898
  );
@@ -21704,7 +22900,7 @@ ${codeAddHint}`);
21704
22900
  }
21705
22901
  if (err instanceof TenantWideNamespacesNotAllowedError) {
21706
22902
  process.stderr.write(`
21707
- \u2716 ${err.message}
22903
+ \u2716 ${errorText(err)}
21708
22904
  Nothing was provisioned.
21709
22905
  `);
21710
22906
  return 2;
@@ -21712,7 +22908,7 @@ ${codeAddHint}`);
21712
22908
  process.stderr.write(`
21713
22909
  ${formatCliError(err, { action: "Bootstrap failed" })}`);
21714
22910
  if (process.env.VECTROS_MCP_DEBUG && err instanceof Error && err.stack) {
21715
- process.stderr.write(`${err.stack}
22911
+ process.stderr.write(`${cleanBlock(err.stack)}
21716
22912
  `);
21717
22913
  }
21718
22914
  return 1;
@@ -21737,6 +22933,7 @@ function parseApplyArgs(argv) {
21737
22933
  seed: false,
21738
22934
  allowTenantWideNamespaces: false,
21739
22935
  allowScriptAdoption: false,
22936
+ allowAgentAdoption: false,
21740
22937
  requireVerifiedIssuers: false,
21741
22938
  confirmExistingPrincipal: false,
21742
22939
  prune: false,
@@ -21797,6 +22994,9 @@ function parseApplyArgs(argv) {
21797
22994
  case "--allow-script-adoption":
21798
22995
  args.allowScriptAdoption = true;
21799
22996
  break;
22997
+ case "--allow-agent-adoption":
22998
+ args.allowAgentAdoption = true;
22999
+ break;
21800
23000
  case "--require-verified-issuers":
21801
23001
  args.requireVerifiedIssuers = true;
21802
23002
  break;
@@ -21869,16 +23069,18 @@ Usage:
21869
23069
  [--set name=value ...] [--values <file>] [--seed]
21870
23070
  [--confirm-existing-principal] [--require-verified-issuers]
21871
23071
  [--allow-tenant-wide-namespaces] [--allow-script-adoption]
23072
+ [--allow-agent-adoption]
21872
23073
  [--token <t> | env VECTROS_BOOTSTRAP_TOKEN] [--base-url <url>]
21873
23074
 
21874
23075
  <file|name> is a path to your own blueprint (YAML/JSON) or the name of a bundled one
21875
23076
  (${BLUEPRINT_NAMES.join(", ")}). Everything it declares is created or reconciled \u2014 the context, its
21876
- service principal and access profile, schemas, roles, issuers, triggers and scripts (and, only with --seed, its
23077
+ service principal and access profile, schemas, roles, issuers, triggers, scripts and agents (and, only with --seed, its
21877
23078
  sample seed records) \u2014 but NO scoped key is minted, nothing is written to a keyring and no MCP client config is
21878
23079
  touched, so a CI runner can re-apply a changed blueprint. To also get a credential, use 'vectros bootstrap'. It
21879
23080
  authenticates the same way bootstrap does and refuses on the same gates (scope, capability, tenant-wide namespaces)
21880
23081
  before anything changes. (A script pushed onto an existing hand-authored name is checked when the scripts are
21881
- pushed, partway through: --allow-script-adoption.)
23082
+ pushed, partway through: --allow-script-adoption. An agent that exists with no blueprint mark is checked when the
23083
+ agents are written, also partway through: --allow-agent-adoption.)
21882
23084
 
21883
23085
  Before it changes anything it prints the account, tenant, context and API it is pointed at, then the plan, and
21884
23086
  asks to proceed unless --yes is given. With no terminal --yes is required.
@@ -21913,7 +23115,7 @@ Exit codes:
21913
23115
  3 needs a confirmation it did not get (an existing service principal: --confirm-existing-principal)
21914
23116
  4 applied in full, but --require-verified-issuers was given and an issuer is pending verification`;
21915
23117
  function blueprintRefusal(err) {
21916
- const message = err instanceof Error ? err.message : String(err);
23118
+ const message = errorText(err);
21917
23119
  if (message.startsWith("Unknown blueprint ")) {
21918
23120
  return `No bundled blueprint matches the argument you gave. Bundled blueprints: ${BLUEPRINT_NAMES.join(", ")} (or pass a path like ./my.yaml).`;
21919
23121
  }
@@ -21934,7 +23136,7 @@ async function runApplyCli(argv, deps = {}) {
21934
23136
  } catch (err) {
21935
23137
  process.stderr.write(
21936
23138
  `
21937
- \u2716 ${redactCredentials(err instanceof Error ? err.message : String(err))}
23139
+ \u2716 ${redactCredentials(errorText(err))}
21938
23140
  See 'vectros blueprint apply --help'. Nothing was changed.
21939
23141
  `
21940
23142
  );
@@ -21964,7 +23166,7 @@ async function runApplyCli(argv, deps = {}) {
21964
23166
  try {
21965
23167
  baseUrl = envToBaseUrl(args.env, args.baseUrl ?? process.env.VECTROS_API_BASE_URL);
21966
23168
  } catch (err) {
21967
- return refuse(err instanceof Error ? err.message : String(err));
23169
+ return refuse(errorText(err));
21968
23170
  }
21969
23171
  const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
21970
23172
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
@@ -22000,7 +23202,7 @@ async function runApplyCli(argv, deps = {}) {
22000
23202
  try {
22001
23203
  cognitoToken = await resolveBridgeCognitoToken(args, promptCtx.prompt, isTty, deps.resolveCognitoToken);
22002
23204
  } catch (err) {
22003
- return refuse(err instanceof Error ? err.message : String(err));
23205
+ return refuse(errorText(err));
22004
23206
  }
22005
23207
  const mintBridge = deps.mintBridgeToken ?? mintBridgeToken;
22006
23208
  const mintContext = deps.mintContextToken ?? mintContextToken;
@@ -22118,6 +23320,7 @@ ${formatCliError(err, { action: "Could not mint a fresh bootstrap token after yo
22118
23320
  client,
22119
23321
  log
22120
23322
  );
23323
+ const confirmTriggerPrincipal = buildConfirmExistingTriggerPrincipal(args, promptCtx.prompt, isTty);
22121
23324
  const result = await (deps.runLoader ?? runLoader)(client, {
22122
23325
  blueprint: resolvedBlueprint,
22123
23326
  tenantId,
@@ -22127,14 +23330,17 @@ ${formatCliError(err, { action: "Could not mint a fresh bootstrap token after yo
22127
23330
  // Off unless asked for: apply never deletes on its own. `blueprint-test` sets the same switch off.
22128
23331
  pruneTriggers: args.prune,
22129
23332
  allowScriptAdoption: args.allowScriptAdoption,
23333
+ allowAgentAdoption: args.allowAgentAdoption,
22130
23334
  readExistingKey: async () => void 0,
22131
23335
  persistMintedKey: async () => {
22132
23336
  },
22133
23337
  provisioned,
22134
23338
  remintBootstrapForContext,
23339
+ confirmExistingTriggerPrincipal: confirmTriggerPrincipal,
22135
23340
  remintForContext: buildRemintForContext({ cognitoToken, apiBaseUrl: baseUrl, tenant, mintContext }),
22136
23341
  log
22137
23342
  });
23343
+ process.stdout.write(triggerPrincipalMarkLines(result.triggerPrincipalsReused));
22138
23344
  process.stdout.write(
22139
23345
  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.
22140
23346
  ` + 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.
@@ -22167,12 +23373,26 @@ function reportApplyError(err) {
22167
23373
  if (err instanceof ExistingPrincipalNotConfirmedError) {
22168
23374
  process.stderr.write(
22169
23375
  `
22170
- \u2716 ${err.message}
23376
+ \u2716 ${errorText(err)}
22171
23377
 
22172
23378
  If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal
22173
23379
  (with --yes in a pipeline, where it is standing consent), or confirm at the prompt.
22174
23380
  If you don't recognize this identity, investigate the blueprint's servicePrincipal.externalId first.
22175
23381
  Only the principal lookup had run; no context, grant or anything else was created or changed.
23382
+ `
23383
+ );
23384
+ return 3;
23385
+ }
23386
+ if (err instanceof ExistingTriggerPrincipalNotConfirmedError) {
23387
+ process.stderr.write(
23388
+ `
23389
+ \u2716 ${errorText(err)}
23390
+
23391
+ If this is expected (a re-apply of a blueprint you manage), re-run with --confirm-existing-principal
23392
+ (with --yes in a pipeline, where it is standing consent), or confirm at the prompt; the principal is then
23393
+ marked as this blueprint's and not asked about again.
23394
+ If you don't recognize this identity, investigate the trigger's service principal first.
23395
+ No schema, role, access profile, trigger or seed record was written.
22176
23396
  `
22177
23397
  );
22178
23398
  return 3;
@@ -22180,7 +23400,7 @@ function reportApplyError(err) {
22180
23400
  if (err instanceof BlueprintIdentityError || err instanceof UnresolvedIdentitiesError) {
22181
23401
  process.stderr.write(
22182
23402
  `
22183
- \u2716 ${err.message}
23403
+ \u2716 ${errorText(err)}
22184
23404
  The app-context, service principal, and any declared namespaces were already provisioned. No schemas, profile, roles or seed records were.
22185
23405
  `
22186
23406
  );
@@ -22189,7 +23409,7 @@ function reportApplyError(err) {
22189
23409
  if (err instanceof IssuerScopeNotAppliedError) {
22190
23410
  process.stderr.write(
22191
23411
  `
22192
- \u2716 ${err.message}
23412
+ \u2716 ${errorText(err)}
22193
23413
  The app-context, service principal, and any declared namespaces were already provisioned. No schemas, profile, roles or seed records were.
22194
23414
  `
22195
23415
  );
@@ -22197,7 +23417,7 @@ function reportApplyError(err) {
22197
23417
  }
22198
23418
  if (err instanceof TenantWideNamespacesNotAllowedError) {
22199
23419
  process.stderr.write(`
22200
- \u2716 ${err.message}
23420
+ \u2716 ${errorText(err)}
22201
23421
  Nothing was changed.
22202
23422
  `);
22203
23423
  return 2;
@@ -22208,7 +23428,7 @@ ${formatCliError(err, { action: "Apply failed" })} The apply stopped part-way:
22208
23428
  `
22209
23429
  );
22210
23430
  if (process.env.VECTROS_MCP_DEBUG && err instanceof Error && err.stack) {
22211
- process.stderr.write(`${err.stack}
23431
+ process.stderr.write(`${cleanBlock(err.stack)}
22212
23432
  `);
22213
23433
  }
22214
23434
  return 1;
@@ -22216,6 +23436,8 @@ ${formatCliError(err, { action: "Apply failed" })} The apply stopped part-way:
22216
23436
 
22217
23437
  // src/lint.ts
22218
23438
  var ENUM_COMPATIBLE_TYPES = /* @__PURE__ */ new Set(["enum", "string", "string[]"]);
23439
+ var PLATFORM_RESERVED_PAYLOAD_FIELD_NAMES = ["externalId", "partnerUserId", "scopes", "userId"];
23440
+ var PAYLOAD_RESERVING_SURFACES = ["record", "document"];
22219
23441
  var BUILT_IN_SORT_BY = /* @__PURE__ */ new Set(["createdAt", "lastUpdated"]);
22220
23442
  function lookupFieldNames(lf) {
22221
23443
  if (typeof lf === "string") return [lf];
@@ -22289,6 +23511,17 @@ function lintBlueprint(bp) {
22289
23511
  }
22290
23512
  fieldIds.add(f.fieldId);
22291
23513
  fieldsById.set(f.fieldId, f);
23514
+ if (PLATFORM_RESERVED_PAYLOAD_FIELD_NAMES.includes(f.fieldId) && (schema.allowedSurfaces ?? ["record"]).some((surface) => PAYLOAD_RESERVING_SURFACES.includes(surface))) {
23515
+ const seeds = (bp.seed ?? []).filter((rec) => rec.typeName === schema.typeName);
23516
+ const seedSetsIt = seeds.some((rec) => Object.prototype.hasOwnProperty.call(rec.fields ?? {}, f.fieldId));
23517
+ const seedsFail = seedSetsIt || f.required === true && seeds.some((rec) => rec.fields !== void 0);
23518
+ 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. `;
23519
+ findings.push({
23520
+ severity: "warning",
23521
+ rule: "reserved-payload-field-name",
23522
+ 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.`)
23523
+ });
23524
+ }
22292
23525
  if (f.enumValues && f.enumValues.length > 0 && !ENUM_COMPATIBLE_TYPES.has(f.fieldType)) {
22293
23526
  findings.push({
22294
23527
  severity: "warning",
@@ -22305,6 +23538,13 @@ function lintBlueprint(bp) {
22305
23538
  }
22306
23539
  for (const lf of schema.lookupFields ?? []) {
22307
23540
  for (const name of lookupFieldNames(lf)) {
23541
+ if (PLATFORM_RESERVED_PAYLOAD_FIELD_NAMES.includes(name)) {
23542
+ findings.push({
23543
+ severity: "error",
23544
+ rule: "reserved-lookup-field-name",
23545
+ 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.`
23546
+ });
23547
+ }
22308
23548
  if (!fieldIds.has(name)) {
22309
23549
  findings.push({
22310
23550
  severity: "error",
@@ -22523,7 +23763,7 @@ schemas:
22523
23763
  lookupFields: [title] # extra O(1) exact-match lookup indexes (max 10)
22524
23764
 
22525
23765
  accessProfile:
22526
- # SCOPE GATE: data-plane only (records/schemas/search/documents/folders/inference/entities),
23766
+ # SCOPE GATE: data-plane only (records/schemas/search/documents/folders/inference/entities/runs),
22527
23767
  # PLUS profiles:r/u/d (bounded by the platform to redistributing access you already have,
22528
23768
  # never granting more), profiles:c:self (create a profile for yourself only \u2014 the
22529
23769
  # platform resolves "self" from the caller, not from anything you declare here) and
@@ -22549,7 +23789,7 @@ accessProfile:
22549
23789
  dataScope: {}
22550
23790
  # capabilities is a SEPARATE gated dimension from allowedActions \u2014 named platform
22551
23791
  # effects a resource:ops verb can't express. Only 'member-lifecycle' and
22552
- # 'delegate-principal-stamp' are admitted today; every other name is REJECTED, same
23792
+ # 'delegate-principal-stamp' are admitted; every other name is REJECTED, same
22553
23793
  # posture as the scope gate above. 'member-lifecycle' is also what lets this clause
22554
23794
  # carry a bare profiles:c.
22555
23795
  # capabilities: [member-lifecycle]
@@ -22680,7 +23920,7 @@ async function resolveAndValidate(arg, verb, supplied) {
22680
23920
  try {
22681
23921
  blueprint = await resolveBlueprintArg(arg, supplied);
22682
23922
  } catch (err) {
22683
- process.stderr.write(` \u2716 ${err instanceof Error ? err.message : String(err)}
23923
+ process.stderr.write(` \u2716 ${errorText(err)}
22684
23924
  `);
22685
23925
  return null;
22686
23926
  }
@@ -22707,7 +23947,7 @@ async function resolveAndValidate(arg, verb, supplied) {
22707
23947
  for (const f of findings) {
22708
23948
  const mark = f.severity === "error" ? "\u2716" : "\u26A0";
22709
23949
  const stream = f.severity === "error" ? process.stderr : process.stdout;
22710
- stream.write(` ${mark} [${f.rule}] ${f.message}
23950
+ stream.write(` ${mark} [${f.rule}] ${singleLine(f.message, 2e3)}
22711
23951
  `);
22712
23952
  }
22713
23953
  if (!lintPasses(findings)) {
@@ -22722,7 +23962,7 @@ async function runValidate(args) {
22722
23962
  try {
22723
23963
  supplied = await buildInputSupply(args.set, args.values);
22724
23964
  } catch (err) {
22725
- process.stderr.write(` \u2716 ${err instanceof Error ? err.message : String(err)}
23965
+ process.stderr.write(` \u2716 ${errorText(err)}
22726
23966
  `);
22727
23967
  return 2;
22728
23968
  }
@@ -22734,6 +23974,13 @@ async function runValidate(args) {
22734
23974
  process.stdout.write(
22735
23975
  ` \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"
22736
23976
  );
23977
+ if (declaresAgents(blueprint)) {
23978
+ const n = Object.keys(blueprint.agents ?? {}).length;
23979
+ process.stdout.write(
23980
+ ` \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).
23981
+ `
23982
+ );
23983
+ }
22737
23984
  return 0;
22738
23985
  }
22739
23986
  async function runPlan(args) {
@@ -22741,13 +23988,20 @@ async function runPlan(args) {
22741
23988
  try {
22742
23989
  supplied = await buildInputSupply(args.set, args.values);
22743
23990
  } catch (err) {
22744
- process.stderr.write(` \u2716 ${err instanceof Error ? err.message : String(err)}
23991
+ process.stderr.write(` \u2716 ${errorText(err)}
22745
23992
  `);
22746
23993
  return 2;
22747
23994
  }
22748
23995
  const blueprint = await resolveAndValidate(args.file, "plan", supplied);
22749
23996
  if (!blueprint) return 2;
22750
- const steps = buildPlan(blueprint);
23997
+ let steps;
23998
+ try {
23999
+ steps = buildPlan(blueprint);
24000
+ } catch (err) {
24001
+ process.stderr.write(` \u2716 ${errorText(err)}
24002
+ `);
24003
+ return 2;
24004
+ }
22751
24005
  process.stdout.write(`
22752
24006
  ${renderPlan(blueprint, steps)}
22753
24007
  `);
@@ -22817,7 +24071,7 @@ async function runInit(args) {
22817
24071
  try {
22818
24072
  await writeFile2(outPath, scaffold, "utf8");
22819
24073
  } catch (err) {
22820
- process.stderr.write(` \u2716 Could not write ${outPath}: ${err instanceof Error ? err.message : String(err)}
24074
+ process.stderr.write(` \u2716 Could not write ${outPath}: ${errorText(err)}
22821
24075
  `);
22822
24076
  return 2;
22823
24077
  }
@@ -22850,7 +24104,7 @@ async function runBlueprintCli(argv, deps = {}) {
22850
24104
  try {
22851
24105
  args = parseBlueprintArgs(argv);
22852
24106
  } catch (err) {
22853
- process.stderr.write(`${err instanceof Error ? err.message : String(err)}
24107
+ process.stderr.write(`${errorText(err)}
22854
24108
 
22855
24109
  ${BLUEPRINT_HELP}
22856
24110
  `);
@@ -25875,100 +27129,338 @@ async function runScriptsList(opts, deps = {}) {
25875
27129
  }
25876
27130
  process.stdout.write(
25877
27131
  "\n" + formatTable(rows, [
25878
- { header: "name", get: (s) => s.name },
25879
- { header: "version", get: (s) => s.scriptVersion === void 0 ? void 0 : String(s.scriptVersion) },
25880
- { header: "id", get: (s) => s.id },
25881
- { header: "bytes", get: (s) => s.source === void 0 ? void 0 : String(utf8Bytes2(s.source)) },
25882
- { header: "createdAt", get: (s) => s.createdAt }
25883
- ]) + "\n"
27132
+ { header: "name", get: (s) => s.name },
27133
+ { header: "version", get: (s) => s.scriptVersion === void 0 ? void 0 : String(s.scriptVersion) },
27134
+ { header: "id", get: (s) => s.id },
27135
+ { header: "bytes", get: (s) => s.source === void 0 ? void 0 : String(utf8Bytes2(s.source)) },
27136
+ { header: "createdAt", get: (s) => s.createdAt }
27137
+ ]) + "\n"
27138
+ );
27139
+ return 0;
27140
+ } catch (err) {
27141
+ process.stderr.write(formatCliError(err, { action: "scripts list failed" }));
27142
+ return 1;
27143
+ }
27144
+ });
27145
+ }
27146
+ async function runScriptsGet(opts, deps = {}) {
27147
+ return withAuth(opts, deps, async (auth) => {
27148
+ const client = auth.client;
27149
+ try {
27150
+ const script = await client.scripts.getScript({ id: opts.id });
27151
+ if (opts.json) {
27152
+ process.stdout.write(`${formatJson(script)}
27153
+ `);
27154
+ return 0;
27155
+ }
27156
+ process.stdout.write(`
27157
+ ${renderScript(script, { source: true })}
27158
+ `);
27159
+ return 0;
27160
+ } catch (err) {
27161
+ process.stderr.write(formatCliError(err, { action: "scripts get failed" }));
27162
+ return 1;
27163
+ }
27164
+ });
27165
+ }
27166
+ async function runScriptsDelete(opts, deps = {}) {
27167
+ process.stderr.write(
27168
+ `
27169
+ \u26A0 Deleting script version '${opts.id}' is permanent \u2014 the stored source is the only copy.
27170
+ Other versions of the same name are unaffected.
27171
+ The platform refuses this delete if a trigger rule still references this version \u2014
27172
+ directly, or via 'latest' if this is the name's current newest version \u2014 and names the
27173
+ offending rule instead of deleting. If no rule references it, the delete proceeds.
27174
+ And if this is the name's LATEST version, its identity is REUSED: the next push of that
27175
+ name recomputes the same version number and the same id, so anything holding a pinned
27176
+ '${opts.id}' or a name@version reference will silently resolve to different source.
27177
+
27178
+ `
27179
+ );
27180
+ if (!opts.force) {
27181
+ const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
27182
+ if (!isTty) {
27183
+ process.stderr.write(
27184
+ ` \u2716 scripts delete is irreversible and needs an interactive confirmation.
27185
+ Re-run with --force to delete '${opts.id}' without a prompt.
27186
+ `
27187
+ );
27188
+ return 2;
27189
+ }
27190
+ const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
27191
+ const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
27192
+ return { prompt: (q) => rl.question(q), close: () => rl.close() };
27193
+ })();
27194
+ try {
27195
+ const typed = await promptCtx.prompt(` Type the version id ('${opts.id}') to confirm: `);
27196
+ if (typed.trim() !== opts.id) {
27197
+ process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
27198
+ `);
27199
+ return 1;
27200
+ }
27201
+ } finally {
27202
+ promptCtx.close();
27203
+ }
27204
+ }
27205
+ return withAuth(opts, deps, async (auth) => {
27206
+ const client = auth.client;
27207
+ try {
27208
+ await client.scripts.deleteScript({ id: opts.id });
27209
+ if (opts.json) {
27210
+ process.stdout.write(`${formatJson({ id: opts.id, deleted: true })}
27211
+ `);
27212
+ return 0;
27213
+ }
27214
+ process.stdout.write(` \u2714 Deleted script version '${opts.id}' from '${opts.context}'.
27215
+ `);
27216
+ return 0;
27217
+ } catch (err) {
27218
+ process.stderr.write(formatCliError(err, { action: "scripts delete failed" }));
27219
+ return 1;
27220
+ }
27221
+ });
27222
+ }
27223
+ function errText(err) {
27224
+ return err instanceof Error ? err.message : String(err);
27225
+ }
27226
+
27227
+ // src/commands/agents.ts
27228
+ import { readFile as readFile4 } from "fs/promises";
27229
+ import { extname as extname2 } from "path";
27230
+ import { parse as parseYaml2 } from "yaml";
27231
+ var DEFINITION_KEYS = /* @__PURE__ */ new Set(["description", "authority", "model", "system", "tools", "limits", "budget"]);
27232
+ async function readDefinition(path4, name) {
27233
+ let raw;
27234
+ try {
27235
+ raw = await readFile4(path4, "utf8");
27236
+ } catch (err) {
27237
+ return { error: `could not read --file '${shortPath(path4, 300)}': ${quotedBlock(err instanceof Error ? err.message : String(err))}` };
27238
+ }
27239
+ let parsed;
27240
+ try {
27241
+ const ext = extname2(path4).toLowerCase();
27242
+ parsed = ext === ".yaml" || ext === ".yml" ? parseYaml2(raw, { logLevel: "error" }) : JSON.parse(raw);
27243
+ } catch (err) {
27244
+ return { error: `--file '${shortPath(path4, 300)}' is not valid ${/\.ya?ml$/i.test(path4) ? "YAML" : "JSON"}: ${quotedBlock(err instanceof Error ? err.message : String(err))}` };
27245
+ }
27246
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
27247
+ return { error: `--file '${shortPath(path4, 300)}' must hold one agent definition (an object with at least a 'model').` };
27248
+ }
27249
+ const entries = Object.entries(parsed);
27250
+ const unknown = entries.map(([k]) => k).filter((k) => !DEFINITION_KEYS.has(k) && k !== "name");
27251
+ if (unknown.length > 0) {
27252
+ return {
27253
+ 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(", ")}.`
27254
+ };
27255
+ }
27256
+ const declaredName = parsed.name;
27257
+ if (declaredName !== void 0 && typeof declaredName !== "string") {
27258
+ return { error: `--file '${shortPath(path4, 300)}' has a 'name' that is not a string.` };
27259
+ }
27260
+ if (declaredName !== void 0 && name !== void 0 && declaredName !== name) {
27261
+ return {
27262
+ error: `--file '${shortPath(path4, 300)}' names the agent '${singleLine(String(declaredName), 80)}', not '${name}'. Remove 'name' from the file or make them match.`
27263
+ };
27264
+ }
27265
+ const { name: _name, ...definition } = parsed;
27266
+ void _name;
27267
+ if (typeof definition.model !== "string" || definition.model.trim().length === 0) {
27268
+ return { error: `--file '${shortPath(path4, 300)}' needs a 'model': the id of the model the agent runs on.` };
27269
+ }
27270
+ if (singleLine(definition.model, definition.model.length + 1) !== definition.model) {
27271
+ 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.` };
27272
+ }
27273
+ return { definition, ...declaredName !== void 0 ? { declaredName } : {} };
27274
+ }
27275
+ function withHint(hint) {
27276
+ return hint ? ` ${hint}
27277
+ ` : "";
27278
+ }
27279
+ function toolsLine(a) {
27280
+ if (!a.tools) return void 0;
27281
+ if (a.tools.length === 0) return "(none)";
27282
+ return a.tools.map((t) => t.allowWrites ? `${t.name} (writes)` : t.name).join(", ");
27283
+ }
27284
+ function oneLine(value, maxLength = 200) {
27285
+ return value === void 0 ? void 0 : singleLine(value, maxLength);
27286
+ }
27287
+ function indentedBlock(value, maxLength = 1e5) {
27288
+ if (value === void 0) return void 0;
27289
+ return value.split(/\r\n|[\r\n\u0085\u2028\u2029]/).map((line, i) => {
27290
+ const clean = stripControlCharacters(line.replace(/\t/g, " "), maxLength);
27291
+ return i === 0 || clean === "" ? clean : ` ${clean}`;
27292
+ }).join("\n").trimEnd();
27293
+ }
27294
+ function renderAgent(a) {
27295
+ return formatKeyValues([
27296
+ ["id", oneLine(a.id)],
27297
+ ["name", oneLine(a.name)],
27298
+ ["status", oneLine(a.status)],
27299
+ ["version", oneLine(a.version === void 0 ? void 0 : String(a.version))],
27300
+ ["latestVersion", oneLine(a.latestVersion === void 0 ? void 0 : String(a.latestVersion))],
27301
+ ["model", oneLine(a.model)],
27302
+ ["authority", oneLine(a.authority)],
27303
+ ["description", oneLine(a.description, 1e3)],
27304
+ ["tools", oneLine(toolsLine(a), 5e3)],
27305
+ ["limits", oneLine(a.limits === void 0 ? void 0 : JSON.stringify(a.limits), 1e3)],
27306
+ ["budget", oneLine(a.budget === void 0 ? void 0 : JSON.stringify(a.budget), 1e3)],
27307
+ ["provisionedBy", oneLine(a.provisionedBy)],
27308
+ ["createdAt", oneLine(a.createdAt)],
27309
+ ["system", indentedBlock(a.system)]
27310
+ ]);
27311
+ }
27312
+ async function runAgentsList(opts, deps = {}) {
27313
+ return withAuth(opts, deps, async (auth) => {
27314
+ const client = auth.client;
27315
+ try {
27316
+ const rows = await drainPages(
27317
+ (startFrom) => client.agents.listAgents({
27318
+ contextId: opts.context,
27319
+ limit: PAGE_LIMIT,
27320
+ ...opts.includeRetired ? { includeRetired: true } : {},
27321
+ ...startFrom ? { startFrom } : {}
27322
+ })
27323
+ );
27324
+ if (opts.json) {
27325
+ process.stdout.write(`${formatJson(rows)}
27326
+ `);
27327
+ return 0;
27328
+ }
27329
+ if (rows.length === 0) {
27330
+ process.stdout.write(`
27331
+ No agents in '${opts.context}'.
27332
+ `);
27333
+ return 0;
27334
+ }
27335
+ const summaryOnly = rows.every((a) => a.model === void 0);
27336
+ process.stdout.write(
27337
+ "\n" + formatTable(rows, [
27338
+ { header: "name", get: (a) => oneLine(a.name) },
27339
+ { header: "version", get: (a) => oneLine(a.latestVersion === void 0 ? void 0 : String(a.latestVersion)) },
27340
+ { header: "status", get: (a) => oneLine(a.status) },
27341
+ { header: "model", get: (a) => oneLine(a.model) },
27342
+ { header: "id", get: (a) => oneLine(a.id) }
27343
+ ]) + "\n" + (summaryOnly ? "\n (A summary: this credential may start runs but cannot read agent definitions, which needs the 'agents:r' scope.)\n" : "")
25884
27344
  );
25885
27345
  return 0;
25886
27346
  } catch (err) {
25887
- process.stderr.write(formatCliError(err, { action: "scripts list failed" }));
27347
+ process.stderr.write(formatCliError(sanitizedAgentError(err), { action: "agents list failed" }) + withHint(agentErrorHint(err, opts.context, "'agents:r'")));
25888
27348
  return 1;
25889
27349
  }
25890
27350
  });
25891
27351
  }
25892
- async function runScriptsGet(opts, deps = {}) {
27352
+ async function runAgentsGet(opts, deps = {}) {
25893
27353
  return withAuth(opts, deps, async (auth) => {
25894
27354
  const client = auth.client;
25895
27355
  try {
25896
- const script = await client.scripts.getScript({ id: opts.id });
27356
+ const agent = await client.agents.getAgent({
27357
+ id: opts.id,
27358
+ contextId: opts.context,
27359
+ ...opts.version !== void 0 ? { version: opts.version } : {}
27360
+ });
25897
27361
  if (opts.json) {
25898
- process.stdout.write(`${formatJson(script)}
27362
+ process.stdout.write(`${formatJson(agent)}
25899
27363
  `);
25900
27364
  return 0;
25901
27365
  }
25902
27366
  process.stdout.write(`
25903
- ${renderScript(script, { source: true })}
27367
+ ${renderAgent(agent)}
25904
27368
  `);
25905
27369
  return 0;
25906
27370
  } catch (err) {
25907
- process.stderr.write(formatCliError(err, { action: "scripts get failed" }));
27371
+ process.stderr.write(formatCliError(sanitizedAgentError(err), { action: "agents get failed" }) + withHint(agentErrorHint(err, opts.context, "'agents:r'")));
25908
27372
  return 1;
25909
27373
  }
25910
27374
  });
25911
27375
  }
25912
- async function runScriptsDelete(opts, deps = {}) {
25913
- process.stderr.write(
25914
- `
25915
- \u26A0 Deleting script version '${opts.id}' is permanent \u2014 the stored source is the only copy.
25916
- Other versions of the same name are unaffected.
25917
- The platform refuses this delete if a trigger rule still references this version \u2014
25918
- directly, or via 'latest' if this is the name's current newest version \u2014 and names the
25919
- offending rule instead of deleting. If no rule references it, the delete proceeds.
25920
- And if this is the name's LATEST version, its identity is REUSED: the next push of that
25921
- name recomputes the same version number and the same id, so anything holding a pinned
25922
- '${opts.id}' or a name@version reference will silently resolve to different source.
25923
-
25924
- `
25925
- );
25926
- if (!opts.force) {
25927
- const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
25928
- if (!isTty) {
25929
- process.stderr.write(
25930
- ` \u2716 scripts delete is irreversible and needs an interactive confirmation.
25931
- Re-run with --force to delete '${opts.id}' without a prompt.
27376
+ async function runAgentsCreate(opts, deps = {}) {
27377
+ if (!AGENT_NAME_RE.test(opts.name)) {
27378
+ process.stderr.write(
27379
+ ` \u2716 '${opts.name}' is not a valid agent name \u2014 use 1-64 characters of letters, digits, underscores ('_') and hyphens ('-').
25932
27380
  `
25933
- );
25934
- return 2;
25935
- }
25936
- const promptCtx = deps.createPrompt ? deps.createPrompt() : (() => {
25937
- const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
25938
- return { prompt: (q) => rl.question(q), close: () => rl.close() };
25939
- })();
27381
+ );
27382
+ return 2;
27383
+ }
27384
+ const read = await readDefinition(opts.file, opts.name);
27385
+ if ("error" in read) {
27386
+ process.stderr.write(` \u2716 ${read.error}
27387
+ `);
27388
+ return 2;
27389
+ }
27390
+ return withAuth(opts, deps, async (auth) => {
27391
+ const client = auth.client;
25940
27392
  try {
25941
- const typed = await promptCtx.prompt(` Type the version id ('${opts.id}') to confirm: `);
25942
- if (typed.trim() !== opts.id) {
25943
- process.stderr.write(` \u2716 Confirmation did not match \u2014 nothing deleted.
27393
+ const agent = await client.agents.createAgent({
27394
+ contextId: opts.context,
27395
+ body: { ...read.definition, name: opts.name }
27396
+ });
27397
+ if (opts.json) {
27398
+ process.stdout.write(`${formatJson(agent)}
25944
27399
  `);
25945
- return 1;
27400
+ return 0;
25946
27401
  }
25947
- } finally {
25948
- promptCtx.close();
27402
+ if (agent.created === false) {
27403
+ process.stdout.write(
27404
+ `
27405
+ = agent '${opts.name}' already exists at version ${oneLine(String(agent.version ?? agent.latestVersion))} \u2014 nothing was written.
27406
+ To change it, write a new version: vectros agents version ${oneLine(agent.id)} --context ${opts.context} --file <definition>
27407
+ `
27408
+ );
27409
+ return 0;
27410
+ }
27411
+ process.stdout.write(`
27412
+ \u2714 created agent '${opts.name}' at version ${oneLine(String(agent.version ?? 1))} in '${opts.context}'
27413
+ ${renderAgent(agent)}
27414
+ `);
27415
+ return 0;
27416
+ } catch (err) {
27417
+ process.stderr.write(
27418
+ formatCliError(sanitizedAgentError(err), { action: `agents create '${opts.name}' failed` }) + withHint(agentErrorHint(err, opts.context, "'agents:c'"))
27419
+ );
27420
+ return 1;
25949
27421
  }
27422
+ });
27423
+ }
27424
+ async function runAgentsVersion(opts, deps = {}) {
27425
+ const read = await readDefinition(opts.file);
27426
+ if ("error" in read) {
27427
+ process.stderr.write(` \u2716 ${read.error}
27428
+ `);
27429
+ return 2;
25950
27430
  }
25951
27431
  return withAuth(opts, deps, async (auth) => {
25952
27432
  const client = auth.client;
25953
27433
  try {
25954
- await client.scripts.deleteScript({ id: opts.id });
27434
+ const agent = await client.agents.updateAgent({
27435
+ id: opts.id,
27436
+ contextId: opts.context,
27437
+ ...opts.restore ? { restore: true } : {},
27438
+ body: {
27439
+ ...read.definition,
27440
+ ...read.declaredName !== void 0 ? { name: read.declaredName } : {},
27441
+ ...opts.expectedVersion !== void 0 ? { expectedVersion: opts.expectedVersion } : {}
27442
+ }
27443
+ });
25955
27444
  if (opts.json) {
25956
- process.stdout.write(`${formatJson({ id: opts.id, deleted: true })}
27445
+ process.stdout.write(`${formatJson(agent)}
25957
27446
  `);
25958
27447
  return 0;
25959
27448
  }
25960
- process.stdout.write(` \u2714 Deleted script version '${opts.id}' from '${opts.context}'.
25961
- `);
27449
+ process.stdout.write(
27450
+ `
27451
+ \u2714 wrote version ${oneLine(String(agent.version ?? agent.latestVersion))} of agent '${oneLine(agent.name ?? opts.id)}' in '${opts.context}'
27452
+ ${renderAgent(agent)}
27453
+ `
27454
+ );
25962
27455
  return 0;
25963
27456
  } catch (err) {
25964
- process.stderr.write(formatCliError(err, { action: "scripts delete failed" }));
27457
+ process.stderr.write(
27458
+ formatCliError(sanitizedAgentError(err), { action: `agents version '${opts.id}' failed` }) + withHint(agentErrorHint(err, opts.context, "'agents:u'"))
27459
+ );
25965
27460
  return 1;
25966
27461
  }
25967
27462
  });
25968
27463
  }
25969
- function errText(err) {
25970
- return err instanceof Error ? err.message : String(err);
25971
- }
25972
27464
 
25973
27465
  // src/commands/assume.ts
25974
27466
  function parseAssumeScopes(raw) {
@@ -26230,8 +27722,10 @@ function withTimeout(p, ms) {
26230
27722
 
26231
27723
  // src/commands/blueprint-test.ts
26232
27724
  import { randomUUID as randomUUID2 } from "crypto";
27725
+ import { writeSync } from "fs";
26233
27726
 
26234
27727
  // src/harness.ts
27728
+ import { createHash as createHash2 } from "crypto";
26235
27729
  var errMsg = (e) => e instanceof Error ? e.message : String(e);
26236
27730
  var isNotFound2 = (e) => typeof e === "object" && e !== null && e.statusCode === 404;
26237
27731
  var SNAPSHOT_PAGE_SIZE = 100;
@@ -26438,7 +27932,7 @@ async function runAssertions(client, bootstrapClient, deps, blueprint, tenantId,
26438
27932
  }
26439
27933
  return assertions;
26440
27934
  }
26441
- async function runTeardown(client, bootstrapClient, blueprint, machineId, snapshot, result) {
27935
+ async function runTeardown(client, bootstrapClient, blueprint, machineId, snapshot, result, runAgentNames, createdAgentIds, seenAgentNames, attemptedAgentNames, refusedAgentNames) {
26442
27936
  const steps = [];
26443
27937
  const keyName = result?.keyName ?? keyNameFor(blueprint.name, machineId);
26444
27938
  try {
@@ -26456,6 +27950,7 @@ async function runTeardown(client, bootstrapClient, blueprint, machineId, snapsh
26456
27950
  } catch (e) {
26457
27951
  steps.push({ step: `revoke key ${keyName}`, ok: false, error: errMsg(e) });
26458
27952
  }
27953
+ steps.push(...await teardownAgents(client, blueprint, runAgentNames, createdAgentIds, seenAgentNames, attemptedAgentNames, refusedAgentNames, result));
26459
27954
  if (!result) return steps;
26460
27955
  for (const id of result.seededRecordIds) {
26461
27956
  try {
@@ -26649,19 +28144,247 @@ async function runTeardown(client, bootstrapClient, blueprint, machineId, snapsh
26649
28144
  }
26650
28145
  return steps;
26651
28146
  }
28147
+ var AGENT_RUN_SUFFIX_LENGTH = 10;
28148
+ var MAX_TESTABLE_AGENT_NAME_LENGTH = 64 - AGENT_RUN_SUFFIX_LENGTH;
28149
+ function agentRunSuffix(machineId) {
28150
+ return `-t${createHash2("sha256").update(machineId).digest("hex").slice(0, 8)}`;
28151
+ }
28152
+ function agentNamesProblem(blueprint) {
28153
+ const tooLong = Object.keys(blueprint.agents ?? {}).filter((name) => name.length > MAX_TESTABLE_AGENT_NAME_LENGTH);
28154
+ if (tooLong.length === 0) return void 0;
28155
+ 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(", ")}.`;
28156
+ }
28157
+ function scopeAgentsToRun(blueprint, machineId) {
28158
+ const declared = Object.entries(blueprint.agents ?? {});
28159
+ if (declared.length === 0) return { blueprint, names: {} };
28160
+ const problem = agentNamesProblem(blueprint);
28161
+ if (problem) throw new Error(problem);
28162
+ const suffix = agentRunSuffix(machineId);
28163
+ const names = /* @__PURE__ */ Object.create(null);
28164
+ const agents = /* @__PURE__ */ Object.create(null);
28165
+ for (const [name, definition] of declared) {
28166
+ names[name] = `${name}${suffix}`;
28167
+ agents[names[name]] = definition;
28168
+ }
28169
+ return { blueprint: { ...blueprint, agents }, names };
28170
+ }
28171
+ function isAgentRemovalFailure(step) {
28172
+ return !step.ok && /^(retire|purge) agent /.test(step.step);
28173
+ }
28174
+ async function teardownAgents(client, blueprint, runNames, createdIds, seenAgentNames, attemptedAgentNames, refusedAgentNames, result) {
28175
+ const steps = [];
28176
+ if (runNames.size === 0) return steps;
28177
+ const found = new Map(createdIds);
28178
+ const notOurs = new Set(seenAgentNames);
28179
+ for (const a of result?.agentActions ?? []) {
28180
+ if (!runNames.has(a.name)) continue;
28181
+ if (a.action === "created") found.set(a.name, a.id);
28182
+ else {
28183
+ notOurs.add(a.name);
28184
+ steps.push({ step: `retire agent ${a.name}`, ok: true, skipped: true, error: `skipped \u2014 this run did not create it (${a.action})` });
28185
+ }
28186
+ }
28187
+ if (!result) {
28188
+ for (const name of [...seenAgentNames].sort()) {
28189
+ steps.push({ step: `retire agent ${name}`, ok: true, skipped: true, error: "skipped \u2014 it existed before this run created anything" });
28190
+ }
28191
+ }
28192
+ const lost = new Set(
28193
+ [...attemptedAgentNames].filter((n) => runNames.has(n) && !found.has(n) && !notOurs.has(n) && !refusedAgentNames.has(n))
28194
+ );
28195
+ if (lost.size > 0 && !result) {
28196
+ let listed = false;
28197
+ try {
28198
+ let startFrom;
28199
+ for (; ; ) {
28200
+ const page = await client.agents.listAgents({ contextId: blueprint.contextId, limit: SNAPSHOT_PAGE_SIZE, startFrom });
28201
+ for (const row of page.data ?? []) {
28202
+ if (!row.name || !row.id || !lost.has(row.name)) continue;
28203
+ if (row.provisionedBy === blueprint.name || row.provisionedBy === void 0) found.set(row.name, row.id);
28204
+ }
28205
+ if (!page.nextCursor) break;
28206
+ startFrom = page.nextCursor;
28207
+ }
28208
+ listed = true;
28209
+ } catch (e) {
28210
+ if (isNotFound2(e)) {
28211
+ steps.push({ step: "list agents", ok: true, skipped: true, error: "skipped \u2014 the context does not exist, so nothing was created" });
28212
+ } else {
28213
+ steps.push({
28214
+ step: "list agents",
28215
+ ok: false,
28216
+ error: `could not list agents, so an agent created before a failure may remain: ${errMsg(e)}`
28217
+ });
28218
+ }
28219
+ }
28220
+ if (listed) {
28221
+ for (const name of [...lost].filter((n) => !found.has(n)).sort()) {
28222
+ steps.push({
28223
+ step: `check agent ${name}`,
28224
+ ok: true,
28225
+ skipped: true,
28226
+ 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`"
28227
+ });
28228
+ }
28229
+ }
28230
+ }
28231
+ for (const [name, id] of [...found].sort(([a], [b]) => a.localeCompare(b))) {
28232
+ try {
28233
+ await client.agents.deleteAgent({ id, contextId: blueprint.contextId });
28234
+ steps.push({ step: `retire agent ${name}`, ok: true });
28235
+ } catch (e) {
28236
+ steps.push({
28237
+ step: `retire agent ${name}`,
28238
+ ok: false,
28239
+ error: `the agent is still active and can be run; retire it with DELETE /v1/agents/${id} (needs agents:d): ${errMsg(e)}`
28240
+ });
28241
+ continue;
28242
+ }
28243
+ try {
28244
+ await client.agents.deleteAgent({ id, contextId: blueprint.contextId, purge: true });
28245
+ steps.push({ step: `purge agent ${name}`, ok: true });
28246
+ } catch (e) {
28247
+ 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)}` });
28248
+ }
28249
+ }
28250
+ return steps;
28251
+ }
28252
+ function onlyAttempted(names, attempted) {
28253
+ const kept = Object.entries(names).filter(([, runName]) => attempted.has(runName));
28254
+ return kept.length > 0 ? Object.fromEntries(kept) : void 0;
28255
+ }
28256
+ var HarnessInterrupted = class extends Error {
28257
+ constructor(signal) {
28258
+ super(`interrupted by ${signal}`);
28259
+ this.signal = signal;
28260
+ }
28261
+ signal;
28262
+ };
28263
+ function watchForInterrupt(deps, run) {
28264
+ const source = deps.signals ?? process;
28265
+ const say = (text) => {
28266
+ try {
28267
+ (deps.notify ?? (() => {
28268
+ }))(text);
28269
+ } catch {
28270
+ }
28271
+ };
28272
+ const forceExit = deps.forceExit ?? ((code) => process.exit(code));
28273
+ let first;
28274
+ let toreDown = false;
28275
+ let standing = () => ({ created: /* @__PURE__ */ new Map(), inFlight: [] });
28276
+ const heard = (signal) => {
28277
+ if (first !== void 0) {
28278
+ try {
28279
+ const { created, inFlight } = standing();
28280
+ const lines = [...created].map(([name, rawId]) => {
28281
+ try {
28282
+ const id = singleLine(String(rawId), 200);
28283
+ return ` ${name}: retire it with DELETE /v1/agents/${id} (needs agents:d), then erase it with DELETE /v1/agents/${id}?purge=true
28284
+ `;
28285
+ } catch {
28286
+ return ` ${name}: its id could not be shown; find it with \`vectros agents list --context ${run.contextId} --include-retired\`
28287
+ `;
28288
+ }
28289
+ });
28290
+ const pending = inFlight.map((name) => ` ${name}: a create was sent and not answered; find it with \`vectros agents list --context ${run.contextId} --include-retired\`
28291
+ `);
28292
+ say(
28293
+ `
28294
+ \u2716 ${signal} again: leaving before teardown finished.
28295
+ ` + (lines.length > 0 ? ` Agents this run created, which may still be active:
28296
+ ${lines.join("")}` : "") + (pending.length > 0 ? ` Creates that may have gone through:
28297
+ ${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}').
28298
+ `
28299
+ );
28300
+ } catch {
28301
+ }
28302
+ forceExit(first === "SIGTERM" || signal === "SIGTERM" ? 143 : 130);
28303
+ return;
28304
+ }
28305
+ first = signal;
28306
+ say(
28307
+ run.keep ? `
28308
+ \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.
28309
+ ` : toreDown ? `
28310
+ \u26A0 ${signal}: teardown is already running and will finish. Send it again to leave now.
28311
+ ` : `
28312
+ \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.
28313
+ `
28314
+ );
28315
+ };
28316
+ const onInt = () => heard("SIGINT");
28317
+ const onTerm = () => heard("SIGTERM");
28318
+ source.on("SIGINT", onInt);
28319
+ source.on("SIGTERM", onTerm);
28320
+ return {
28321
+ get signal() {
28322
+ return first;
28323
+ },
28324
+ throwIfInterrupted() {
28325
+ if (first !== void 0) throw new HarnessInterrupted(first);
28326
+ },
28327
+ setAgentStanding(fn) {
28328
+ standing = fn;
28329
+ },
28330
+ tearingDown() {
28331
+ toreDown = true;
28332
+ },
28333
+ dispose() {
28334
+ source.off("SIGINT", onInt);
28335
+ source.off("SIGTERM", onTerm);
28336
+ }
28337
+ };
28338
+ }
26652
28339
  async function runBlueprintHarness(client, input, deps) {
28340
+ const interrupt = watchForInterrupt(deps, {
28341
+ keep: input.keep ?? false,
28342
+ contextId: input.blueprint.contextId,
28343
+ keyName: keyNameFor(input.blueprint.name, input.machineId)
28344
+ });
28345
+ try {
28346
+ return await runHarness(client, input, deps, interrupt);
28347
+ } catch (err) {
28348
+ if (interrupt.signal !== void 0) {
28349
+ let carried = err;
28350
+ try {
28351
+ carried = Object.assign(err !== null && typeof err === "object" ? err : new Error(String(err)), { interruptedBy: interrupt.signal });
28352
+ } catch {
28353
+ const shape = err;
28354
+ let message = "unknown error";
28355
+ try {
28356
+ message = typeof shape?.message === "string" ? shape.message : extractServerError(err).message;
28357
+ } catch {
28358
+ }
28359
+ carried = Object.assign(new Error(message, { cause: err }), {
28360
+ interruptedBy: interrupt.signal,
28361
+ ...shape && typeof shape === "object" && "statusCode" in shape ? { statusCode: shape.statusCode } : {},
28362
+ ...shape && typeof shape === "object" && "body" in shape ? { body: shape.body } : {}
28363
+ });
28364
+ }
28365
+ throw carried;
28366
+ }
28367
+ throw err;
28368
+ } finally {
28369
+ interrupt.dispose();
28370
+ }
28371
+ }
28372
+ async function runHarness(client, input, deps, interrupt) {
26653
28373
  const { tenantId, machineId, keep = false, log, remintForContext, remintBootstrapForContext } = input;
28374
+ const scoped = scopeAgentsToRun(input.blueprint, machineId);
28375
+ const runBlueprintInput = scoped.blueprint;
28376
+ const agentNames = Object.keys(scoped.names).length > 0 ? scoped.names : void 0;
26654
28377
  const readExistingKey = async () => void 0;
26655
28378
  const persistMintedKey = async () => {
26656
28379
  };
26657
28380
  const provisioned = await provisionContext(client, {
26658
- blueprint: input.blueprint,
28381
+ blueprint: runBlueprintInput,
26659
28382
  remintBootstrapForContext,
26660
28383
  readExistingKey,
26661
28384
  log
26662
28385
  });
26663
28386
  const blueprint = await resolveApplyTimeIdentities(
26664
- input.blueprint,
28387
+ runBlueprintInput,
26665
28388
  provisioned,
26666
28389
  client,
26667
28390
  log
@@ -26704,10 +28427,19 @@ async function runBlueprintHarness(client, input, deps) {
26704
28427
  existingIssuerIds: issuerSnapshot.existingIssuerIds,
26705
28428
  failed: contextSnapshot.failed || issuerSnapshot.failed
26706
28429
  };
28430
+ const createdAgentIds = /* @__PURE__ */ new Map();
28431
+ const seenAgentNames = /* @__PURE__ */ new Set();
28432
+ const attemptedAgentNames = /* @__PURE__ */ new Set();
28433
+ const refusedAgentNames = /* @__PURE__ */ new Set();
26707
28434
  let result;
26708
28435
  let assertions = [];
26709
28436
  let applyError;
26710
- try {
28437
+ interrupt.setAgentStanding(() => ({
28438
+ created: createdAgentIds,
28439
+ inFlight: [...attemptedAgentNames].filter((n) => !createdAgentIds.has(n) && !seenAgentNames.has(n) && !refusedAgentNames.has(n))
28440
+ }));
28441
+ const applyAndAssert = (async () => {
28442
+ interrupt.throwIfInterrupted();
26711
28443
  result = await runLoader(client, {
26712
28444
  blueprint,
26713
28445
  tenantId,
@@ -26718,6 +28450,13 @@ async function runBlueprintHarness(client, input, deps) {
26718
28450
  provisioned,
26719
28451
  remintForContext,
26720
28452
  remintBootstrapForContext,
28453
+ onAgentCreated: ({ name, id }) => createdAgentIds.set(name, id),
28454
+ onAgentSeen: ({ name }) => seenAgentNames.add(name),
28455
+ onAgentAttempt: (name) => {
28456
+ if (interrupt.signal !== void 0) return false;
28457
+ attemptedAgentNames.add(name);
28458
+ },
28459
+ onAgentRefused: (name) => refusedAgentNames.add(name),
26721
28460
  // CREATED-ONLY, and this is the seam that keeps it true for triggers. The loader's step 5d
26722
28461
  // reconciles triggers by ABSENCE — it deletes rules this blueprint provisioned and no longer
26723
28462
  // declares. That is right for a real bootstrap run, which owns its context; it is wrong here.
@@ -26735,6 +28474,7 @@ async function runBlueprintHarness(client, input, deps) {
26735
28474
  // fresh, collision-free name, exactly as the harness already expects for schemas/roles/issuers.
26736
28475
  });
26737
28476
  log.debug({ component: "harness", contextId: result.contextId, keyName: result.keyName }, "applied");
28477
+ interrupt.throwIfInterrupted();
26738
28478
  assertions = await runAssertions(
26739
28479
  await remint(blueprint.contextId),
26740
28480
  hasIssuers || hasNamespaces ? await remintBootstrap(blueprint.contextId) : client,
@@ -26743,10 +28483,12 @@ async function runBlueprintHarness(client, input, deps) {
26743
28483
  tenantId,
26744
28484
  result
26745
28485
  );
26746
- } catch (e) {
28486
+ })().catch((e) => {
26747
28487
  applyError = errMsg(e);
26748
28488
  log.warn({ component: "harness", err: applyError }, "apply phase failed");
26749
- }
28489
+ });
28490
+ await applyAndAssert;
28491
+ interrupt.tearingDown();
26750
28492
  let teardown = [];
26751
28493
  if (!keep) {
26752
28494
  let tdClient = client;
@@ -26761,9 +28503,22 @@ async function runBlueprintHarness(client, input, deps) {
26761
28503
  } catch {
26762
28504
  }
26763
28505
  }
26764
- teardown = await runTeardown(tdClient, tdBootstrapClient, blueprint, machineId, snapshot, result);
28506
+ teardown = await runTeardown(
28507
+ tdClient,
28508
+ tdBootstrapClient,
28509
+ blueprint,
28510
+ machineId,
28511
+ snapshot,
28512
+ result,
28513
+ new Set(Object.values(scoped.names)),
28514
+ createdAgentIds,
28515
+ seenAgentNames,
28516
+ attemptedAgentNames,
28517
+ refusedAgentNames
28518
+ );
26765
28519
  }
26766
- const passed = applyError === void 0 && assertions.length > 0 && assertions.every((a) => a.ok);
28520
+ const shownAgentNames = agentNames && interrupt.signal ? onlyAttempted(agentNames, attemptedAgentNames) : agentNames;
28521
+ const passed = applyError === void 0 && assertions.length > 0 && assertions.every((a) => a.ok) && !teardown.some(isAgentRemovalFailure) && interrupt.signal === void 0;
26767
28522
  return {
26768
28523
  passed,
26769
28524
  applied: result ? {
@@ -26777,7 +28532,9 @@ async function runBlueprintHarness(client, input, deps) {
26777
28532
  assertions,
26778
28533
  teardown,
26779
28534
  kept: keep,
26780
- error: applyError
28535
+ error: applyError,
28536
+ ...shownAgentNames ? { agentNames: shownAgentNames } : {},
28537
+ ...interrupt.signal ? { interrupted: interrupt.signal } : {}
26781
28538
  };
26782
28539
  }
26783
28540
 
@@ -26791,6 +28548,47 @@ async function resolveTestBlueprint(fileOrName) {
26791
28548
  }
26792
28549
  return validateBlueprint(raw);
26793
28550
  }
28551
+ function noticeIoForFd(fd, maxWaitMs = 2e3) {
28552
+ return {
28553
+ write: (bytes, offset) => writeSync(fd, bytes, offset),
28554
+ sleep: (ms) => void Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms),
28555
+ maxWaitMs
28556
+ };
28557
+ }
28558
+ var stderrIo = noticeIoForFd(2);
28559
+ function writeNotice(text, io = stderrIo) {
28560
+ const bytes = Buffer.from(text);
28561
+ const giveUp = () => {
28562
+ try {
28563
+ io.write(Buffer.from("\n [notice truncated]\n"), 0);
28564
+ } catch {
28565
+ }
28566
+ };
28567
+ let done = 0;
28568
+ let waited = 0;
28569
+ while (done < bytes.length) {
28570
+ try {
28571
+ const wrote = io.write(bytes, done);
28572
+ if (wrote > 0) {
28573
+ done += wrote;
28574
+ continue;
28575
+ }
28576
+ if (waited >= io.maxWaitMs) return giveUp();
28577
+ io.sleep(10);
28578
+ waited += 10;
28579
+ } catch (err) {
28580
+ if (err.code !== "EAGAIN") return;
28581
+ if (waited >= io.maxWaitMs) return giveUp();
28582
+ io.sleep(10);
28583
+ waited += 10;
28584
+ }
28585
+ }
28586
+ }
28587
+ function harnessExitCode(report) {
28588
+ if (report.interrupted === "SIGINT") return 130;
28589
+ if (report.interrupted === "SIGTERM") return 143;
28590
+ return report.passed ? 0 : 1;
28591
+ }
26794
28592
  function renderReport(blueprint, env, report) {
26795
28593
  const lines = [];
26796
28594
  lines.push("");
@@ -26801,12 +28599,16 @@ function renderReport(blueprint, env, report) {
26801
28599
  ` \u2714 applied \u2014 key ${a.keyName} (${a.keyAction}), ${Object.keys(a.schemaIds).length} schema(s)` + (a.seededCount ? `, ${a.seededCount} seed(s)` : "")
26802
28600
  );
26803
28601
  } else {
26804
- lines.push(` \u2716 apply failed \u2014 ${report.error ?? "unknown error"}`);
28602
+ lines.push(` \u2716 apply failed \u2014 ${singleLine(report.error ?? "unknown error", 600)}`);
28603
+ }
28604
+ if (report.agentNames && Object.keys(report.agentNames).length > 0) {
28605
+ lines.push(" Agents ran under names of their own, so a repeat run never meets this run's agents:");
28606
+ for (const [declared, ran] of Object.entries(report.agentNames)) lines.push(` ${declared} \u2192 ${ran}`);
26805
28607
  }
26806
28608
  if (report.assertions.length) {
26807
28609
  lines.push("");
26808
28610
  lines.push(" Assertions:");
26809
- for (const a of report.assertions) lines.push(` ${a.ok ? "\u2714" : "\u2716"} ${a.name}: ${a.detail}`);
28611
+ for (const a of report.assertions) lines.push(` ${a.ok ? "\u2714" : "\u2716"} ${singleLine(a.name, 200)}: ${singleLine(a.detail, 600)}`);
26810
28612
  }
26811
28613
  if (report.kept) {
26812
28614
  lines.push("");
@@ -26816,11 +28618,19 @@ function renderReport(blueprint, env, report) {
26816
28618
  lines.push(" Teardown:");
26817
28619
  for (const t of report.teardown) {
26818
28620
  const mark = !t.ok ? "\u2716" : t.skipped ? "\u21B7" : "\u2714";
26819
- lines.push(` ${mark} ${t.step}${t.error ? ` \u2014 ${t.error}` : ""}`);
28621
+ lines.push(` ${mark} ${singleLine(t.step, 300)}${t.error ? ` \u2014 ${singleLine(t.error, 600)}` : ""}`);
26820
28622
  }
26821
28623
  const tdFail = report.teardown.filter((t) => !t.ok);
26822
28624
  if (tdFail.length) lines.push(` \u26A0 ${tdFail.length} teardown step(s) failed \u2014 manual cleanup may be needed.`);
26823
28625
  }
28626
+ if (report.interrupted) {
28627
+ lines.push(
28628
+ 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.`
28629
+ );
28630
+ }
28631
+ if (report.teardown.some(isAgentRemovalFailure)) {
28632
+ lines.push(" \u2716 An agent could not be retired or purged, so this run fails: it is still active or still holds its content.");
28633
+ }
26824
28634
  lines.push("");
26825
28635
  lines.push(report.passed ? " RESULT: PASS" : " RESULT: FAIL");
26826
28636
  lines.push("");
@@ -26849,6 +28659,13 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
26849
28659
  `);
26850
28660
  return 1;
26851
28661
  }
28662
+ const agentNamesIssue = agentNamesProblem(blueprint);
28663
+ if (agentNamesIssue) {
28664
+ process.stderr.write(` \u2716 ${agentNamesIssue}
28665
+ Nothing was changed.
28666
+ `);
28667
+ return 2;
28668
+ }
26852
28669
  return withAuth({ ...opts, provisioning: true }, deps, async (auth, log) => {
26853
28670
  const machineId = `harness-${Date.now()}-${randomUUID2().slice(0, 8)}`;
26854
28671
  try {
@@ -26890,10 +28707,10 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
26890
28707
  environment: auth.baseUrl
26891
28708
  });
26892
28709
  };
26893
- const report = await runBlueprintHarness(
28710
+ const report = await (deps.runHarness ?? runBlueprintHarness)(
26894
28711
  client,
26895
28712
  { blueprint, tenantId, machineId, keep: opts.keep, log, remintForContext, remintBootstrapForContext },
26896
- { pingWithKey }
28713
+ { pingWithKey, notify: deps.notify ?? ((text) => writeNotice(text)) }
26897
28714
  );
26898
28715
  if (opts.json) {
26899
28716
  const envelope = {
@@ -26907,10 +28724,16 @@ async function runBlueprintTest(fileOrName, opts, deps = {}) {
26907
28724
  process.stdout.write(`${renderReport(blueprint, auth.env, report)}
26908
28725
  `);
26909
28726
  }
26910
- return report.passed ? 0 : 1;
28727
+ return harnessExitCode(report);
26911
28728
  } catch (err) {
26912
- process.stderr.write(formatCliError(err, { action: "blueprint-test failed" }));
26913
- return 1;
28729
+ process.stderr.write(formatCliError(sanitizedAgentError(err), { action: "blueprint-test failed" }));
28730
+ const by = err?.interruptedBy;
28731
+ if (by && opts.json) {
28732
+ const failed = { passed: false, assertions: [], teardown: [], kept: Boolean(opts.keep), error: singleLine(sanitizedAgentError(err).message, 600), interrupted: by };
28733
+ process.stdout.write(`${formatJson({ blueprint: { name: blueprint.name, version: blueprint.version, contextId: blueprint.contextId }, env: auth.env, ...failed })}
28734
+ `);
28735
+ }
28736
+ return by === "SIGINT" ? 130 : by === "SIGTERM" ? 143 : 1;
26914
28737
  }
26915
28738
  });
26916
28739
  }
@@ -27094,9 +28917,9 @@ function emitJoined(opts) {
27094
28917
 
27095
28918
  // src/build-info.ts
27096
28919
  var BUILD_INFO = {
27097
- cli: true ? "0.26.0" : "dev",
27098
- sdk: true ? "0.48.0" : "dev",
27099
- blueprints: true ? "0.20.3" : "dev"
28920
+ cli: true ? "0.27.0" : "dev",
28921
+ sdk: true ? "0.49.0" : "dev",
28922
+ blueprints: true ? "0.21.0" : "dev"
27100
28923
  };
27101
28924
  function formatBuildInfo() {
27102
28925
  return `vectros ${BUILD_INFO.cli} (sdk ${BUILD_INFO.sdk}, blueprints ${BUILD_INFO.blueprints})`;
@@ -27133,6 +28956,17 @@ function parseTenantFlag(raw) {
27133
28956
  }
27134
28957
  return raw;
27135
28958
  }
28959
+ var POSITIVE_INT_INVALID = /* @__PURE__ */ Symbol("parsePositiveInt:invalid");
28960
+ function parsePositiveInt(raw, flag) {
28961
+ if (raw === void 0) return void 0;
28962
+ if (!/^[1-9][0-9]{0,8}$/.test(raw)) {
28963
+ process.stderr.write(` \u2716 ${flag} must be a positive whole number (got '${raw}')
28964
+ `);
28965
+ process.exitCode = 2;
28966
+ return POSITIVE_INT_INVALID;
28967
+ }
28968
+ return Number(raw);
28969
+ }
27136
28970
  function toCommonOptions(flags) {
27137
28971
  return {
27138
28972
  env: flags.env,
@@ -27187,6 +29021,7 @@ function buildProgram(deps = {}) {
27187
29021
  registerDomainsCommands(program, deps);
27188
29022
  registerNamespaceCommands(program, deps);
27189
29023
  registerScriptsCommands(program, deps);
29024
+ registerAgentsCommands(program, deps);
27190
29025
  registerAssumeCommand(program, deps);
27191
29026
  registerJoinCommand(program, deps);
27192
29027
  registerKeyringCommands(program);
@@ -27642,6 +29477,57 @@ function registerScriptsCommands(program, deps) {
27642
29477
  }
27643
29478
  );
27644
29479
  }
29480
+ function registerAgentsCommands(program, deps) {
29481
+ const agents = program.command("agents").description("Manage stored agents (named, versioned model + prompt + tool whitelist definitions).");
29482
+ 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)");
29483
+ addCommonAuthOptions(list).action(
29484
+ async (flags) => {
29485
+ const tenant = parseTenantFlag(flags.tenant);
29486
+ if (tenant === TENANT_FLAG_INVALID) return;
29487
+ process.exitCode = await runAgentsList(
29488
+ { ...toCommonOptions(flags), context: flags.context, includeRetired: Boolean(flags.includeRetired), tenant },
29489
+ deps
29490
+ );
29491
+ }
29492
+ );
29493
+ 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)");
29494
+ addCommonAuthOptions(get).action(
29495
+ async (id, flags) => {
29496
+ const tenant = parseTenantFlag(flags.tenant);
29497
+ if (tenant === TENANT_FLAG_INVALID) return;
29498
+ const version2 = parsePositiveInt(flags.atVersion, "--at-version");
29499
+ if (version2 === POSITIVE_INT_INVALID) return;
29500
+ process.exitCode = await runAgentsGet(
29501
+ { ...toCommonOptions(flags), id, context: flags.context, version: version2, tenant },
29502
+ deps
29503
+ );
29504
+ }
29505
+ );
29506
+ 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)");
29507
+ addCommonAuthOptions(create).action(
29508
+ async (name, flags) => {
29509
+ const tenant = parseTenantFlag(flags.tenant);
29510
+ if (tenant === TENANT_FLAG_INVALID) return;
29511
+ process.exitCode = await runAgentsCreate(
29512
+ { ...toCommonOptions(flags), name, context: flags.context, file: flags.file, tenant },
29513
+ deps
29514
+ );
29515
+ }
29516
+ );
29517
+ 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)");
29518
+ addCommonAuthOptions(version).action(
29519
+ async (id, flags) => {
29520
+ const tenant = parseTenantFlag(flags.tenant);
29521
+ if (tenant === TENANT_FLAG_INVALID) return;
29522
+ const expectedVersion = parsePositiveInt(flags.expectedVersion, "--expected-version");
29523
+ if (expectedVersion === POSITIVE_INT_INVALID) return;
29524
+ process.exitCode = await runAgentsVersion(
29525
+ { ...toCommonOptions(flags), id, context: flags.context, file: flags.file, expectedVersion, restore: Boolean(flags.restore), tenant },
29526
+ deps
29527
+ );
29528
+ }
29529
+ );
29530
+ }
27645
29531
  function registerRoleCommands(program, deps) {
27646
29532
  const role = program.command("role").description("Manage context-scoped roles (reusable scope rules; bind via `access grant --role`).");
27647
29533
  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(
@@ -27967,10 +29853,10 @@ async function main() {
27967
29853
  await buildProgram().parseAsync(process.argv);
27968
29854
  }
27969
29855
  main().catch((err) => {
27970
- process.stderr.write(`fatal: ${err instanceof Error ? err.message : String(err)}
29856
+ process.stderr.write(`fatal: ${errorText(err)}
27971
29857
  `);
27972
29858
  if (process.env.VECTROS_MCP_DEBUG && err instanceof Error && err.stack) {
27973
- process.stderr.write(`${err.stack}
29859
+ process.stderr.write(`${cleanBlock(err.stack)}
27974
29860
  `);
27975
29861
  }
27976
29862
  process.exitCode = 2;