@botiverse/raft-sdk 0.2.0 → 1.0.0-alpha.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/esm/index.js CHANGED
@@ -4439,6 +4439,184 @@ function createAgentApiMessageClient(options) {
4439
4439
  } };
4440
4440
  }
4441
4441
  //#endregion
4442
+ //#region ../shared/src/agentApiRouteMeta.ts
4443
+ const read = {
4444
+ sideEffect: "read",
4445
+ idempotency: "natural",
4446
+ destructive: false,
4447
+ audience: "both"
4448
+ };
4449
+ const naturalWrite = {
4450
+ sideEffect: "write",
4451
+ idempotency: "natural",
4452
+ destructive: false,
4453
+ audience: "both"
4454
+ };
4455
+ const keyedWrite = {
4456
+ sideEffect: "write",
4457
+ idempotency: "key",
4458
+ destructive: false,
4459
+ audience: "both"
4460
+ };
4461
+ const write = {
4462
+ sideEffect: "write",
4463
+ idempotency: "none",
4464
+ destructive: false,
4465
+ audience: "both"
4466
+ };
4467
+ const naturalDestructive = {
4468
+ sideEffect: "write",
4469
+ idempotency: "natural",
4470
+ destructive: true,
4471
+ audience: "both"
4472
+ };
4473
+ const destructive = {
4474
+ sideEffect: "write",
4475
+ idempotency: "none",
4476
+ destructive: true,
4477
+ audience: "both"
4478
+ };
4479
+ const managedNaturalWrite = {
4480
+ sideEffect: "write",
4481
+ idempotency: "natural",
4482
+ destructive: false,
4483
+ audience: "managed"
4484
+ };
4485
+ const managedWrite = {
4486
+ sideEffect: "write",
4487
+ idempotency: "none",
4488
+ destructive: false,
4489
+ audience: "managed"
4490
+ };
4491
+ const managedNaturalDestructive = {
4492
+ sideEffect: "write",
4493
+ idempotency: "natural",
4494
+ destructive: true,
4495
+ audience: "managed"
4496
+ };
4497
+ const AGENT_API_ROUTE_META = {
4498
+ feedbackLocatorIngest: naturalWrite,
4499
+ feedbackLocatorList: read,
4500
+ events: {
4501
+ sideEffect: "destructive_read",
4502
+ idempotency: "none",
4503
+ destructive: true,
4504
+ audience: "both"
4505
+ },
4506
+ historyRead: read,
4507
+ knowledgeGet: read,
4508
+ knowledgeSearch: read,
4509
+ managedMcpTools: read,
4510
+ managedMcpCall: destructive,
4511
+ messageSend: keyedWrite,
4512
+ messageSendV2: keyedWrite,
4513
+ messageResolve: read,
4514
+ messageSearch: read,
4515
+ messageReactionAdd: naturalWrite,
4516
+ messageReactionRemove: naturalDestructive,
4517
+ channelJoin: naturalWrite,
4518
+ channelLeave: naturalDestructive,
4519
+ channelMute: naturalWrite,
4520
+ channelUnmute: naturalWrite,
4521
+ channelArchive: naturalDestructive,
4522
+ channelUnarchive: naturalWrite,
4523
+ channelMembers: read,
4524
+ resolveChannel: read,
4525
+ threadUnfollow: naturalDestructive,
4526
+ threadList: read,
4527
+ inboxList: read,
4528
+ agentContext: read,
4529
+ serverInfo: read,
4530
+ serverUpdate: naturalDestructive,
4531
+ mentionActionsPending: read,
4532
+ senderMentionDeliveries: read,
4533
+ mentionActionsExecute: write,
4534
+ taskClaim: write,
4535
+ taskList: read,
4536
+ taskCreate: write,
4537
+ taskUnclaim: destructive,
4538
+ taskAssign: naturalDestructive,
4539
+ taskUpdateStatus: naturalDestructive,
4540
+ taskResourceReceipt: write,
4541
+ taskDelete: destructive,
4542
+ taskConvert: write,
4543
+ taskAmend: destructive,
4544
+ taskHistory: read,
4545
+ migrationBegin: {
4546
+ sideEffect: "write",
4547
+ idempotency: "none",
4548
+ destructive: true,
4549
+ audience: "managed"
4550
+ },
4551
+ migrationStatus: read,
4552
+ migrationReady: managedNaturalWrite,
4553
+ migrationArrived: managedNaturalWrite,
4554
+ reminderList: read,
4555
+ reminderCreate: managedWrite,
4556
+ reminderCancel: naturalDestructive,
4557
+ reminderSnooze: managedNaturalDestructive,
4558
+ reminderUpdate: managedNaturalDestructive,
4559
+ appSourceAck: naturalWrite,
4560
+ reminderLog: read,
4561
+ appConfigGet: read,
4562
+ appConfigPatch: destructive,
4563
+ profileShow: read,
4564
+ profileUpdate: naturalDestructive,
4565
+ profileAvatarUpdate: naturalDestructive,
4566
+ integrationList: read,
4567
+ integrationMarketplaceSearch: read,
4568
+ integrationLogin: naturalWrite,
4569
+ integrationAppPrepare: write,
4570
+ integrationAppRotateSecret: destructive,
4571
+ integrationAppTransferOwner: naturalDestructive,
4572
+ integrationAppUpdate: naturalDestructive,
4573
+ integrationAppManage: destructive,
4574
+ integrationAppLogoUpdate: naturalDestructive,
4575
+ integrationAppList: read,
4576
+ integrationAppStatus: read,
4577
+ actionPrepare: write,
4578
+ attachmentUpload: write,
4579
+ attachmentUploadCapabilities: read,
4580
+ attachmentUploadSessionCreate: write,
4581
+ attachmentUploadSessionComplete: naturalWrite,
4582
+ attachmentUploadSessionCancel: naturalDestructive,
4583
+ attachmentUploadSessionStatus: read,
4584
+ attachmentDownload: read,
4585
+ attachmentCommentsList: read,
4586
+ pushWebhookStatus: {
4587
+ sideEffect: "read",
4588
+ idempotency: "natural",
4589
+ destructive: false,
4590
+ audience: "external"
4591
+ },
4592
+ pushWebhookRegister: {
4593
+ sideEffect: "write",
4594
+ idempotency: "natural",
4595
+ destructive: true,
4596
+ audience: "external"
4597
+ },
4598
+ pushWebhookDelete: {
4599
+ sideEffect: "write",
4600
+ idempotency: "natural",
4601
+ destructive: true,
4602
+ audience: "external"
4603
+ },
4604
+ mentionsList: read
4605
+ };
4606
+ function toAgentApiToolAnnotations(meta) {
4607
+ return {
4608
+ readOnlyHint: meta.sideEffect === "read",
4609
+ destructiveHint: meta.destructive,
4610
+ idempotentHint: meta.idempotency !== "none"
4611
+ };
4612
+ }
4613
+ function getAgentApiRetryPolicy(meta) {
4614
+ if (meta.sideEffect === "read") return "retry";
4615
+ if (meta.sideEffect === "write" && meta.idempotency === "key") return "retry_when_keyed";
4616
+ if (meta.sideEffect === "write" && meta.idempotency === "natural") return "retry";
4617
+ return "single_attempt";
4618
+ }
4619
+ //#endregion
4442
4620
  //#region ../shared/src/actionCards.ts
4443
4621
  const uuidSchema = uuid();
4444
4622
  /**
@@ -4818,7 +4996,8 @@ const taskStatusSchema = _enum([
4818
4996
  ]);
4819
4997
  const agentApiEventsQuerySchema = passthroughObject({
4820
4998
  since: optionalStringSchema,
4821
- limit: optionalStringSchema
4999
+ limit: optionalStringSchema,
5000
+ ack: _enum(["cursor", "immediate"]).optional()
4822
5001
  });
4823
5002
  const agentApiHistoryQuerySchema = passthroughObject({
4824
5003
  channel: string().trim().min(1),
@@ -5622,15 +5801,18 @@ const agentApiEventsResponseSchema = passthroughObject({
5622
5801
  pending_notice_ids: array(string()).optional(),
5623
5802
  wake_reason: string().nullable().optional(),
5624
5803
  has_more: boolean(),
5625
- inbox_hint: agentApiEventsInboxHintSchema.nullable().optional()
5804
+ inbox_hint: agentApiEventsInboxHintSchema.nullable().optional(),
5805
+ ack_mode: _enum(["cursor", "immediate"]).optional()
5806
+ });
5807
+ const agentApiHistoryConsumptionScopeSchema = object({
5808
+ agent_id: string().uuid(),
5809
+ channel_id: string().uuid(),
5810
+ channel_type: _enum(["dm", "thread"]),
5811
+ target: string().min(1)
5626
5812
  });
5627
5813
  const agentApiHistoryResponseSchema = passthroughObject({
5628
- consumption_scope: object({
5629
- agent_id: string().uuid(),
5630
- channel_id: string().uuid(),
5631
- channel_type: _enum(["dm", "thread"]),
5632
- target: string().min(1)
5633
- }).optional(),
5814
+ target: string().min(1).optional(),
5815
+ consumption_scope: agentApiHistoryConsumptionScopeSchema.optional(),
5634
5816
  messages: array(agentApiMessageEnvelopeSchema),
5635
5817
  has_more: boolean(),
5636
5818
  has_older: boolean(),
@@ -6001,6 +6183,45 @@ const agentApiMigrationArrivedBodySchema = passthroughObject({
6001
6183
  });
6002
6184
  const agentApiMigrationResponseSchema = passthroughObject({ migration: agentApiMigrationSummarySchema });
6003
6185
  const agentApiMigrationStatusResponseSchema = passthroughObject({ migration: agentApiMigrationSummarySchema.nullable() });
6186
+ const agentApiPushWebhookRegisterBodySchema = object({
6187
+ /** Public HTTPS endpoint that receives `raft-agent-inbox-notice.v1` POSTs. */
6188
+ url: string().trim().min(1),
6189
+ /** Receiver-owned HMAC-SHA256 secret; stored encrypted, never returned. */
6190
+ secret: string().min(1).max(512)
6191
+ }).strict();
6192
+ const agentApiPushWebhookStatusResponseSchema = passthroughObject({
6193
+ registered: boolean(),
6194
+ url: nullableStringSchema,
6195
+ enabled: boolean(),
6196
+ disabledReason: _enum([
6197
+ "endpoint_rejected",
6198
+ "credential_revoked",
6199
+ "agent_inactive"
6200
+ ]).nullable(),
6201
+ disabledAt: nullableStringSchema,
6202
+ lastAttemptAt: nullableStringSchema,
6203
+ lastDeliveryAt: nullableStringSchema,
6204
+ lastError: nullableStringSchema,
6205
+ consecutiveFailures: number$1().int().nonnegative(),
6206
+ nextAttemptAt: nullableStringSchema
6207
+ });
6208
+ const agentApiMentionsListQuerySchema = passthroughObject({
6209
+ limit: optionalStringSchema,
6210
+ before_seq: optionalStringSchema
6211
+ });
6212
+ const agentApiMentionsListResponseSchema = passthroughObject({
6213
+ mentions: array(passthroughObject({
6214
+ id: string(),
6215
+ messageId: string(),
6216
+ messageSeq: number$1(),
6217
+ channelId: string(),
6218
+ handleAtSendTime: nullableStringSchema.optional(),
6219
+ source: string(),
6220
+ confidence: unknown().optional(),
6221
+ createdAt: string()
6222
+ })),
6223
+ has_more: boolean()
6224
+ });
6004
6225
  function route(input) {
6005
6226
  return {
6006
6227
  ...input,
@@ -7042,6 +7263,58 @@ const agentApiContract = {
7042
7263
  query: agentApiAttachmentCommentsQuerySchema
7043
7264
  },
7044
7265
  response: { body: agentApiAttachmentCommentsResponseSchema }
7266
+ }),
7267
+ pushWebhookStatus: route({
7268
+ key: "pushWebhookStatus",
7269
+ method: "GET",
7270
+ path: "/push-webhook",
7271
+ client: {
7272
+ resource: "pushWebhook",
7273
+ method: "status"
7274
+ },
7275
+ capability: "read",
7276
+ description: "Read the bound External Agent's inbox push registration and delivery status.",
7277
+ request: {},
7278
+ response: { body: agentApiPushWebhookStatusResponseSchema }
7279
+ }),
7280
+ pushWebhookRegister: route({
7281
+ key: "pushWebhookRegister",
7282
+ method: "PUT",
7283
+ path: "/push-webhook",
7284
+ client: {
7285
+ resource: "pushWebhook",
7286
+ method: "register"
7287
+ },
7288
+ capability: "read",
7289
+ description: "Register or replace the bound External Agent's inbox push webhook (raft-agent-inbox-notice.v1); a new registration replaces the secret and re-enables delivery.",
7290
+ request: { body: agentApiPushWebhookRegisterBodySchema },
7291
+ response: { body: agentApiPushWebhookStatusResponseSchema }
7292
+ }),
7293
+ pushWebhookDelete: route({
7294
+ key: "pushWebhookDelete",
7295
+ method: "DELETE",
7296
+ path: "/push-webhook",
7297
+ client: {
7298
+ resource: "pushWebhook",
7299
+ method: "unregister"
7300
+ },
7301
+ capability: "read",
7302
+ description: "Remove the bound External Agent's inbox push registration.",
7303
+ request: {},
7304
+ response: { kind: "empty" }
7305
+ }),
7306
+ mentionsList: route({
7307
+ key: "mentionsList",
7308
+ method: "GET",
7309
+ path: "/mentions",
7310
+ client: {
7311
+ resource: "mentions",
7312
+ method: "list"
7313
+ },
7314
+ capability: "mentions",
7315
+ description: "List notifiable @mentions of the bound agent, newest first.",
7316
+ request: { query: agentApiMentionsListQuerySchema },
7317
+ response: { body: agentApiMentionsListResponseSchema }
7045
7318
  })
7046
7319
  };
7047
7320
  function getAgentApiResponseKind(response) {
@@ -7049,10 +7322,15 @@ function getAgentApiResponseKind(response) {
7049
7322
  }
7050
7323
  function parseAgentApiResponse(key, value) {
7051
7324
  const response = agentApiContract[key].response;
7052
- if (getAgentApiResponseKind(response) === "binary") {
7325
+ const kind = getAgentApiResponseKind(response);
7326
+ if (kind === "binary") {
7053
7327
  if (!(value instanceof Uint8Array)) throw new TypeError(`Agent API ${key} response did not contain binary bytes`);
7054
7328
  return value;
7055
7329
  }
7330
+ if (kind === "empty") {
7331
+ if (value !== null && value !== void 0) throw new TypeError(`Agent API ${key} response must be empty`);
7332
+ return null;
7333
+ }
7056
7334
  if (!("body" in response)) throw new TypeError(`Agent API ${key} response contract is missing a JSON body schema`);
7057
7335
  return response.body.parse(value);
7058
7336
  }
@@ -7173,6 +7451,18 @@ async function requestAgentApiRawRoute(transport, routeKey, options = {}) {
7173
7451
  data: response.data
7174
7452
  };
7175
7453
  }
7454
+ if (getAgentApiResponseKind(route.response) === "empty") {
7455
+ if (response.data !== null) return failure$3(routeKey, "response_contract_mismatch", `Agent API ${route.key} response must be empty`, {
7456
+ status: response.status,
7457
+ response: response.data
7458
+ });
7459
+ return {
7460
+ ok: true,
7461
+ routeKey,
7462
+ status: response.status,
7463
+ data: null
7464
+ };
7465
+ }
7176
7466
  if (response.data === null) return failure$3(routeKey, "empty_response", `Agent API ${route.key} returned an empty response body`, { status: response.status });
7177
7467
  try {
7178
7468
  return {
@@ -7428,7 +7718,8 @@ async function joinRaftChannelByTarget(client, request) {
7428
7718
  const sequence = number$1().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
7429
7719
  const requestSchema = object({
7430
7720
  since: union([sequence, literal("latest")]).optional(),
7431
- limit: number$1().int().min(1).max(200).optional()
7721
+ limit: number$1().int().min(1).max(200).optional(),
7722
+ ack: literal("cursor").optional()
7432
7723
  }).strict();
7433
7724
  const metadataSchema = object({
7434
7725
  seq: sequence.optional(),
@@ -7462,12 +7753,13 @@ function projectMessage(message) {
7462
7753
  externalMessage: message.external_message
7463
7754
  };
7464
7755
  }
7465
- function failure$2(code, status) {
7756
+ function failure$2(code, status, cursorAck = false) {
7757
+ const ackNote = cursorAck ? "The batch was not acknowledged; receiving again with the same since returns it again" : "Delivery acknowledgement may already have occurred";
7466
7758
  const messages = {
7467
- INVALID_REQUEST: "Receive requires a nonnegative safe integer or latest cursor and an integer limit from 1 to 200.",
7468
- TRANSPORT_ERROR: "Event receive transport failed. Delivery acknowledgement may already have occurred; no retry was attempted.",
7469
- HTTP_ERROR: "Event receive returned an HTTP error. Delivery acknowledgement may already have occurred; no retry was attempted.",
7470
- INVALID_RESPONSE: "Event receive response did not match the SDK contract. Delivery acknowledgement may already have occurred; no retry was attempted."
7759
+ INVALID_REQUEST: "Receive requires a nonnegative safe integer or latest cursor, an integer limit from 1 to 200, and ack omitted or \"cursor\".",
7760
+ TRANSPORT_ERROR: `Event receive transport failed. ${ackNote}; no retry was attempted.`,
7761
+ HTTP_ERROR: `Event receive returned an HTTP error. ${ackNote}; no retry was attempted.`,
7762
+ INVALID_RESPONSE: `Event receive response did not match the SDK contract. ${ackNote}; no retry was attempted.`
7471
7763
  };
7472
7764
  return {
7473
7765
  ok: false,
@@ -7482,11 +7774,13 @@ function failure$2(code, status) {
7482
7774
  async function receiveRaftEvents(client, request = {}) {
7483
7775
  const parsed = requestSchema.safeParse(request);
7484
7776
  if (!parsed.success) return failure$2("INVALID_REQUEST");
7777
+ const cursorAck = parsed.data.ack === "cursor";
7485
7778
  const result = await client.events.get({
7486
7779
  ...parsed.data.since === void 0 ? {} : { since: String(parsed.data.since) },
7487
- ...parsed.data.limit === void 0 ? {} : { limit: String(parsed.data.limit) }
7780
+ ...parsed.data.limit === void 0 ? {} : { limit: String(parsed.data.limit) },
7781
+ ...cursorAck ? { ack: "cursor" } : {}
7488
7782
  });
7489
- if (!result.ok) return failure$2(result.error.kind === "transport" ? "TRANSPORT_ERROR" : result.error.kind === "http" ? "HTTP_ERROR" : "INVALID_RESPONSE", result.status);
7783
+ if (!result.ok) return failure$2(result.error.kind === "transport" ? "TRANSPORT_ERROR" : result.error.kind === "http" ? "HTTP_ERROR" : "INVALID_RESPONSE", result.status, cursorAck);
7490
7784
  try {
7491
7785
  return {
7492
7786
  ok: true,
@@ -7496,206 +7790,2328 @@ async function receiveRaftEvents(client, request = {}) {
7496
7790
  lastSeenSeq: sequence.nullable().parse(result.data.last_seen_seq),
7497
7791
  lastSeenMessageId: result.data.last_seen_msgId,
7498
7792
  hasMore: result.data.has_more,
7499
- replyTarget: result.data.reply_target
7793
+ replyTarget: result.data.reply_target,
7794
+ ackMode: result.data.ack_mode ?? null
7500
7795
  }
7501
7796
  };
7502
7797
  } catch {
7503
- return failure$2("INVALID_RESPONSE", result.status);
7798
+ return failure$2("INVALID_RESPONSE", result.status, cursorAck);
7504
7799
  }
7505
7800
  }
7506
7801
  //#endregion
7507
- //#region src/context.ts
7508
- const projection = object({
7509
- agent: object({
7510
- id: string(),
7511
- name: string(),
7512
- displayName: string().nullable(),
7513
- description: string().nullable(),
7514
- runtime: string(),
7515
- external: boolean()
7516
- }),
7517
- server: object({
7518
- id: string(),
7519
- slug: string(),
7520
- name: string()
7521
- }),
7522
- credential: object({ capabilities: array(string()) }),
7523
- prompt: object({ text: string() }).nullable()
7524
- });
7525
- function failure$1(code, status) {
7526
- const messages = {
7527
- TRANSPORT_ERROR: "Agent context transport failed.",
7528
- HTTP_ERROR: "Agent context returned an HTTP error.",
7529
- INVALID_RESPONSE: "Agent context response did not match the SDK contract."
7530
- };
7531
- return {
7532
- ok: false,
7533
- ...status === void 0 ? {} : { status },
7534
- error: {
7535
- code,
7536
- message: messages[code]
7802
+ //#region ../shared/src/generated/agentApiRoutes.ts
7803
+ const AGENT_API_ROUTE_MANIFEST = [
7804
+ {
7805
+ "key": "feedbackLocatorIngest",
7806
+ "method": "POST",
7807
+ "path": "/feedback-locators",
7808
+ "fullPath": "/internal/agent-api/feedback-locators",
7809
+ "client": {
7810
+ "resource": "feedbackLocators",
7811
+ "method": "create"
7812
+ },
7813
+ "capability": "send",
7814
+ "description": "Validate and atomically index one locator-only feedback artifact.",
7815
+ "sideEffect": "write",
7816
+ "idempotency": "natural",
7817
+ "destructive": false,
7818
+ "audience": "both",
7819
+ "request": {
7820
+ "params": false,
7821
+ "query": false,
7822
+ "body": true
7823
+ },
7824
+ "response": {
7825
+ "kind": "json",
7826
+ "body": true
7537
7827
  }
7538
- };
7539
- }
7540
- /** Internal adapter for `client.agent.context()`. Read-only; safe to retry. */
7541
- async function getRaftContext(client) {
7542
- const result = await client.agent.context();
7543
- if (!result.ok) return failure$1(result.error.kind === "transport" ? "TRANSPORT_ERROR" : result.error.kind === "http" ? "HTTP_ERROR" : "INVALID_RESPONSE", result.status);
7544
- const parsed = projection.safeParse(result.data);
7545
- if (!parsed.success) return failure$1("INVALID_RESPONSE", result.status);
7546
- const { agent, server, credential, prompt } = parsed.data;
7547
- return {
7548
- ok: true,
7549
- status: result.status,
7550
- data: {
7551
- agent: { ...agent },
7552
- server: { ...server },
7553
- capabilities: [...credential.capabilities],
7554
- guide: prompt?.text ?? null
7828
+ },
7829
+ {
7830
+ "key": "feedbackLocatorList",
7831
+ "method": "GET",
7832
+ "path": "/feedback-locators",
7833
+ "fullPath": "/internal/agent-api/feedback-locators",
7834
+ "client": {
7835
+ "resource": "feedbackLocators",
7836
+ "method": "list"
7837
+ },
7838
+ "capability": "read",
7839
+ "description": "Query the locator-only feedback index without returning session or feedback content.",
7840
+ "sideEffect": "read",
7841
+ "idempotency": "natural",
7842
+ "destructive": false,
7843
+ "audience": "both",
7844
+ "request": {
7845
+ "params": false,
7846
+ "query": true,
7847
+ "body": false
7848
+ },
7849
+ "response": {
7850
+ "kind": "json",
7851
+ "body": true
7555
7852
  }
7556
- };
7557
- }
7558
- //#endregion
7559
- //#region src/manage.ts
7560
- const AVATAR_MIME_TYPES = /* @__PURE__ */ new Set([
7561
- "image/jpeg",
7562
- "image/png",
7563
- "image/gif",
7564
- "image/webp"
7565
- ]);
7566
- const AVATAR_MAX_BYTES = 5242880;
7567
- const SERVER_ERROR_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
7568
- const MESSAGES = {
7569
- INVALID_REQUEST: "The request did not match the Agent API contract; nothing was sent.",
7570
- TRANSPORT_ERROR: "The request did not reach the Raft Server.",
7571
- HTTP_ERROR: "The Raft Server rejected the request.",
7572
- INVALID_RESPONSE: "The Raft Server response did not match the Agent API contract."
7573
- };
7574
- function failure(code, status, errorCode) {
7575
- const safeErrorCode = typeof errorCode === "string" && SERVER_ERROR_CODE.test(errorCode) ? errorCode : void 0;
7576
- return {
7577
- ok: false,
7578
- ...status === void 0 ? {} : { status },
7579
- error: {
7580
- code,
7581
- message: MESSAGES[code],
7582
- ...safeErrorCode ? { errorCode: safeErrorCode } : {}
7853
+ },
7854
+ {
7855
+ "key": "events",
7856
+ "method": "GET",
7857
+ "path": "/events",
7858
+ "fullPath": "/internal/agent-api/events",
7859
+ "client": {
7860
+ "resource": "events",
7861
+ "method": "get"
7862
+ },
7863
+ "capability": "read",
7864
+ "description": "Drain pending events for the bound agent credential.",
7865
+ "sideEffect": "destructive_read",
7866
+ "idempotency": "none",
7867
+ "destructive": true,
7868
+ "audience": "both",
7869
+ "request": {
7870
+ "params": false,
7871
+ "query": true,
7872
+ "body": false
7873
+ },
7874
+ "response": {
7875
+ "kind": "json",
7876
+ "body": true
7583
7877
  }
7584
- };
7585
- }
7586
- /** Map a shared-client result to the SDK's body-free result. */
7587
- function toRaftApiResult(result) {
7588
- if (result.ok) return {
7589
- ok: true,
7590
- status: result.status,
7591
- data: result.data
7592
- };
7593
- const { error } = result;
7594
- if (error.kind === "transport") return failure("TRANSPORT_ERROR");
7595
- if (error.kind === "http") return failure("HTTP_ERROR", error.status, error.errorCode);
7596
- return failure(error.reason === "request_contract_mismatch" || error.reason === "missing_path_param" || error.reason === "missing_route" ? "INVALID_REQUEST" : "INVALID_RESPONSE", result.status);
7597
- }
7598
- const AVATAR_PATH = "/internal/agent-api/profile/avatar";
7599
- /** One multipart POST; never retried, because the upload is a write. */
7600
- async function uploadRaftAvatar(transport, upload) {
7601
- if (!upload || !AVATAR_MIME_TYPES.has(upload.mimeType) || typeof upload.filename !== "string" || !upload.filename.trim()) return failure("INVALID_REQUEST");
7602
- const blob = upload.data instanceof Blob ? upload.data : upload.data instanceof Uint8Array ? new Blob([Uint8Array.from(upload.data)], { type: upload.mimeType }) : null;
7603
- if (!blob || blob.size === 0 || blob.size > AVATAR_MAX_BYTES) return failure("INVALID_REQUEST");
7604
- const form = new FormData();
7605
- form.append("avatar", blob, upload.filename);
7606
- await transport.beforeRequest?.({
7607
- method: "POST",
7608
- path: AVATAR_PATH
7609
- });
7610
- const headers = new Headers(transport.headers);
7611
- headers.delete("content-type");
7612
- headers.set("accept", "application/json");
7613
- headers.set("authorization", transport.authorization);
7614
- let response;
7615
- try {
7616
- response = await transport.fetch(`${transport.serverUrl}${AVATAR_PATH}`, {
7617
- method: "POST",
7618
- headers,
7619
- body: form
7620
- });
7621
- } catch {
7622
- return failure("TRANSPORT_ERROR");
7623
- }
7624
- let body = null;
7625
- try {
7626
- body = await response.json();
7627
- } catch {
7628
- body = null;
7629
- }
7630
- if (!response.ok) {
7631
- const errorCode = body && typeof body === "object" ? body.errorCode : void 0;
7632
- return failure("HTTP_ERROR", response.status, typeof errorCode === "string" ? errorCode : null);
7633
- }
7634
- try {
7635
- return {
7636
- ok: true,
7637
- status: response.status,
7638
- data: parseAgentApiResponse("profileAvatarUpdate", body)
7639
- };
7640
- } catch {
7641
- return failure("INVALID_RESPONSE", response.status);
7642
- }
7643
- }
7644
- /** `readApi` may retry; `writeApi` must make exactly one attempt. */
7645
- function createRaftManageClient(readApi, writeApi, avatar) {
7646
- return {
7647
- profile: {
7648
- show: async (request = {}) => toRaftApiResult(await readApi.profile.show(request.target === void 0 ? {} : { target: request.target })),
7649
- update: async (request) => toRaftApiResult(await writeApi.profile.update(request)),
7650
- updateAvatar: (upload) => uploadRaftAvatar(avatar, upload)
7878
+ },
7879
+ {
7880
+ "key": "historyRead",
7881
+ "method": "GET",
7882
+ "path": "/history",
7883
+ "fullPath": "/internal/agent-api/history",
7884
+ "client": {
7885
+ "resource": "history",
7886
+ "method": "read"
7651
7887
  },
7652
- server: { update: async (request) => toRaftApiResult(await writeApi.server.update(request)) },
7653
- actions: { prepare: async (request) => toRaftApiResult(await writeApi.actions.prepare(request)) },
7654
- apps: {
7655
- getConfig: async (appId) => toRaftApiResult(await readApi.apps.getConfig({ appId })),
7656
- patchConfig: async (appId, patch) => toRaftApiResult(await writeApi.apps.patchConfig({ appId }, patch))
7888
+ "capability": "read",
7889
+ "description": "Read visible history for a channel, DM, or thread target.",
7890
+ "sideEffect": "read",
7891
+ "idempotency": "natural",
7892
+ "destructive": false,
7893
+ "audience": "both",
7894
+ "request": {
7895
+ "params": false,
7896
+ "query": true,
7897
+ "body": false
7898
+ },
7899
+ "response": {
7900
+ "kind": "json",
7901
+ "body": true
7657
7902
  }
7658
- };
7659
- }
7660
- //#endregion
7661
- //#region src/client.ts
7662
- var RaftSdkConfigurationError = class extends Error {
7663
- code;
7664
- name = "RaftSdkConfigurationError";
7665
- constructor(code, message) {
7666
- super(message);
7667
- this.code = code;
7668
- }
7669
- };
7670
- function requireServerUrl(value) {
7671
- const serverUrl = value.trim();
7672
- if (!serverUrl) throw new RaftSdkConfigurationError("MISSING_SERVER_URL", "Raft Server URL is required");
7673
- let parsed;
7674
- try {
7675
- parsed = new URL(serverUrl);
7676
- } catch {
7677
- throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must be an absolute HTTP(S) URL");
7678
- }
7679
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must use HTTP or HTTPS");
7680
- if (parsed.username || parsed.password) throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must not contain credentials");
7681
- return serverUrl.replace(/\/+$/, "");
7682
- }
7683
- function requireAgentCredential(value) {
7684
- const credential = value.trim();
7685
- if (!credential) throw new RaftSdkConfigurationError("MISSING_AGENT_CREDENTIAL", "Raft Agent credential is required");
7686
- if (!credential.startsWith("sk_agent_") || credential.length === 9) throw new RaftSdkConfigurationError("INVALID_AGENT_CREDENTIAL", "Raft Agent credential must be a long-lived External Agent credential");
7687
- return credential;
7688
- }
7689
- /**
7690
- * Create a typed client for the credential-derived `/internal/agent-api/*`
7691
- * surface. The SDK does not read CLI profiles, environment variables, or the
7692
- * host user's home directory: the caller chooses and supplies the credential.
7693
- */
7694
- function createRaftClient(options) {
7695
- const serverUrl = requireServerUrl(options.serverUrl);
7696
- const auth = { authorization: `Bearer ${requireAgentCredential(options.credential)}` };
7697
- const messageClient = createAgentApiMessageClient({
7698
- baseUrl: serverUrl,
7903
+ },
7904
+ {
7905
+ "key": "knowledgeGet",
7906
+ "method": "GET",
7907
+ "path": "/knowledge",
7908
+ "fullPath": "/internal/agent-api/knowledge",
7909
+ "client": {
7910
+ "resource": "knowledge",
7911
+ "method": "get"
7912
+ },
7913
+ "capability": "knowledge",
7914
+ "description": "Fetch a Slock Manual for Agents topic from the current server.",
7915
+ "sideEffect": "read",
7916
+ "idempotency": "natural",
7917
+ "destructive": false,
7918
+ "audience": "both",
7919
+ "request": {
7920
+ "params": false,
7921
+ "query": true,
7922
+ "body": false
7923
+ },
7924
+ "response": {
7925
+ "kind": "json",
7926
+ "body": true
7927
+ }
7928
+ },
7929
+ {
7930
+ "key": "knowledgeSearch",
7931
+ "method": "GET",
7932
+ "path": "/knowledge/search",
7933
+ "fullPath": "/internal/agent-api/knowledge/search",
7934
+ "client": {
7935
+ "resource": "knowledge",
7936
+ "method": "search"
7937
+ },
7938
+ "capability": "knowledge",
7939
+ "description": "Search Slock Manual for Agents topics from the current server.",
7940
+ "sideEffect": "read",
7941
+ "idempotency": "natural",
7942
+ "destructive": false,
7943
+ "audience": "both",
7944
+ "request": {
7945
+ "params": false,
7946
+ "query": true,
7947
+ "body": false
7948
+ },
7949
+ "response": {
7950
+ "kind": "json",
7951
+ "body": true
7952
+ }
7953
+ },
7954
+ {
7955
+ "key": "managedMcpTools",
7956
+ "method": "GET",
7957
+ "path": "/mcp/tools",
7958
+ "fullPath": "/internal/agent-api/mcp/tools",
7959
+ "client": {
7960
+ "resource": "mcp",
7961
+ "method": "tools"
7962
+ },
7963
+ "capability": "mcp",
7964
+ "description": "Fetch the current Server-authorized managed MCP tool catalog for this Agent.",
7965
+ "sideEffect": "read",
7966
+ "idempotency": "natural",
7967
+ "destructive": false,
7968
+ "audience": "both",
7969
+ "request": {
7970
+ "params": false,
7971
+ "query": false,
7972
+ "body": false
7973
+ },
7974
+ "response": {
7975
+ "kind": "json",
7976
+ "body": true
7977
+ }
7978
+ },
7979
+ {
7980
+ "key": "managedMcpCall",
7981
+ "method": "POST",
7982
+ "path": "/mcp/call",
7983
+ "fullPath": "/internal/agent-api/mcp/call",
7984
+ "client": {
7985
+ "resource": "mcp",
7986
+ "method": "call"
7987
+ },
7988
+ "capability": "mcp",
7989
+ "description": "Call an allowlisted managed MCP tool through the Server gateway.",
7990
+ "sideEffect": "write",
7991
+ "idempotency": "none",
7992
+ "destructive": true,
7993
+ "audience": "both",
7994
+ "request": {
7995
+ "params": false,
7996
+ "query": false,
7997
+ "body": true
7998
+ },
7999
+ "response": {
8000
+ "kind": "json",
8001
+ "body": true
8002
+ }
8003
+ },
8004
+ {
8005
+ "key": "messageSend",
8006
+ "method": "POST",
8007
+ "path": "/send",
8008
+ "fullPath": "/internal/agent-api/send",
8009
+ "client": {
8010
+ "resource": "messages",
8011
+ "method": "send"
8012
+ },
8013
+ "capability": "send",
8014
+ "description": "Send a message as the bound agent credential.",
8015
+ "sideEffect": "write",
8016
+ "idempotency": "key",
8017
+ "destructive": false,
8018
+ "audience": "both",
8019
+ "request": {
8020
+ "params": false,
8021
+ "query": false,
8022
+ "body": true
8023
+ },
8024
+ "response": {
8025
+ "kind": "json",
8026
+ "body": true
8027
+ }
8028
+ },
8029
+ {
8030
+ "key": "messageSendV2",
8031
+ "method": "POST",
8032
+ "path": "/v2/send",
8033
+ "fullPath": "/internal/agent-api/v2/send",
8034
+ "client": {
8035
+ "resource": "messages",
8036
+ "method": "sendV2"
8037
+ },
8038
+ "capability": "send",
8039
+ "description": "Send a message through the versioned typed-mention contract.",
8040
+ "sideEffect": "write",
8041
+ "idempotency": "key",
8042
+ "destructive": false,
8043
+ "audience": "both",
8044
+ "request": {
8045
+ "params": false,
8046
+ "query": false,
8047
+ "body": true
8048
+ },
8049
+ "response": {
8050
+ "kind": "json",
8051
+ "body": true
8052
+ }
8053
+ },
8054
+ {
8055
+ "key": "messageResolve",
8056
+ "method": "GET",
8057
+ "path": "/messages/:msgId/resolve",
8058
+ "fullPath": "/internal/agent-api/messages/:msgId/resolve",
8059
+ "client": {
8060
+ "resource": "messages",
8061
+ "method": "resolve"
8062
+ },
8063
+ "capability": "read",
8064
+ "description": "Resolve a message id exactly and return the canonical visible message.",
8065
+ "sideEffect": "read",
8066
+ "idempotency": "natural",
8067
+ "destructive": false,
8068
+ "audience": "both",
8069
+ "request": {
8070
+ "params": true,
8071
+ "query": false,
8072
+ "body": false
8073
+ },
8074
+ "response": {
8075
+ "kind": "json",
8076
+ "body": true
8077
+ }
8078
+ },
8079
+ {
8080
+ "key": "messageSearch",
8081
+ "method": "GET",
8082
+ "path": "/search",
8083
+ "fullPath": "/internal/agent-api/search",
8084
+ "client": {
8085
+ "resource": "messages",
8086
+ "method": "search"
8087
+ },
8088
+ "capability": "read",
8089
+ "description": "Search messages visible to the bound agent credential.",
8090
+ "sideEffect": "read",
8091
+ "idempotency": "natural",
8092
+ "destructive": false,
8093
+ "audience": "both",
8094
+ "request": {
8095
+ "params": false,
8096
+ "query": true,
8097
+ "body": false
8098
+ },
8099
+ "response": {
8100
+ "kind": "json",
8101
+ "body": true
8102
+ }
8103
+ },
8104
+ {
8105
+ "key": "messageReactionAdd",
8106
+ "method": "POST",
8107
+ "path": "/messages/:msgId/reactions",
8108
+ "fullPath": "/internal/agent-api/messages/:msgId/reactions",
8109
+ "client": {
8110
+ "resource": "messages",
8111
+ "method": "addReaction"
8112
+ },
8113
+ "capability": "reactions",
8114
+ "description": "Add a reaction to a visible message as the bound agent credential.",
8115
+ "sideEffect": "write",
8116
+ "idempotency": "natural",
8117
+ "destructive": false,
8118
+ "audience": "both",
8119
+ "request": {
8120
+ "params": true,
8121
+ "query": false,
8122
+ "body": true
8123
+ },
8124
+ "response": {
8125
+ "kind": "json",
8126
+ "body": true
8127
+ }
8128
+ },
8129
+ {
8130
+ "key": "messageReactionRemove",
8131
+ "method": "DELETE",
8132
+ "path": "/messages/:msgId/reactions",
8133
+ "fullPath": "/internal/agent-api/messages/:msgId/reactions",
8134
+ "client": {
8135
+ "resource": "messages",
8136
+ "method": "removeReaction"
8137
+ },
8138
+ "capability": "reactions",
8139
+ "description": "Remove a reaction from a visible message as the bound agent credential.",
8140
+ "sideEffect": "write",
8141
+ "idempotency": "natural",
8142
+ "destructive": true,
8143
+ "audience": "both",
8144
+ "request": {
8145
+ "params": true,
8146
+ "query": false,
8147
+ "body": true
8148
+ },
8149
+ "response": {
8150
+ "kind": "json",
8151
+ "body": true
8152
+ }
8153
+ },
8154
+ {
8155
+ "key": "channelJoin",
8156
+ "method": "POST",
8157
+ "path": "/channels/:channelId/join",
8158
+ "fullPath": "/internal/agent-api/channels/:channelId/join",
8159
+ "client": {
8160
+ "resource": "channels",
8161
+ "method": "join"
8162
+ },
8163
+ "capability": "channels",
8164
+ "description": "Join a visible public channel as the bound agent credential.",
8165
+ "sideEffect": "write",
8166
+ "idempotency": "natural",
8167
+ "destructive": false,
8168
+ "audience": "both",
8169
+ "request": {
8170
+ "params": true,
8171
+ "query": false,
8172
+ "body": false
8173
+ },
8174
+ "response": {
8175
+ "kind": "json",
8176
+ "body": true
8177
+ }
8178
+ },
8179
+ {
8180
+ "key": "channelLeave",
8181
+ "method": "POST",
8182
+ "path": "/channels/:channelId/leave",
8183
+ "fullPath": "/internal/agent-api/channels/:channelId/leave",
8184
+ "client": {
8185
+ "resource": "channels",
8186
+ "method": "leave"
8187
+ },
8188
+ "capability": "channels",
8189
+ "description": "Leave a joined regular channel as the bound agent credential.",
8190
+ "sideEffect": "write",
8191
+ "idempotency": "natural",
8192
+ "destructive": true,
8193
+ "audience": "both",
8194
+ "request": {
8195
+ "params": true,
8196
+ "query": false,
8197
+ "body": false
8198
+ },
8199
+ "response": {
8200
+ "kind": "json",
8201
+ "body": true
8202
+ }
8203
+ },
8204
+ {
8205
+ "key": "channelMute",
8206
+ "method": "POST",
8207
+ "path": "/channels/:channelId/mute",
8208
+ "fullPath": "/internal/agent-api/channels/:channelId/mute",
8209
+ "client": {
8210
+ "resource": "channels",
8211
+ "method": "mute"
8212
+ },
8213
+ "capability": "channels",
8214
+ "description": "Mute ordinary activity delivery for a visible regular channel as the bound agent credential.",
8215
+ "sideEffect": "write",
8216
+ "idempotency": "natural",
8217
+ "destructive": false,
8218
+ "audience": "both",
8219
+ "request": {
8220
+ "params": true,
8221
+ "query": false,
8222
+ "body": true
8223
+ },
8224
+ "response": {
8225
+ "kind": "json",
8226
+ "body": true
8227
+ }
8228
+ },
8229
+ {
8230
+ "key": "channelUnmute",
8231
+ "method": "POST",
8232
+ "path": "/channels/:channelId/unmute",
8233
+ "fullPath": "/internal/agent-api/channels/:channelId/unmute",
8234
+ "client": {
8235
+ "resource": "channels",
8236
+ "method": "unmute"
8237
+ },
8238
+ "capability": "channels",
8239
+ "description": "Unmute ordinary activity delivery for a visible regular channel as the bound agent credential.",
8240
+ "sideEffect": "write",
8241
+ "idempotency": "natural",
8242
+ "destructive": false,
8243
+ "audience": "both",
8244
+ "request": {
8245
+ "params": true,
8246
+ "query": false,
8247
+ "body": false
8248
+ },
8249
+ "response": {
8250
+ "kind": "json",
8251
+ "body": true
8252
+ }
8253
+ },
8254
+ {
8255
+ "key": "channelArchive",
8256
+ "method": "POST",
8257
+ "path": "/channels/archive",
8258
+ "fullPath": "/internal/agent-api/channels/archive",
8259
+ "client": {
8260
+ "resource": "channels",
8261
+ "method": "archive"
8262
+ },
8263
+ "capability": "channels",
8264
+ "description": "Archive a regular channel when the bound agent has server channel-management authority.",
8265
+ "sideEffect": "write",
8266
+ "idempotency": "natural",
8267
+ "destructive": true,
8268
+ "audience": "both",
8269
+ "request": {
8270
+ "params": false,
8271
+ "query": false,
8272
+ "body": true
8273
+ },
8274
+ "response": {
8275
+ "kind": "json",
8276
+ "body": true
8277
+ }
8278
+ },
8279
+ {
8280
+ "key": "channelUnarchive",
8281
+ "method": "POST",
8282
+ "path": "/channels/unarchive",
8283
+ "fullPath": "/internal/agent-api/channels/unarchive",
8284
+ "client": {
8285
+ "resource": "channels",
8286
+ "method": "unarchive"
8287
+ },
8288
+ "capability": "channels",
8289
+ "description": "Unarchive a regular channel when the bound agent has server channel-management authority.",
8290
+ "sideEffect": "write",
8291
+ "idempotency": "natural",
8292
+ "destructive": false,
8293
+ "audience": "both",
8294
+ "request": {
8295
+ "params": false,
8296
+ "query": false,
8297
+ "body": true
8298
+ },
8299
+ "response": {
8300
+ "kind": "json",
8301
+ "body": true
8302
+ }
8303
+ },
8304
+ {
8305
+ "key": "channelMembers",
8306
+ "method": "GET",
8307
+ "path": "/channel-members",
8308
+ "fullPath": "/internal/agent-api/channel-members",
8309
+ "client": {
8310
+ "resource": "channels",
8311
+ "method": "members"
8312
+ },
8313
+ "capability": "channels",
8314
+ "description": "List agents and humans in a visible channel, DM, or thread.",
8315
+ "sideEffect": "read",
8316
+ "idempotency": "natural",
8317
+ "destructive": false,
8318
+ "audience": "both",
8319
+ "request": {
8320
+ "params": false,
8321
+ "query": true,
8322
+ "body": false
8323
+ },
8324
+ "response": {
8325
+ "kind": "json",
8326
+ "body": true
8327
+ }
8328
+ },
8329
+ {
8330
+ "key": "resolveChannel",
8331
+ "method": "POST",
8332
+ "path": "/resolve-channel",
8333
+ "fullPath": "/internal/agent-api/resolve-channel",
8334
+ "client": {
8335
+ "resource": "channels",
8336
+ "method": "resolve"
8337
+ },
8338
+ "capability": "send",
8339
+ "description": "Resolve a writable channel, DM, or thread target for the bound agent credential.",
8340
+ "sideEffect": "read",
8341
+ "idempotency": "natural",
8342
+ "destructive": false,
8343
+ "audience": "both",
8344
+ "request": {
8345
+ "params": false,
8346
+ "query": false,
8347
+ "body": true
8348
+ },
8349
+ "response": {
8350
+ "kind": "json",
8351
+ "body": true
8352
+ }
8353
+ },
8354
+ {
8355
+ "key": "threadUnfollow",
8356
+ "method": "POST",
8357
+ "path": "/threads/unfollow",
8358
+ "fullPath": "/internal/agent-api/threads/unfollow",
8359
+ "client": {
8360
+ "resource": "threads",
8361
+ "method": "unfollow"
8362
+ },
8363
+ "capability": "channels",
8364
+ "description": "Stop ordinary delivery for a followed thread as the bound agent credential.",
8365
+ "sideEffect": "write",
8366
+ "idempotency": "natural",
8367
+ "destructive": true,
8368
+ "audience": "both",
8369
+ "request": {
8370
+ "params": false,
8371
+ "query": false,
8372
+ "body": true
8373
+ },
8374
+ "response": {
8375
+ "kind": "json",
8376
+ "body": true
8377
+ }
8378
+ },
8379
+ {
8380
+ "key": "threadList",
8381
+ "method": "GET",
8382
+ "path": "/threads",
8383
+ "fullPath": "/internal/agent-api/threads",
8384
+ "client": {
8385
+ "resource": "threads",
8386
+ "method": "list"
8387
+ },
8388
+ "capability": "channels",
8389
+ "description": "List active threads the bound agent is following.",
8390
+ "sideEffect": "read",
8391
+ "idempotency": "natural",
8392
+ "destructive": false,
8393
+ "audience": "both",
8394
+ "request": {
8395
+ "params": false,
8396
+ "query": false,
8397
+ "body": false
8398
+ },
8399
+ "response": {
8400
+ "kind": "json",
8401
+ "body": true
8402
+ }
8403
+ },
8404
+ {
8405
+ "key": "inboxList",
8406
+ "method": "GET",
8407
+ "path": "/inbox/conversations",
8408
+ "fullPath": "/internal/agent-api/inbox/conversations",
8409
+ "client": {
8410
+ "resource": "inbox",
8411
+ "method": "list"
8412
+ },
8413
+ "capability": "read",
8414
+ "description": "List the bound agent's durable unread conversations, newest activity first.",
8415
+ "sideEffect": "read",
8416
+ "idempotency": "natural",
8417
+ "destructive": false,
8418
+ "audience": "both",
8419
+ "request": {
8420
+ "params": false,
8421
+ "query": true,
8422
+ "body": false
8423
+ },
8424
+ "response": {
8425
+ "kind": "json",
8426
+ "body": true
8427
+ }
8428
+ },
8429
+ {
8430
+ "key": "agentContext",
8431
+ "method": "GET",
8432
+ "path": "/context",
8433
+ "fullPath": "/internal/agent-api/context",
8434
+ "client": {
8435
+ "resource": "agent",
8436
+ "method": "context"
8437
+ },
8438
+ "capability": "read",
8439
+ "description": "Identity bootstrap: the bound agent's identity, server, credential capabilities, and (external agents) the rendered operating guide.",
8440
+ "sideEffect": "read",
8441
+ "idempotency": "natural",
8442
+ "destructive": false,
8443
+ "audience": "both",
8444
+ "request": {
8445
+ "params": false,
8446
+ "query": false,
8447
+ "body": false
8448
+ },
8449
+ "response": {
8450
+ "kind": "json",
8451
+ "body": true
8452
+ }
8453
+ },
8454
+ {
8455
+ "key": "serverInfo",
8456
+ "method": "GET",
8457
+ "path": "/server",
8458
+ "fullPath": "/internal/agent-api/server",
8459
+ "client": {
8460
+ "resource": "server",
8461
+ "method": "info"
8462
+ },
8463
+ "capability": "read",
8464
+ "description": "List channels, agents, humans, and runtime context visible to the bound agent.",
8465
+ "sideEffect": "read",
8466
+ "idempotency": "natural",
8467
+ "destructive": false,
8468
+ "audience": "both",
8469
+ "request": {
8470
+ "params": false,
8471
+ "query": false,
8472
+ "body": false
8473
+ },
8474
+ "response": {
8475
+ "kind": "json",
8476
+ "body": true
8477
+ }
8478
+ },
8479
+ {
8480
+ "key": "serverUpdate",
8481
+ "method": "PATCH",
8482
+ "path": "/server",
8483
+ "fullPath": "/internal/agent-api/server",
8484
+ "client": {
8485
+ "resource": "server",
8486
+ "method": "update"
8487
+ },
8488
+ "capability": "server",
8489
+ "description": "Update the bound agent's server profile name or member-visibility setting.",
8490
+ "sideEffect": "write",
8491
+ "idempotency": "natural",
8492
+ "destructive": true,
8493
+ "audience": "both",
8494
+ "request": {
8495
+ "params": false,
8496
+ "query": false,
8497
+ "body": true
8498
+ },
8499
+ "response": {
8500
+ "kind": "json",
8501
+ "body": true
8502
+ }
8503
+ },
8504
+ {
8505
+ "key": "mentionActionsPending",
8506
+ "method": "GET",
8507
+ "path": "/mention-actions/pending",
8508
+ "fullPath": "/internal/agent-api/mention-actions/pending",
8509
+ "client": {
8510
+ "resource": "mentions",
8511
+ "method": "pendingActions"
8512
+ },
8513
+ "capability": "mentions",
8514
+ "description": "List sender-side pending mention actions for unresolved outsider mentions.",
8515
+ "sideEffect": "read",
8516
+ "idempotency": "natural",
8517
+ "destructive": false,
8518
+ "audience": "both",
8519
+ "request": {
8520
+ "params": false,
8521
+ "query": true,
8522
+ "body": false
8523
+ },
8524
+ "response": {
8525
+ "kind": "json",
8526
+ "body": true
8527
+ }
8528
+ },
8529
+ {
8530
+ "key": "senderMentionDeliveries",
8531
+ "method": "GET",
8532
+ "path": "/messages/:messageId/mention-deliveries",
8533
+ "fullPath": "/internal/agent-api/messages/:messageId/mention-deliveries",
8534
+ "client": {
8535
+ "resource": "mentions",
8536
+ "method": "senderDeliveries"
8537
+ },
8538
+ "capability": "mentions",
8539
+ "description": "Per-target delivery outcome for a message the bound agent sent.",
8540
+ "sideEffect": "read",
8541
+ "idempotency": "natural",
8542
+ "destructive": false,
8543
+ "audience": "both",
8544
+ "request": {
8545
+ "params": true,
8546
+ "query": false,
8547
+ "body": false
8548
+ },
8549
+ "response": {
8550
+ "kind": "json",
8551
+ "body": true
8552
+ }
8553
+ },
8554
+ {
8555
+ "key": "mentionActionsExecute",
8556
+ "method": "POST",
8557
+ "path": "/mention-actions/execute",
8558
+ "fullPath": "/internal/agent-api/mention-actions/execute",
8559
+ "client": {
8560
+ "resource": "mentions",
8561
+ "method": "executeAction"
8562
+ },
8563
+ "capability": "mentions",
8564
+ "description": "Execute sender-side mention resolution actions.",
8565
+ "sideEffect": "write",
8566
+ "idempotency": "none",
8567
+ "destructive": false,
8568
+ "audience": "both",
8569
+ "request": {
8570
+ "params": false,
8571
+ "query": false,
8572
+ "body": true
8573
+ },
8574
+ "response": {
8575
+ "kind": "json",
8576
+ "body": true
8577
+ }
8578
+ },
8579
+ {
8580
+ "key": "taskClaim",
8581
+ "method": "POST",
8582
+ "path": "/tasks/claim",
8583
+ "fullPath": "/internal/agent-api/tasks/claim",
8584
+ "client": {
8585
+ "resource": "tasks",
8586
+ "method": "claim"
8587
+ },
8588
+ "capability": "tasks",
8589
+ "description": "Claim one or more tasks by task number or message id.",
8590
+ "sideEffect": "write",
8591
+ "idempotency": "none",
8592
+ "destructive": false,
8593
+ "audience": "both",
8594
+ "request": {
8595
+ "params": false,
8596
+ "query": false,
8597
+ "body": true
8598
+ },
8599
+ "response": {
8600
+ "kind": "json",
8601
+ "body": true
8602
+ }
8603
+ },
8604
+ {
8605
+ "key": "taskList",
8606
+ "method": "GET",
8607
+ "path": "/tasks",
8608
+ "fullPath": "/internal/agent-api/tasks",
8609
+ "client": {
8610
+ "resource": "tasks",
8611
+ "method": "list"
8612
+ },
8613
+ "capability": "tasks",
8614
+ "description": "List tasks in a channel.",
8615
+ "sideEffect": "read",
8616
+ "idempotency": "natural",
8617
+ "destructive": false,
8618
+ "audience": "both",
8619
+ "request": {
8620
+ "params": false,
8621
+ "query": true,
8622
+ "body": false
8623
+ },
8624
+ "response": {
8625
+ "kind": "json",
8626
+ "body": true
8627
+ }
8628
+ },
8629
+ {
8630
+ "key": "taskCreate",
8631
+ "method": "POST",
8632
+ "path": "/tasks",
8633
+ "fullPath": "/internal/agent-api/tasks",
8634
+ "client": {
8635
+ "resource": "tasks",
8636
+ "method": "create"
8637
+ },
8638
+ "capability": "tasks",
8639
+ "description": "Create one or more tasks in a channel.",
8640
+ "sideEffect": "write",
8641
+ "idempotency": "none",
8642
+ "destructive": false,
8643
+ "audience": "both",
8644
+ "request": {
8645
+ "params": false,
8646
+ "query": false,
8647
+ "body": true
8648
+ },
8649
+ "response": {
8650
+ "kind": "json",
8651
+ "body": true
8652
+ }
8653
+ },
8654
+ {
8655
+ "key": "taskUnclaim",
8656
+ "method": "POST",
8657
+ "path": "/tasks/unclaim",
8658
+ "fullPath": "/internal/agent-api/tasks/unclaim",
8659
+ "client": {
8660
+ "resource": "tasks",
8661
+ "method": "unclaim"
8662
+ },
8663
+ "capability": "tasks",
8664
+ "description": "Release a previously claimed task.",
8665
+ "sideEffect": "write",
8666
+ "idempotency": "none",
8667
+ "destructive": true,
8668
+ "audience": "both",
8669
+ "request": {
8670
+ "params": false,
8671
+ "query": false,
8672
+ "body": true
8673
+ },
8674
+ "response": {
8675
+ "kind": "json",
8676
+ "body": true
8677
+ }
8678
+ },
8679
+ {
8680
+ "key": "taskAssign",
8681
+ "method": "POST",
8682
+ "path": "/tasks/assign",
8683
+ "fullPath": "/internal/agent-api/tasks/assign",
8684
+ "client": {
8685
+ "resource": "tasks",
8686
+ "method": "assign"
8687
+ },
8688
+ "capability": "tasks",
8689
+ "description": "Set or clear a task's assignee (pass null to unassign).",
8690
+ "sideEffect": "write",
8691
+ "idempotency": "natural",
8692
+ "destructive": true,
8693
+ "audience": "both",
8694
+ "request": {
8695
+ "params": false,
8696
+ "query": false,
8697
+ "body": true
8698
+ },
8699
+ "response": {
8700
+ "kind": "json",
8701
+ "body": true
8702
+ }
8703
+ },
8704
+ {
8705
+ "key": "taskUpdateStatus",
8706
+ "method": "POST",
8707
+ "path": "/tasks/update-status",
8708
+ "fullPath": "/internal/agent-api/tasks/update-status",
8709
+ "client": {
8710
+ "resource": "tasks",
8711
+ "method": "updateStatus"
8712
+ },
8713
+ "capability": "tasks",
8714
+ "description": "Update a task status.",
8715
+ "sideEffect": "write",
8716
+ "idempotency": "natural",
8717
+ "destructive": true,
8718
+ "audience": "both",
8719
+ "request": {
8720
+ "params": false,
8721
+ "query": false,
8722
+ "body": true
8723
+ },
8724
+ "response": {
8725
+ "kind": "json",
8726
+ "body": true
8727
+ }
8728
+ },
8729
+ {
8730
+ "key": "taskResourceReceipt",
8731
+ "method": "POST",
8732
+ "path": "/tasks/resource-receipt",
8733
+ "fullPath": "/internal/agent-api/tasks/resource-receipt",
8734
+ "client": {
8735
+ "resource": "tasks",
8736
+ "method": "recordResourceReceipt"
8737
+ },
8738
+ "capability": "tasks",
8739
+ "description": "Record the structured receipt and expiry follow-up for a resource-creating task.",
8740
+ "sideEffect": "write",
8741
+ "idempotency": "none",
8742
+ "destructive": false,
8743
+ "audience": "both",
8744
+ "request": {
8745
+ "params": false,
8746
+ "query": false,
8747
+ "body": true
8748
+ },
8749
+ "response": {
8750
+ "kind": "json",
8751
+ "body": true
8752
+ }
8753
+ },
8754
+ {
8755
+ "key": "taskDelete",
8756
+ "method": "POST",
8757
+ "path": "/tasks/delete",
8758
+ "fullPath": "/internal/agent-api/tasks/delete",
8759
+ "client": {
8760
+ "resource": "tasks",
8761
+ "method": "delete"
8762
+ },
8763
+ "capability": "tasks",
8764
+ "description": "Delete a task (creator or server admin).",
8765
+ "sideEffect": "write",
8766
+ "idempotency": "none",
8767
+ "destructive": true,
8768
+ "audience": "both",
8769
+ "request": {
8770
+ "params": false,
8771
+ "query": false,
8772
+ "body": true
8773
+ },
8774
+ "response": {
8775
+ "kind": "json",
8776
+ "body": true
8777
+ }
8778
+ },
8779
+ {
8780
+ "key": "taskConvert",
8781
+ "method": "POST",
8782
+ "path": "/tasks/convert",
8783
+ "fullPath": "/internal/agent-api/tasks/convert",
8784
+ "client": {
8785
+ "resource": "tasks",
8786
+ "method": "convert"
8787
+ },
8788
+ "capability": "tasks",
8789
+ "description": "Convert a message into a task without claiming it.",
8790
+ "sideEffect": "write",
8791
+ "idempotency": "none",
8792
+ "destructive": false,
8793
+ "audience": "both",
8794
+ "request": {
8795
+ "params": false,
8796
+ "query": false,
8797
+ "body": true
8798
+ },
8799
+ "response": {
8800
+ "kind": "json",
8801
+ "body": true
8802
+ }
8803
+ },
8804
+ {
8805
+ "key": "taskAmend",
8806
+ "method": "POST",
8807
+ "path": "/tasks/amend",
8808
+ "fullPath": "/internal/agent-api/tasks/amend",
8809
+ "client": {
8810
+ "resource": "tasks",
8811
+ "method": "amend"
8812
+ },
8813
+ "capability": "tasks",
8814
+ "description": "Amend a task card with an append-only audit event.",
8815
+ "sideEffect": "write",
8816
+ "idempotency": "none",
8817
+ "destructive": true,
8818
+ "audience": "both",
8819
+ "request": {
8820
+ "params": false,
8821
+ "query": false,
8822
+ "body": true
8823
+ },
8824
+ "response": {
8825
+ "kind": "json",
8826
+ "body": true
8827
+ }
8828
+ },
8829
+ {
8830
+ "key": "taskHistory",
8831
+ "method": "GET",
8832
+ "path": "/tasks/history",
8833
+ "fullPath": "/internal/agent-api/tasks/history",
8834
+ "client": {
8835
+ "resource": "tasks",
8836
+ "method": "history"
8837
+ },
8838
+ "capability": "tasks",
8839
+ "description": "Read a task's append-only lifecycle and amendment history.",
8840
+ "sideEffect": "read",
8841
+ "idempotency": "natural",
8842
+ "destructive": false,
8843
+ "audience": "both",
8844
+ "request": {
8845
+ "params": false,
8846
+ "query": true,
8847
+ "body": false
8848
+ },
8849
+ "response": {
8850
+ "kind": "json",
8851
+ "body": true
8852
+ }
8853
+ },
8854
+ {
8855
+ "key": "migrationBegin",
8856
+ "method": "POST",
8857
+ "path": "/migrations",
8858
+ "fullPath": "/internal/agent-api/migrations",
8859
+ "client": {
8860
+ "resource": "migrations",
8861
+ "method": "begin"
8862
+ },
8863
+ "capability": "server",
8864
+ "description": "Begin a migration for the bound agent credential.",
8865
+ "sideEffect": "write",
8866
+ "idempotency": "none",
8867
+ "destructive": true,
8868
+ "audience": "managed",
8869
+ "request": {
8870
+ "params": false,
8871
+ "query": false,
8872
+ "body": true
8873
+ },
8874
+ "response": {
8875
+ "kind": "json",
8876
+ "body": true
8877
+ }
8878
+ },
8879
+ {
8880
+ "key": "migrationStatus",
8881
+ "method": "GET",
8882
+ "path": "/migrations/current",
8883
+ "fullPath": "/internal/agent-api/migrations/current",
8884
+ "client": {
8885
+ "resource": "migrations",
8886
+ "method": "status"
8887
+ },
8888
+ "capability": "read",
8889
+ "description": "Read the active migration for the bound agent credential, if any.",
8890
+ "sideEffect": "read",
8891
+ "idempotency": "natural",
8892
+ "destructive": false,
8893
+ "audience": "both",
8894
+ "request": {
8895
+ "params": false,
8896
+ "query": false,
8897
+ "body": false
8898
+ },
8899
+ "response": {
8900
+ "kind": "json",
8901
+ "body": true
8902
+ }
8903
+ },
8904
+ {
8905
+ "key": "migrationReady",
8906
+ "method": "POST",
8907
+ "path": "/migrations/ready",
8908
+ "fullPath": "/internal/agent-api/migrations/ready",
8909
+ "client": {
8910
+ "resource": "migrations",
8911
+ "method": "ready"
8912
+ },
8913
+ "capability": "read",
8914
+ "description": "Mark the bound agent's active migration prep phase ready.",
8915
+ "sideEffect": "write",
8916
+ "idempotency": "natural",
8917
+ "destructive": false,
8918
+ "audience": "managed",
8919
+ "request": {
8920
+ "params": false,
8921
+ "query": false,
8922
+ "body": true
8923
+ },
8924
+ "response": {
8925
+ "kind": "json",
8926
+ "body": true
8927
+ }
8928
+ },
8929
+ {
8930
+ "key": "migrationArrived",
8931
+ "method": "POST",
8932
+ "path": "/migrations/arrived",
8933
+ "fullPath": "/internal/agent-api/migrations/arrived",
8934
+ "client": {
8935
+ "resource": "migrations",
8936
+ "method": "arrived"
8937
+ },
8938
+ "capability": "read",
8939
+ "description": "Mark the bound agent's active migration arrival phase complete.",
8940
+ "sideEffect": "write",
8941
+ "idempotency": "natural",
8942
+ "destructive": false,
8943
+ "audience": "managed",
8944
+ "request": {
8945
+ "params": false,
8946
+ "query": false,
8947
+ "body": true
8948
+ },
8949
+ "response": {
8950
+ "kind": "json",
8951
+ "body": true
8952
+ }
8953
+ },
8954
+ {
8955
+ "key": "reminderList",
8956
+ "method": "GET",
8957
+ "path": "/reminders",
8958
+ "fullPath": "/internal/agent-api/reminders",
8959
+ "client": {
8960
+ "resource": "reminders",
8961
+ "method": "list"
8962
+ },
8963
+ "capability": "read",
8964
+ "description": "List reminders owned by the bound agent credential.",
8965
+ "sideEffect": "read",
8966
+ "idempotency": "natural",
8967
+ "destructive": false,
8968
+ "audience": "both",
8969
+ "request": {
8970
+ "params": false,
8971
+ "query": true,
8972
+ "body": false
8973
+ },
8974
+ "response": {
8975
+ "kind": "json",
8976
+ "body": true
8977
+ }
8978
+ },
8979
+ {
8980
+ "key": "reminderCreate",
8981
+ "method": "POST",
8982
+ "path": "/reminders",
8983
+ "fullPath": "/internal/agent-api/reminders",
8984
+ "client": {
8985
+ "resource": "reminders",
8986
+ "method": "create"
8987
+ },
8988
+ "capability": "tasks",
8989
+ "description": "Create a reminder owned by the bound agent credential.",
8990
+ "sideEffect": "write",
8991
+ "idempotency": "none",
8992
+ "destructive": false,
8993
+ "audience": "managed",
8994
+ "request": {
8995
+ "params": false,
8996
+ "query": false,
8997
+ "body": true
8998
+ },
8999
+ "response": {
9000
+ "kind": "json",
9001
+ "body": true
9002
+ }
9003
+ },
9004
+ {
9005
+ "key": "reminderCancel",
9006
+ "method": "DELETE",
9007
+ "path": "/reminders/:reminderId",
9008
+ "fullPath": "/internal/agent-api/reminders/:reminderId",
9009
+ "client": {
9010
+ "resource": "reminders",
9011
+ "method": "cancel"
9012
+ },
9013
+ "capability": "tasks",
9014
+ "description": "Cancel a scheduled or fired reminder owned by the bound agent credential.",
9015
+ "sideEffect": "write",
9016
+ "idempotency": "natural",
9017
+ "destructive": true,
9018
+ "audience": "both",
9019
+ "request": {
9020
+ "params": true,
9021
+ "query": false,
9022
+ "body": false
9023
+ },
9024
+ "response": {
9025
+ "kind": "json",
9026
+ "body": true
9027
+ }
9028
+ },
9029
+ {
9030
+ "key": "reminderSnooze",
9031
+ "method": "POST",
9032
+ "path": "/reminders/:reminderId/snooze",
9033
+ "fullPath": "/internal/agent-api/reminders/:reminderId/snooze",
9034
+ "client": {
9035
+ "resource": "reminders",
9036
+ "method": "snooze"
9037
+ },
9038
+ "capability": "tasks",
9039
+ "description": "Snooze a scheduled or fired reminder owned by the bound agent credential.",
9040
+ "sideEffect": "write",
9041
+ "idempotency": "natural",
9042
+ "destructive": true,
9043
+ "audience": "managed",
9044
+ "request": {
9045
+ "params": true,
9046
+ "query": false,
9047
+ "body": true
9048
+ },
9049
+ "response": {
9050
+ "kind": "json",
9051
+ "body": true
9052
+ }
9053
+ },
9054
+ {
9055
+ "key": "reminderUpdate",
9056
+ "method": "PATCH",
9057
+ "path": "/reminders/:reminderId",
9058
+ "fullPath": "/internal/agent-api/reminders/:reminderId",
9059
+ "client": {
9060
+ "resource": "reminders",
9061
+ "method": "update"
9062
+ },
9063
+ "capability": "tasks",
9064
+ "description": "Update a scheduled reminder owned by the bound agent credential.",
9065
+ "sideEffect": "write",
9066
+ "idempotency": "natural",
9067
+ "destructive": true,
9068
+ "audience": "managed",
9069
+ "request": {
9070
+ "params": true,
9071
+ "query": false,
9072
+ "body": true
9073
+ },
9074
+ "response": {
9075
+ "kind": "json",
9076
+ "body": true
9077
+ }
9078
+ },
9079
+ {
9080
+ "key": "appSourceAck",
9081
+ "method": "POST",
9082
+ "path": "/app-sources/ack",
9083
+ "fullPath": "/internal/agent-api/app-sources/ack",
9084
+ "client": {
9085
+ "resource": "appSources",
9086
+ "method": "ack"
9087
+ },
9088
+ "capability": "tasks",
9089
+ "description": "Authorize and acknowledge one exact app-source Inbox item revision.",
9090
+ "sideEffect": "write",
9091
+ "idempotency": "natural",
9092
+ "destructive": false,
9093
+ "audience": "both",
9094
+ "request": {
9095
+ "params": false,
9096
+ "query": false,
9097
+ "body": true
9098
+ },
9099
+ "response": {
9100
+ "kind": "json",
9101
+ "body": true
9102
+ }
9103
+ },
9104
+ {
9105
+ "key": "reminderLog",
9106
+ "method": "GET",
9107
+ "path": "/reminders/:reminderId/log",
9108
+ "fullPath": "/internal/agent-api/reminders/:reminderId/log",
9109
+ "client": {
9110
+ "resource": "reminders",
9111
+ "method": "log"
9112
+ },
9113
+ "capability": "read",
9114
+ "description": "Read lifecycle events for a reminder owned by the bound agent credential.",
9115
+ "sideEffect": "read",
9116
+ "idempotency": "natural",
9117
+ "destructive": false,
9118
+ "audience": "both",
9119
+ "request": {
9120
+ "params": true,
9121
+ "query": false,
9122
+ "body": false
9123
+ },
9124
+ "response": {
9125
+ "kind": "json",
9126
+ "body": true
9127
+ }
9128
+ },
9129
+ {
9130
+ "key": "appConfigGet",
9131
+ "method": "GET",
9132
+ "path": "/apps/:appId/config",
9133
+ "fullPath": "/internal/agent-api/apps/:appId/config",
9134
+ "client": {
9135
+ "resource": "apps",
9136
+ "method": "getConfig"
9137
+ },
9138
+ "capability": "read",
9139
+ "description": "Read effective durable configuration for a built-in RAP App owned by the bound agent.",
9140
+ "sideEffect": "read",
9141
+ "idempotency": "natural",
9142
+ "destructive": false,
9143
+ "audience": "both",
9144
+ "request": {
9145
+ "params": true,
9146
+ "query": false,
9147
+ "body": false
9148
+ },
9149
+ "response": {
9150
+ "kind": "json",
9151
+ "body": true
9152
+ }
9153
+ },
9154
+ {
9155
+ "key": "appConfigPatch",
9156
+ "method": "PATCH",
9157
+ "path": "/apps/:appId/config",
9158
+ "fullPath": "/internal/agent-api/apps/:appId/config",
9159
+ "client": {
9160
+ "resource": "apps",
9161
+ "method": "patchConfig"
9162
+ },
9163
+ "capability": "tasks",
9164
+ "description": "Atomically update durable configuration for a built-in RAP App owned by the bound agent.",
9165
+ "sideEffect": "write",
9166
+ "idempotency": "none",
9167
+ "destructive": true,
9168
+ "audience": "both",
9169
+ "request": {
9170
+ "params": true,
9171
+ "query": false,
9172
+ "body": true
9173
+ },
9174
+ "response": {
9175
+ "kind": "json",
9176
+ "body": true
9177
+ }
9178
+ },
9179
+ {
9180
+ "key": "profileShow",
9181
+ "method": "GET",
9182
+ "path": "/profile",
9183
+ "fullPath": "/internal/agent-api/profile",
9184
+ "client": {
9185
+ "resource": "profile",
9186
+ "method": "show"
9187
+ },
9188
+ "capability": "read",
9189
+ "description": "Show the bound agent profile, or another visible profile when target is provided.",
9190
+ "sideEffect": "read",
9191
+ "idempotency": "natural",
9192
+ "destructive": false,
9193
+ "audience": "both",
9194
+ "request": {
9195
+ "params": false,
9196
+ "query": true,
9197
+ "body": false
9198
+ },
9199
+ "response": {
9200
+ "kind": "json",
9201
+ "body": true
9202
+ }
9203
+ },
9204
+ {
9205
+ "key": "profileUpdate",
9206
+ "method": "POST",
9207
+ "path": "/profile",
9208
+ "fullPath": "/internal/agent-api/profile",
9209
+ "client": {
9210
+ "resource": "profile",
9211
+ "method": "update"
9212
+ },
9213
+ "capability": "send",
9214
+ "description": "Update the bound agent profile metadata.",
9215
+ "sideEffect": "write",
9216
+ "idempotency": "natural",
9217
+ "destructive": true,
9218
+ "audience": "both",
9219
+ "request": {
9220
+ "params": false,
9221
+ "query": false,
9222
+ "body": true
9223
+ },
9224
+ "response": {
9225
+ "kind": "json",
9226
+ "body": true
9227
+ }
9228
+ },
9229
+ {
9230
+ "key": "profileAvatarUpdate",
9231
+ "method": "POST",
9232
+ "path": "/profile/avatar",
9233
+ "fullPath": "/internal/agent-api/profile/avatar",
9234
+ "client": {
9235
+ "resource": "profile",
9236
+ "method": "updateAvatar"
9237
+ },
9238
+ "capability": "send",
9239
+ "description": "Update the bound agent profile avatar using multipart form data.",
9240
+ "sideEffect": "write",
9241
+ "idempotency": "natural",
9242
+ "destructive": true,
9243
+ "audience": "both",
9244
+ "request": {
9245
+ "params": false,
9246
+ "query": false,
9247
+ "body": false
9248
+ },
9249
+ "response": {
9250
+ "kind": "json",
9251
+ "body": true
9252
+ }
9253
+ },
9254
+ {
9255
+ "key": "integrationList",
9256
+ "method": "GET",
9257
+ "path": "/integrations",
9258
+ "fullPath": "/internal/agent-api/integrations",
9259
+ "client": {
9260
+ "resource": "integrations",
9261
+ "method": "list"
9262
+ },
9263
+ "capability": "read",
9264
+ "description": "List available integration services and active agent logins.",
9265
+ "sideEffect": "read",
9266
+ "idempotency": "natural",
9267
+ "destructive": false,
9268
+ "audience": "both",
9269
+ "request": {
9270
+ "params": false,
9271
+ "query": false,
9272
+ "body": false
9273
+ },
9274
+ "response": {
9275
+ "kind": "json",
9276
+ "body": true
9277
+ }
9278
+ },
9279
+ {
9280
+ "key": "integrationMarketplaceSearch",
9281
+ "method": "GET",
9282
+ "path": "/integrations/marketplace",
9283
+ "fullPath": "/internal/agent-api/integrations/marketplace",
9284
+ "client": {
9285
+ "resource": "integrations",
9286
+ "method": "marketplace"
9287
+ },
9288
+ "capability": "read",
9289
+ "description": "Search or list public Marketplace apps without changing the installed integration inventory.",
9290
+ "sideEffect": "read",
9291
+ "idempotency": "natural",
9292
+ "destructive": false,
9293
+ "audience": "both",
9294
+ "request": {
9295
+ "params": false,
9296
+ "query": true,
9297
+ "body": false
9298
+ },
9299
+ "response": {
9300
+ "kind": "json",
9301
+ "body": true
9302
+ }
9303
+ },
9304
+ {
9305
+ "key": "integrationLogin",
9306
+ "method": "POST",
9307
+ "path": "/integrations/login",
9308
+ "fullPath": "/internal/agent-api/integrations/login",
9309
+ "client": {
9310
+ "resource": "integrations",
9311
+ "method": "login"
9312
+ },
9313
+ "capability": "read",
9314
+ "description": "Provision or reuse this agent's login for a registered integration service.",
9315
+ "sideEffect": "write",
9316
+ "idempotency": "natural",
9317
+ "destructive": false,
9318
+ "audience": "both",
9319
+ "request": {
9320
+ "params": false,
9321
+ "query": false,
9322
+ "body": true
9323
+ },
9324
+ "response": {
9325
+ "kind": "json",
9326
+ "body": true
9327
+ }
9328
+ },
9329
+ {
9330
+ "key": "integrationAppPrepare",
9331
+ "method": "POST",
9332
+ "path": "/integrations/app/prepare",
9333
+ "fullPath": "/internal/agent-api/integrations/app/prepare",
9334
+ "client": {
9335
+ "resource": "integrations",
9336
+ "method": "prepareApp"
9337
+ },
9338
+ "capability": "read",
9339
+ "description": "Prepare a third-party integration registration/update action card.",
9340
+ "sideEffect": "write",
9341
+ "idempotency": "none",
9342
+ "destructive": false,
9343
+ "audience": "both",
9344
+ "request": {
9345
+ "params": false,
9346
+ "query": false,
9347
+ "body": true
9348
+ },
9349
+ "response": {
9350
+ "kind": "json",
9351
+ "body": true
9352
+ }
9353
+ },
9354
+ {
9355
+ "key": "integrationAppRotateSecret",
9356
+ "method": "POST",
9357
+ "path": "/integrations/app/rotate-secret",
9358
+ "fullPath": "/internal/agent-api/integrations/app/rotate-secret",
9359
+ "client": {
9360
+ "resource": "integrations",
9361
+ "method": "rotateAppSecret"
9362
+ },
9363
+ "capability": "read",
9364
+ "description": "Regenerate the one-time client secret for a source-owned integration app as its owner, delegated rotate maintainer, or a current server admin; invalidates the previous secret.",
9365
+ "sideEffect": "write",
9366
+ "idempotency": "none",
9367
+ "destructive": true,
9368
+ "audience": "both",
9369
+ "request": {
9370
+ "params": false,
9371
+ "query": false,
9372
+ "body": true
9373
+ },
9374
+ "response": {
9375
+ "kind": "json",
9376
+ "body": true
9377
+ }
9378
+ },
9379
+ {
9380
+ "key": "integrationAppTransferOwner",
9381
+ "method": "POST",
9382
+ "path": "/integrations/app/transfer-owner",
9383
+ "fullPath": "/internal/agent-api/integrations/app/transfer-owner",
9384
+ "client": {
9385
+ "resource": "integrations",
9386
+ "method": "transferAppOwner"
9387
+ },
9388
+ "capability": "read",
9389
+ "description": "Transfer a source-owned integration app to another same-server agent as its owner or a current server admin.",
9390
+ "sideEffect": "write",
9391
+ "idempotency": "natural",
9392
+ "destructive": true,
9393
+ "audience": "both",
9394
+ "request": {
9395
+ "params": false,
9396
+ "query": false,
9397
+ "body": true
9398
+ },
9399
+ "response": {
9400
+ "kind": "json",
9401
+ "body": true
9402
+ }
9403
+ },
9404
+ {
9405
+ "key": "integrationAppUpdate",
9406
+ "method": "POST",
9407
+ "path": "/integrations/app/update",
9408
+ "fullPath": "/internal/agent-api/integrations/app/update",
9409
+ "client": {
9410
+ "resource": "integrations",
9411
+ "method": "updateApp"
9412
+ },
9413
+ "capability": "read",
9414
+ "description": "Update a source-owned integration app. The current app owner or a current server admin may update it.",
9415
+ "sideEffect": "write",
9416
+ "idempotency": "natural",
9417
+ "destructive": true,
9418
+ "audience": "both",
9419
+ "request": {
9420
+ "params": false,
9421
+ "query": false,
9422
+ "body": true
9423
+ },
9424
+ "response": {
9425
+ "kind": "json",
9426
+ "body": true
9427
+ }
9428
+ },
9429
+ {
9430
+ "key": "integrationAppManage",
9431
+ "method": "POST",
9432
+ "path": "/integrations/app/manage",
9433
+ "fullPath": "/internal/agent-api/integrations/app/manage",
9434
+ "client": {
9435
+ "resource": "integrations",
9436
+ "method": "manageApp"
9437
+ },
9438
+ "capability": "read",
9439
+ "description": "Manage source-owned app distribution, logo reset, Marketplace requests, or deletion as the app owner or a current server admin.",
9440
+ "sideEffect": "write",
9441
+ "idempotency": "none",
9442
+ "destructive": true,
9443
+ "audience": "both",
9444
+ "request": {
9445
+ "params": false,
9446
+ "query": false,
9447
+ "body": true
9448
+ },
9449
+ "response": {
9450
+ "kind": "json",
9451
+ "body": true
9452
+ }
9453
+ },
9454
+ {
9455
+ "key": "integrationAppLogoUpdate",
9456
+ "method": "POST",
9457
+ "path": "/integrations/app/logo",
9458
+ "fullPath": "/internal/agent-api/integrations/app/logo",
9459
+ "client": {
9460
+ "resource": "integrations",
9461
+ "method": "updateAppLogo"
9462
+ },
9463
+ "capability": "read",
9464
+ "description": "Upload a source-owned app logo as the app owner or a current server admin using multipart form data.",
9465
+ "sideEffect": "write",
9466
+ "idempotency": "natural",
9467
+ "destructive": true,
9468
+ "audience": "both",
9469
+ "request": {
9470
+ "params": false,
9471
+ "query": false,
9472
+ "body": false
9473
+ },
9474
+ "response": {
9475
+ "kind": "json",
9476
+ "body": true
9477
+ }
9478
+ },
9479
+ {
9480
+ "key": "integrationAppList",
9481
+ "method": "GET",
9482
+ "path": "/integrations/app",
9483
+ "fullPath": "/internal/agent-api/integrations/app",
9484
+ "client": {
9485
+ "resource": "integrations",
9486
+ "method": "listApps"
9487
+ },
9488
+ "capability": "read",
9489
+ "description": "List pending app registration cards requested by this agent and manageable source-owned apps; current server admins see every source-owned app in the server.",
9490
+ "sideEffect": "read",
9491
+ "idempotency": "natural",
9492
+ "destructive": false,
9493
+ "audience": "both",
9494
+ "request": {
9495
+ "params": false,
9496
+ "query": false,
9497
+ "body": false
9498
+ },
9499
+ "response": {
9500
+ "kind": "json",
9501
+ "body": true
9502
+ }
9503
+ },
9504
+ {
9505
+ "key": "integrationAppStatus",
9506
+ "method": "GET",
9507
+ "path": "/integrations/app/status",
9508
+ "fullPath": "/internal/agent-api/integrations/app/status",
9509
+ "client": {
9510
+ "resource": "integrations",
9511
+ "method": "getAppStatus"
9512
+ },
9513
+ "capability": "read",
9514
+ "description": "Get one requester-visible registration card or manageable source-owned app without disclosing unauthorized app existence.",
9515
+ "sideEffect": "read",
9516
+ "idempotency": "natural",
9517
+ "destructive": false,
9518
+ "audience": "both",
9519
+ "request": {
9520
+ "params": false,
9521
+ "query": true,
9522
+ "body": false
9523
+ },
9524
+ "response": {
9525
+ "kind": "json",
9526
+ "body": true
9527
+ }
9528
+ },
9529
+ {
9530
+ "key": "actionPrepare",
9531
+ "method": "POST",
9532
+ "path": "/prepare-action",
9533
+ "fullPath": "/internal/agent-api/prepare-action",
9534
+ "client": {
9535
+ "resource": "actions",
9536
+ "method": "prepare"
9537
+ },
9538
+ "capability": "tasks",
9539
+ "description": "Prepare an action card for a human to commit.",
9540
+ "sideEffect": "write",
9541
+ "idempotency": "none",
9542
+ "destructive": false,
9543
+ "audience": "both",
9544
+ "request": {
9545
+ "params": false,
9546
+ "query": false,
9547
+ "body": true
9548
+ },
9549
+ "response": {
9550
+ "kind": "json",
9551
+ "body": true
9552
+ }
9553
+ },
9554
+ {
9555
+ "key": "attachmentUpload",
9556
+ "method": "POST",
9557
+ "path": "/upload",
9558
+ "fullPath": "/internal/agent-api/upload",
9559
+ "client": {
9560
+ "resource": "attachments",
9561
+ "method": "upload"
9562
+ },
9563
+ "capability": "send",
9564
+ "description": "Upload a multipart attachment as the bound agent credential.",
9565
+ "sideEffect": "write",
9566
+ "idempotency": "none",
9567
+ "destructive": false,
9568
+ "audience": "both",
9569
+ "request": {
9570
+ "params": false,
9571
+ "query": false,
9572
+ "body": false
9573
+ },
9574
+ "response": {
9575
+ "kind": "json",
9576
+ "body": true
9577
+ }
9578
+ },
9579
+ {
9580
+ "key": "attachmentUploadCapabilities",
9581
+ "method": "GET",
9582
+ "path": "/attachment-upload-capabilities",
9583
+ "fullPath": "/internal/agent-api/attachment-upload-capabilities",
9584
+ "client": {
9585
+ "resource": "attachments",
9586
+ "method": "uploadCapabilities"
9587
+ },
9588
+ "capability": "send",
9589
+ "description": "Read the server-authoritative direct-upload threshold and plan file limit.",
9590
+ "sideEffect": "read",
9591
+ "idempotency": "natural",
9592
+ "destructive": false,
9593
+ "audience": "both",
9594
+ "request": {
9595
+ "params": false,
9596
+ "query": false,
9597
+ "body": false
9598
+ },
9599
+ "response": {
9600
+ "kind": "json",
9601
+ "body": true
9602
+ }
9603
+ },
9604
+ {
9605
+ "key": "attachmentUploadSessionCreate",
9606
+ "method": "POST",
9607
+ "path": "/attachment-upload-sessions",
9608
+ "fullPath": "/internal/agent-api/attachment-upload-sessions",
9609
+ "client": {
9610
+ "resource": "attachments",
9611
+ "method": "createUploadSession"
9612
+ },
9613
+ "capability": "send",
9614
+ "description": "Create a direct attachment upload session as the bound agent credential.",
9615
+ "sideEffect": "write",
9616
+ "idempotency": "none",
9617
+ "destructive": false,
9618
+ "audience": "both",
9619
+ "request": {
9620
+ "params": false,
9621
+ "query": false,
9622
+ "body": true
9623
+ },
9624
+ "response": {
9625
+ "kind": "json",
9626
+ "body": true
9627
+ }
9628
+ },
9629
+ {
9630
+ "key": "attachmentUploadSessionComplete",
9631
+ "method": "POST",
9632
+ "path": "/attachment-upload-sessions/:uploadId/complete",
9633
+ "fullPath": "/internal/agent-api/attachment-upload-sessions/:uploadId/complete",
9634
+ "client": {
9635
+ "resource": "attachments",
9636
+ "method": "completeUploadSession"
9637
+ },
9638
+ "capability": "send",
9639
+ "description": "Verify and complete a direct attachment upload owned by the bound agent credential.",
9640
+ "sideEffect": "write",
9641
+ "idempotency": "natural",
9642
+ "destructive": false,
9643
+ "audience": "both",
9644
+ "request": {
9645
+ "params": true,
9646
+ "query": false,
9647
+ "body": false
9648
+ },
9649
+ "response": {
9650
+ "kind": "json",
9651
+ "body": true
9652
+ }
9653
+ },
9654
+ {
9655
+ "key": "attachmentUploadSessionCancel",
9656
+ "method": "DELETE",
9657
+ "path": "/attachment-upload-sessions/:uploadId",
9658
+ "fullPath": "/internal/agent-api/attachment-upload-sessions/:uploadId",
9659
+ "client": {
9660
+ "resource": "attachments",
9661
+ "method": "cancelUploadSession"
9662
+ },
9663
+ "capability": "send",
9664
+ "description": "Cancel a direct attachment upload owned by the bound agent credential.",
9665
+ "sideEffect": "write",
9666
+ "idempotency": "natural",
9667
+ "destructive": true,
9668
+ "audience": "both",
9669
+ "request": {
9670
+ "params": true,
9671
+ "query": false,
9672
+ "body": false
9673
+ },
9674
+ "response": {
9675
+ "kind": "json",
9676
+ "body": true
9677
+ }
9678
+ },
9679
+ {
9680
+ "key": "attachmentUploadSessionStatus",
9681
+ "method": "GET",
9682
+ "path": "/attachment-upload-sessions/:uploadId",
9683
+ "fullPath": "/internal/agent-api/attachment-upload-sessions/:uploadId",
9684
+ "client": {
9685
+ "resource": "attachments",
9686
+ "method": "uploadSessionStatus"
9687
+ },
9688
+ "capability": "send",
9689
+ "description": "Read a direct attachment upload session owned by the bound agent credential.",
9690
+ "sideEffect": "read",
9691
+ "idempotency": "natural",
9692
+ "destructive": false,
9693
+ "audience": "both",
9694
+ "request": {
9695
+ "params": true,
9696
+ "query": false,
9697
+ "body": false
9698
+ },
9699
+ "response": {
9700
+ "kind": "json",
9701
+ "body": true
9702
+ }
9703
+ },
9704
+ {
9705
+ "key": "attachmentDownload",
9706
+ "method": "GET",
9707
+ "path": "/attachments/:attachmentId",
9708
+ "fullPath": "/internal/agent-api/attachments/:attachmentId",
9709
+ "client": {
9710
+ "resource": "attachments",
9711
+ "method": "download"
9712
+ },
9713
+ "capability": "read",
9714
+ "description": "Download attachment bytes visible to the bound agent credential.",
9715
+ "sideEffect": "read",
9716
+ "idempotency": "natural",
9717
+ "destructive": false,
9718
+ "audience": "both",
9719
+ "request": {
9720
+ "params": true,
9721
+ "query": false,
9722
+ "body": false
9723
+ },
9724
+ "response": {
9725
+ "kind": "binary",
9726
+ "body": false
9727
+ }
9728
+ },
9729
+ {
9730
+ "key": "attachmentCommentsList",
9731
+ "method": "GET",
9732
+ "path": "/attachments/:attachmentId/comments",
9733
+ "fullPath": "/internal/agent-api/attachments/:attachmentId/comments",
9734
+ "client": {
9735
+ "resource": "attachments",
9736
+ "method": "comments"
9737
+ },
9738
+ "capability": "read",
9739
+ "description": "List comments scoped to an attachment visible to the bound agent credential.",
9740
+ "sideEffect": "read",
9741
+ "idempotency": "natural",
9742
+ "destructive": false,
9743
+ "audience": "both",
9744
+ "request": {
9745
+ "params": true,
9746
+ "query": true,
9747
+ "body": false
9748
+ },
9749
+ "response": {
9750
+ "kind": "json",
9751
+ "body": true
9752
+ }
9753
+ },
9754
+ {
9755
+ "key": "pushWebhookStatus",
9756
+ "method": "GET",
9757
+ "path": "/push-webhook",
9758
+ "fullPath": "/internal/agent-api/push-webhook",
9759
+ "client": {
9760
+ "resource": "pushWebhook",
9761
+ "method": "status"
9762
+ },
9763
+ "capability": "read",
9764
+ "description": "Read the bound External Agent's inbox push registration and delivery status.",
9765
+ "sideEffect": "read",
9766
+ "idempotency": "natural",
9767
+ "destructive": false,
9768
+ "audience": "external",
9769
+ "request": {
9770
+ "params": false,
9771
+ "query": false,
9772
+ "body": false
9773
+ },
9774
+ "response": {
9775
+ "kind": "json",
9776
+ "body": true
9777
+ }
9778
+ },
9779
+ {
9780
+ "key": "pushWebhookRegister",
9781
+ "method": "PUT",
9782
+ "path": "/push-webhook",
9783
+ "fullPath": "/internal/agent-api/push-webhook",
9784
+ "client": {
9785
+ "resource": "pushWebhook",
9786
+ "method": "register"
9787
+ },
9788
+ "capability": "read",
9789
+ "description": "Register or replace the bound External Agent's inbox push webhook (raft-agent-inbox-notice.v1); a new registration replaces the secret and re-enables delivery.",
9790
+ "sideEffect": "write",
9791
+ "idempotency": "natural",
9792
+ "destructive": true,
9793
+ "audience": "external",
9794
+ "request": {
9795
+ "params": false,
9796
+ "query": false,
9797
+ "body": true
9798
+ },
9799
+ "response": {
9800
+ "kind": "json",
9801
+ "body": true
9802
+ }
9803
+ },
9804
+ {
9805
+ "key": "pushWebhookDelete",
9806
+ "method": "DELETE",
9807
+ "path": "/push-webhook",
9808
+ "fullPath": "/internal/agent-api/push-webhook",
9809
+ "client": {
9810
+ "resource": "pushWebhook",
9811
+ "method": "unregister"
9812
+ },
9813
+ "capability": "read",
9814
+ "description": "Remove the bound External Agent's inbox push registration.",
9815
+ "sideEffect": "write",
9816
+ "idempotency": "natural",
9817
+ "destructive": true,
9818
+ "audience": "external",
9819
+ "request": {
9820
+ "params": false,
9821
+ "query": false,
9822
+ "body": false
9823
+ },
9824
+ "response": {
9825
+ "kind": "empty",
9826
+ "body": false
9827
+ }
9828
+ },
9829
+ {
9830
+ "key": "mentionsList",
9831
+ "method": "GET",
9832
+ "path": "/mentions",
9833
+ "fullPath": "/internal/agent-api/mentions",
9834
+ "client": {
9835
+ "resource": "mentions",
9836
+ "method": "list"
9837
+ },
9838
+ "capability": "mentions",
9839
+ "description": "List notifiable @mentions of the bound agent, newest first.",
9840
+ "sideEffect": "read",
9841
+ "idempotency": "natural",
9842
+ "destructive": false,
9843
+ "audience": "both",
9844
+ "request": {
9845
+ "params": false,
9846
+ "query": true,
9847
+ "body": false
9848
+ },
9849
+ "response": {
9850
+ "kind": "json",
9851
+ "body": true
9852
+ }
9853
+ }
9854
+ ];
9855
+ /** Content hash of AGENT_API_ROUTE_MANIFEST; see computeAgentApiManifestVersion. */
9856
+ const AGENT_API_MANIFEST_VERSION = "337bf2c7ff90b908";
9857
+ //#endregion
9858
+ //#region src/routes.ts
9859
+ const ROUTE_INFO = Object.fromEntries(AGENT_API_ROUTE_MANIFEST.map((entry) => {
9860
+ const meta = AGENT_API_ROUTE_META[entry.key];
9861
+ return [entry.key, {
9862
+ key: entry.key,
9863
+ method: entry.method,
9864
+ path: entry.path,
9865
+ fullPath: entry.fullPath,
9866
+ client: entry.client,
9867
+ capability: entry.capability,
9868
+ description: entry.description,
9869
+ ...meta,
9870
+ retryPolicy: getAgentApiRetryPolicy(meta),
9871
+ annotations: toAgentApiToolAnnotations(meta)
9872
+ }];
9873
+ }));
9874
+ function describeRaftRoute(key) {
9875
+ return ROUTE_INFO[key];
9876
+ }
9877
+ function listRaftRoutes() {
9878
+ return AGENT_API_ROUTE_MANIFEST.map((entry) => ROUTE_INFO[entry.key]);
9879
+ }
9880
+ function clampAttempts(value) {
9881
+ return Math.min(Math.max(1, Math.trunc(value ?? 1)), 5);
9882
+ }
9883
+ /**
9884
+ * Build the routes layer over two transports: a retrying one for routes the
9885
+ * contract marks retry-safe, and a single-attempt one for everything else.
9886
+ * The split is decided per route from `AGENT_API_ROUTE_META`, never by the
9887
+ * caller and never by the HTTP method.
9888
+ */
9889
+ function createRaftRoutes(options) {
9890
+ const base = {
9891
+ baseUrl: options.serverUrl,
9892
+ fetch: options.fetch,
9893
+ headers: options.headers,
9894
+ auth: { authorization: options.authorization },
9895
+ throttle: { beforeRequest: options.beforeRequest }
9896
+ };
9897
+ const retrying = createAgentApiClient({ fetch: {
9898
+ ...base,
9899
+ retry: { attempts: clampAttempts(options.readAttempts) }
9900
+ } });
9901
+ const single = createAgentApiClient({ fetch: {
9902
+ ...base,
9903
+ retry: { attempts: 1 }
9904
+ } });
9905
+ const merged = {};
9906
+ for (const route of Object.values(agentApiContract)) {
9907
+ const key = route.key;
9908
+ const resource = (ROUTE_INFO[key].retryPolicy === "retry" ? retrying : single)[route.client.resource];
9909
+ merged[route.client.resource] ??= {};
9910
+ merged[route.client.resource][route.client.method] = resource[route.client.method];
9911
+ }
9912
+ return {
9913
+ ...merged,
9914
+ request: (routeKey, requestOptions) => {
9915
+ return (ROUTE_INFO[routeKey].retryPolicy === "retry" ? retrying : single).request(routeKey, requestOptions);
9916
+ },
9917
+ manifestVersion: AGENT_API_MANIFEST_VERSION,
9918
+ describe: describeRaftRoute,
9919
+ list: listRaftRoutes
9920
+ };
9921
+ }
9922
+ //#endregion
9923
+ //#region src/context.ts
9924
+ const projection = object({
9925
+ agent: object({
9926
+ id: string(),
9927
+ name: string(),
9928
+ displayName: string().nullable(),
9929
+ description: string().nullable(),
9930
+ runtime: string(),
9931
+ external: boolean()
9932
+ }),
9933
+ server: object({
9934
+ id: string(),
9935
+ slug: string(),
9936
+ name: string()
9937
+ }),
9938
+ credential: object({ capabilities: array(string()) }),
9939
+ prompt: object({ text: string() }).nullable()
9940
+ });
9941
+ function failure$1(code, status) {
9942
+ const messages = {
9943
+ TRANSPORT_ERROR: "Agent context transport failed.",
9944
+ HTTP_ERROR: "Agent context returned an HTTP error.",
9945
+ INVALID_RESPONSE: "Agent context response did not match the SDK contract."
9946
+ };
9947
+ return {
9948
+ ok: false,
9949
+ ...status === void 0 ? {} : { status },
9950
+ error: {
9951
+ code,
9952
+ message: messages[code]
9953
+ }
9954
+ };
9955
+ }
9956
+ /** Internal adapter for `client.agent.context()`. Read-only; safe to retry. */
9957
+ async function getRaftContext(client) {
9958
+ const result = await client.agent.context();
9959
+ if (!result.ok) return failure$1(result.error.kind === "transport" ? "TRANSPORT_ERROR" : result.error.kind === "http" ? "HTTP_ERROR" : "INVALID_RESPONSE", result.status);
9960
+ const parsed = projection.safeParse(result.data);
9961
+ if (!parsed.success) return failure$1("INVALID_RESPONSE", result.status);
9962
+ const { agent, server, credential, prompt } = parsed.data;
9963
+ return {
9964
+ ok: true,
9965
+ status: result.status,
9966
+ data: {
9967
+ agent: { ...agent },
9968
+ server: { ...server },
9969
+ capabilities: [...credential.capabilities],
9970
+ guide: prompt?.text ?? null
9971
+ }
9972
+ };
9973
+ }
9974
+ //#endregion
9975
+ //#region src/manage.ts
9976
+ const AVATAR_MIME_TYPES = /* @__PURE__ */ new Set([
9977
+ "image/jpeg",
9978
+ "image/png",
9979
+ "image/gif",
9980
+ "image/webp"
9981
+ ]);
9982
+ const AVATAR_MAX_BYTES = 5242880;
9983
+ const SERVER_ERROR_CODE$1 = /^[A-Za-z0-9_.:-]{1,64}$/;
9984
+ const MESSAGES = {
9985
+ INVALID_REQUEST: "The request did not match the Agent API contract; nothing was sent.",
9986
+ TRANSPORT_ERROR: "The request did not reach the Raft Server.",
9987
+ HTTP_ERROR: "The Raft Server rejected the request.",
9988
+ INVALID_RESPONSE: "The Raft Server response did not match the Agent API contract."
9989
+ };
9990
+ function failure(code, status, errorCode) {
9991
+ const safeErrorCode = typeof errorCode === "string" && SERVER_ERROR_CODE$1.test(errorCode) ? errorCode : void 0;
9992
+ return {
9993
+ ok: false,
9994
+ ...status === void 0 ? {} : { status },
9995
+ error: {
9996
+ code,
9997
+ message: MESSAGES[code],
9998
+ ...safeErrorCode ? { errorCode: safeErrorCode } : {}
9999
+ }
10000
+ };
10001
+ }
10002
+ /** Map a shared-client result to the SDK's body-free result. */
10003
+ function toRaftApiResult(result) {
10004
+ if (result.ok) return {
10005
+ ok: true,
10006
+ status: result.status,
10007
+ data: result.data
10008
+ };
10009
+ const { error } = result;
10010
+ if (error.kind === "transport") return failure("TRANSPORT_ERROR");
10011
+ if (error.kind === "http") return failure("HTTP_ERROR", error.status, error.errorCode);
10012
+ return failure(error.reason === "request_contract_mismatch" || error.reason === "missing_path_param" || error.reason === "missing_route" ? "INVALID_REQUEST" : "INVALID_RESPONSE", result.status);
10013
+ }
10014
+ const AVATAR_PATH = "/internal/agent-api/profile/avatar";
10015
+ /** One multipart POST; never retried, because the upload is a write. */
10016
+ async function uploadRaftAvatar(transport, upload) {
10017
+ if (!upload || !AVATAR_MIME_TYPES.has(upload.mimeType) || typeof upload.filename !== "string" || !upload.filename.trim()) return failure("INVALID_REQUEST");
10018
+ const blob = upload.data instanceof Blob ? upload.data : upload.data instanceof Uint8Array ? new Blob([Uint8Array.from(upload.data)], { type: upload.mimeType }) : null;
10019
+ if (!blob || blob.size === 0 || blob.size > AVATAR_MAX_BYTES) return failure("INVALID_REQUEST");
10020
+ const form = new FormData();
10021
+ form.append("avatar", blob, upload.filename);
10022
+ await transport.beforeRequest?.({
10023
+ method: "POST",
10024
+ path: AVATAR_PATH
10025
+ });
10026
+ const headers = new Headers(transport.headers);
10027
+ headers.delete("content-type");
10028
+ headers.set("accept", "application/json");
10029
+ headers.set("authorization", transport.authorization);
10030
+ let response;
10031
+ try {
10032
+ response = await transport.fetch(`${transport.serverUrl}${AVATAR_PATH}`, {
10033
+ method: "POST",
10034
+ headers,
10035
+ body: form
10036
+ });
10037
+ } catch {
10038
+ return failure("TRANSPORT_ERROR");
10039
+ }
10040
+ let body = null;
10041
+ try {
10042
+ body = await response.json();
10043
+ } catch {
10044
+ body = null;
10045
+ }
10046
+ if (!response.ok) {
10047
+ const errorCode = body && typeof body === "object" ? body.errorCode : void 0;
10048
+ return failure("HTTP_ERROR", response.status, typeof errorCode === "string" ? errorCode : null);
10049
+ }
10050
+ try {
10051
+ return {
10052
+ ok: true,
10053
+ status: response.status,
10054
+ data: parseAgentApiResponse("profileAvatarUpdate", body)
10055
+ };
10056
+ } catch {
10057
+ return failure("INVALID_RESPONSE", response.status);
10058
+ }
10059
+ }
10060
+ /** `readApi` may retry; `writeApi` must make exactly one attempt. */
10061
+ function createRaftManageClient(readApi, writeApi, avatar) {
10062
+ return {
10063
+ profile: {
10064
+ show: async (request = {}) => toRaftApiResult(await readApi.profile.show(request.target === void 0 ? {} : { target: request.target })),
10065
+ update: async (request) => toRaftApiResult(await writeApi.profile.update(request)),
10066
+ updateAvatar: (upload) => uploadRaftAvatar(avatar, upload)
10067
+ },
10068
+ server: { update: async (request) => toRaftApiResult(await writeApi.server.update(request)) },
10069
+ actions: { prepare: async (request) => toRaftApiResult(await writeApi.actions.prepare(request)) },
10070
+ apps: {
10071
+ getConfig: async (appId) => toRaftApiResult(await readApi.apps.getConfig({ appId })),
10072
+ patchConfig: async (appId, patch) => toRaftApiResult(await writeApi.apps.patchConfig({ appId }, patch))
10073
+ }
10074
+ };
10075
+ }
10076
+ //#endregion
10077
+ //#region src/client.ts
10078
+ var RaftSdkConfigurationError = class extends Error {
10079
+ code;
10080
+ name = "RaftSdkConfigurationError";
10081
+ constructor(code, message) {
10082
+ super(message);
10083
+ this.code = code;
10084
+ }
10085
+ };
10086
+ function requireServerUrl(value) {
10087
+ const serverUrl = value.trim();
10088
+ if (!serverUrl) throw new RaftSdkConfigurationError("MISSING_SERVER_URL", "Raft Server URL is required");
10089
+ let parsed;
10090
+ try {
10091
+ parsed = new URL(serverUrl);
10092
+ } catch {
10093
+ throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must be an absolute HTTP(S) URL");
10094
+ }
10095
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must use HTTP or HTTPS");
10096
+ if (parsed.username || parsed.password) throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must not contain credentials");
10097
+ return serverUrl.replace(/\/+$/, "");
10098
+ }
10099
+ function requireAgentCredential(value) {
10100
+ const credential = value.trim();
10101
+ if (!credential) throw new RaftSdkConfigurationError("MISSING_AGENT_CREDENTIAL", "Raft Agent credential is required");
10102
+ if (!credential.startsWith("sk_agent_") || credential.length === 9) throw new RaftSdkConfigurationError("INVALID_AGENT_CREDENTIAL", "Raft Agent credential must be a long-lived External Agent credential");
10103
+ return credential;
10104
+ }
10105
+ /**
10106
+ * Create a typed client for the credential-derived `/internal/agent-api/*`
10107
+ * surface. The SDK does not read CLI profiles, environment variables, or the
10108
+ * host user's home directory: the caller chooses and supplies the credential.
10109
+ */
10110
+ function createRaftClient(options) {
10111
+ const serverUrl = requireServerUrl(options.serverUrl);
10112
+ const auth = { authorization: `Bearer ${requireAgentCredential(options.credential)}` };
10113
+ const messageClient = createAgentApiMessageClient({
10114
+ baseUrl: serverUrl,
7699
10115
  fetch: options.fetch,
7700
10116
  headers: options.headers,
7701
10117
  retry: options.retry,
@@ -7731,248 +10147,1675 @@ function createRaftClient(options) {
7731
10147
  auth
7732
10148
  } });
7733
10149
  return {
7734
- ...messageClient,
7735
- ...createRaftManageClient(agentApi, writeApi, {
7736
- serverUrl,
7737
- fetch: options.fetch ?? fetch,
7738
- headers: Object.fromEntries(new Headers(options.headers)),
7739
- authorization: auth.authorization,
7740
- beforeRequest: options.throttle?.beforeRequest
7741
- }),
7742
- events: { receive: (request) => receiveRaftEvents(eventsApi, request) },
7743
- channels: { join: (request) => joinRaftChannelByTarget(agentApi, request) },
7744
- agent: { context: () => getRaftContext(agentApi) }
10150
+ ...messageClient,
10151
+ ...createRaftManageClient(agentApi, writeApi, {
10152
+ serverUrl,
10153
+ fetch: options.fetch ?? fetch,
10154
+ headers: Object.fromEntries(new Headers(options.headers)),
10155
+ authorization: auth.authorization,
10156
+ beforeRequest: options.throttle?.beforeRequest
10157
+ }),
10158
+ events: { receive: (request) => receiveRaftEvents(eventsApi, request) },
10159
+ channels: { join: (request) => joinRaftChannelByTarget(agentApi, request) },
10160
+ routes: createRaftRoutes({
10161
+ serverUrl,
10162
+ fetch: options.fetch,
10163
+ headers: options.headers,
10164
+ authorization: auth.authorization,
10165
+ readAttempts: options.retry?.attempts,
10166
+ beforeRequest: options.throttle?.beforeRequest
10167
+ }),
10168
+ agent: { context: () => getRaftContext(agentApi) }
10169
+ };
10170
+ }
10171
+ //#endregion
10172
+ //#region ../shared/src/clock.ts
10173
+ function currentTimeMs() {
10174
+ return Date.now();
10175
+ }
10176
+ function currentDate() {
10177
+ return new Date(currentTimeMs());
10178
+ }
10179
+ //#endregion
10180
+ //#region src/credential.ts
10181
+ var RaftCredentialError = class extends Error {
10182
+ code;
10183
+ name = "RaftCredentialError";
10184
+ constructor(code, message) {
10185
+ super(message);
10186
+ this.code = code;
10187
+ }
10188
+ };
10189
+ function isNonEmptyString(value) {
10190
+ return typeof value === "string" && value.trim().length > 0;
10191
+ }
10192
+ function parseIdentityResponse(value) {
10193
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
10194
+ const input = value;
10195
+ if (!isNonEmptyString(input.agentId) || !isNonEmptyString(input.agentName) || input.agentDisplayName !== null && typeof input.agentDisplayName !== "string" || !isNonEmptyString(input.serverId) || !isNonEmptyString(input.credentialId) || !Array.isArray(input.scopes) || !input.scopes.every(isNonEmptyString)) return null;
10196
+ return {
10197
+ agentId: input.agentId,
10198
+ agentName: input.agentName,
10199
+ agentDisplayName: input.agentDisplayName,
10200
+ serverId: input.serverId,
10201
+ credentialId: input.credentialId,
10202
+ scopes: [...input.scopes]
10203
+ };
10204
+ }
10205
+ function parseStoredRaftCredential(value) {
10206
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
10207
+ const input = value;
10208
+ if (input.schemaVersion !== 1 || !isNonEmptyString(input.serverUrl) || !isNonEmptyString(input.credential) || !isNonEmptyString(input.storedAt)) return null;
10209
+ const identity = parseIdentityResponse(input);
10210
+ if (!identity || Number.isNaN(Date.parse(input.storedAt))) return null;
10211
+ let serverUrl;
10212
+ let credential;
10213
+ try {
10214
+ serverUrl = requireServerUrl(input.serverUrl);
10215
+ credential = requireAgentCredential(input.credential);
10216
+ } catch {
10217
+ return null;
10218
+ }
10219
+ return {
10220
+ schemaVersion: 1,
10221
+ serverUrl,
10222
+ credential,
10223
+ storedAt: input.storedAt,
10224
+ ...identity
10225
+ };
10226
+ }
10227
+ /**
10228
+ * Validate an existing External Agent credential against Raft, derive its
10229
+ * bound identity from the credential-authenticated `whoami` surface, and save
10230
+ * it through the caller's explicit store. This does not mint or rotate a
10231
+ * credential and never returns the credential bytes.
10232
+ */
10233
+ async function bootstrapRaftCredential(options) {
10234
+ const serverUrl = requireServerUrl(options.serverUrl);
10235
+ const credential = requireAgentCredential(options.credential);
10236
+ const httpFetch = options.fetch ?? globalThis.fetch;
10237
+ let existing;
10238
+ try {
10239
+ existing = await options.store.load();
10240
+ } catch {
10241
+ throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential could not be checked before saving");
10242
+ }
10243
+ if (existing) {
10244
+ const validatedExisting = parseStoredRaftCredential(existing);
10245
+ if (!validatedExisting) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential record is invalid");
10246
+ existing = validatedExisting;
10247
+ if (existing.serverUrl !== serverUrl || existing.credential !== credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
10248
+ }
10249
+ let response;
10250
+ try {
10251
+ response = await httpFetch(`${serverUrl}/internal/agent-api/`, {
10252
+ method: "GET",
10253
+ headers: { authorization: `Bearer ${credential}` }
10254
+ });
10255
+ } catch {
10256
+ throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", "Could not reach the Raft Server to verify the Agent credential");
10257
+ }
10258
+ if (!response.ok) {
10259
+ if (response.status === 401 || response.status === 403) throw new RaftCredentialError("CREDENTIAL_REJECTED", "The Raft Server rejected the Agent credential");
10260
+ throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", `The Raft Server could not verify the Agent credential (status ${response.status})`);
10261
+ }
10262
+ let payload;
10263
+ try {
10264
+ payload = await response.json();
10265
+ } catch {
10266
+ throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
10267
+ }
10268
+ const identity = parseIdentityResponse(payload);
10269
+ if (!identity) throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
10270
+ if (!identity.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The Raft Agent credential does not include the send capability required by this SDK");
10271
+ if (existing && (existing.serverId !== identity.serverId || existing.agentId !== identity.agentId)) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store is already bound to a different Raft Agent identity");
10272
+ if (existing) return {
10273
+ serverUrl,
10274
+ ...identity
10275
+ };
10276
+ const record = {
10277
+ schemaVersion: 1,
10278
+ serverUrl,
10279
+ credential,
10280
+ storedAt: currentDate().toISOString(),
10281
+ ...identity
10282
+ };
10283
+ try {
10284
+ await options.store.save(record);
10285
+ } catch (error) {
10286
+ if (error instanceof RaftCredentialError && error.code === "CREDENTIAL_STORE_CONFLICT") throw error;
10287
+ throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The verified Agent credential could not be saved");
10288
+ }
10289
+ return {
10290
+ serverUrl,
10291
+ ...identity
10292
+ };
10293
+ }
10294
+ /** Load the caller-selected credential store and create a normal Raft client. */
10295
+ async function createRaftClientFromStore(options) {
10296
+ let record;
10297
+ try {
10298
+ record = await options.store.load();
10299
+ } catch {
10300
+ throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential could not be loaded");
10301
+ }
10302
+ if (!record) throw new RaftCredentialError("CREDENTIAL_NOT_FOUND", "No stored Raft Agent credential was found");
10303
+ const validated = parseStoredRaftCredential(record);
10304
+ if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential is invalid");
10305
+ if (!validated.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The stored Raft Agent credential does not include the send capability required by this SDK");
10306
+ return createRaftClient({
10307
+ serverUrl: validated.serverUrl,
10308
+ credential: validated.credential,
10309
+ fetch: options.fetch,
10310
+ headers: options.headers,
10311
+ retry: options.retry,
10312
+ throttle: options.throttle
10313
+ });
10314
+ }
10315
+ //#endregion
10316
+ //#region src/fileCredentialStore.ts
10317
+ const MAX_CREDENTIAL_FILE_BYTES = 65536;
10318
+ /**
10319
+ * Create a Node.js credential store at one explicit absolute path. The SDK
10320
+ * never discovers CLI profiles or a home directory. Writes use a same-directory
10321
+ * temporary file followed by atomic rename, and the final file is mode 0600.
10322
+ */
10323
+ function createFileCredentialStore(filePath) {
10324
+ if (!filePath.trim() || !isAbsolute(filePath)) throw new RaftSdkConfigurationError("INVALID_CREDENTIAL_PATH", "Raft credential file path must be an explicit absolute path");
10325
+ return {
10326
+ load: () => loadFileCredential(filePath),
10327
+ async save(record) {
10328
+ const validated = parseStoredRaftCredential(record);
10329
+ if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "Refusing to write an invalid Raft Agent credential record");
10330
+ const directory = dirname(filePath);
10331
+ const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
10332
+ let handle;
10333
+ try {
10334
+ await mkdir(directory, {
10335
+ recursive: true,
10336
+ mode: 448
10337
+ });
10338
+ const existing = await loadFileCredential(filePath);
10339
+ if (existing) {
10340
+ assertSameCredential(existing, validated);
10341
+ return;
10342
+ }
10343
+ handle = await open(temporaryPath, "wx", 384);
10344
+ await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
10345
+ await handle.sync();
10346
+ await chmod(temporaryPath, 384);
10347
+ await handle.close();
10348
+ handle = void 0;
10349
+ try {
10350
+ await link(temporaryPath, filePath);
10351
+ } catch (error) {
10352
+ if (error.code !== "EEXIST") throw error;
10353
+ const winner = await loadFileCredential(filePath);
10354
+ if (!winner) throw error;
10355
+ assertSameCredential(winner, validated);
10356
+ }
10357
+ await unlink(temporaryPath);
10358
+ } catch (error) {
10359
+ if (handle) await handle.close().catch(() => void 0);
10360
+ await unlink(temporaryPath).catch(() => void 0);
10361
+ if (error instanceof RaftCredentialError) throw error;
10362
+ throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The Raft Agent credential could not be written atomically");
10363
+ }
10364
+ }
10365
+ };
10366
+ }
10367
+ async function loadFileCredential(filePath) {
10368
+ let initialMetadata;
10369
+ try {
10370
+ initialMetadata = await lstat(filePath);
10371
+ } catch (error) {
10372
+ if (error.code === "ENOENT") return null;
10373
+ throw fileReadError();
10374
+ }
10375
+ if (!initialMetadata.isFile() || initialMetadata.isSymbolicLink() || initialMetadata.size > MAX_CREDENTIAL_FILE_BYTES) throw fileReadError();
10376
+ let parsed;
10377
+ let handle;
10378
+ try {
10379
+ const noFollow = process.platform === "win32" ? 0 : constants.O_NOFOLLOW;
10380
+ handle = await open(filePath, constants.O_RDONLY | noFollow);
10381
+ const openedMetadata = await handle.stat();
10382
+ if (!openedMetadata.isFile() || openedMetadata.size > MAX_CREDENTIAL_FILE_BYTES || initialMetadata.dev !== openedMetadata.dev || initialMetadata.ino !== openedMetadata.ino) throw fileReadError();
10383
+ if (process.platform !== "win32" && (openedMetadata.mode & 63) !== 0) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file permissions are too broad; expected mode 0600");
10384
+ parsed = JSON.parse(await handle.readFile("utf8"));
10385
+ } catch (error) {
10386
+ if (error instanceof RaftCredentialError) throw error;
10387
+ throw fileReadError();
10388
+ } finally {
10389
+ await handle?.close().catch(() => void 0);
10390
+ }
10391
+ const record = parseStoredRaftCredential(parsed);
10392
+ if (!record) throw fileReadError();
10393
+ return record;
10394
+ }
10395
+ function assertSameCredential(existing, candidate) {
10396
+ if (existing.credential !== candidate.credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
10397
+ }
10398
+ function fileReadError() {
10399
+ return new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file is invalid or unreadable");
10400
+ }
10401
+ //#endregion
10402
+ //#region ../shared/src/agentOps/outcome.ts
10403
+ const SERVER_ERROR_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
10404
+ const DEFAULT_MESSAGES = {
10405
+ INVALID_REQUEST: "The request did not match the Agent API contract; nothing was sent.",
10406
+ TRANSPORT_ERROR: "The request did not reach the Raft Server.",
10407
+ HTTP_ERROR: "The Raft Server rejected the request.",
10408
+ INVALID_RESPONSE: "The Raft Server response did not match the Agent API contract.",
10409
+ CAPABILITY_NOT_AUTHORIZED: "This credential does not carry the capability this operation needs.",
10410
+ SCOPE_DENIED: "This agent's scope set does not allow this operation.",
10411
+ NOT_FOUND: "The target or message does not exist or is not visible to this agent.",
10412
+ CONFLICT: "The Raft Server refused the operation because of the current state.",
10413
+ IDEMPOTENCY_KEY_REUSED: "This idempotency key was already used for a different message.",
10414
+ UNSUPPORTED_FOR_EXTERNAL_AGENTS: "This operation is not available to External Agents.",
10415
+ UNAVAILABLE: "The Raft Server could not serve this operation right now."
10416
+ };
10417
+ const DEFAULT_NEXT_ACTION = {
10418
+ INVALID_REQUEST: "Fix the request arguments; nothing was sent.",
10419
+ TRANSPORT_ERROR: "Check connectivity to the Raft Server and retry if the operation is safe to repeat.",
10420
+ HTTP_ERROR: "Read the Server's error code and adjust the request.",
10421
+ INVALID_RESPONSE: "Upgrade the SDK or report the Server version; the response shape is not the one this SDK knows.",
10422
+ CAPABILITY_NOT_AUTHORIZED: "Ask a human who can mint credentials to include the missing capability.",
10423
+ SCOPE_DENIED: "Ask a human with editAgents authority to extend this agent's scopes.",
10424
+ NOT_FOUND: "Check the target spelling with `raft server info --channels` or resolve the message id first.",
10425
+ CONFLICT: "Read the current state before repeating this operation.",
10426
+ IDEMPOTENCY_KEY_REUSED: "Use a new idempotency key for a different message, or resend the identical payload to reconcile.",
10427
+ UNSUPPORTED_FOR_EXTERNAL_AGENTS: "Use your own runtime for this; the Server does not provide it to External Agents.",
10428
+ UNAVAILABLE: "Retry in a moment."
10429
+ };
10430
+ const RETRYABLE = {
10431
+ INVALID_REQUEST: false,
10432
+ TRANSPORT_ERROR: true,
10433
+ HTTP_ERROR: false,
10434
+ INVALID_RESPONSE: false,
10435
+ CAPABILITY_NOT_AUTHORIZED: false,
10436
+ SCOPE_DENIED: false,
10437
+ NOT_FOUND: false,
10438
+ CONFLICT: false,
10439
+ IDEMPOTENCY_KEY_REUSED: false,
10440
+ UNSUPPORTED_FOR_EXTERNAL_AGENTS: false,
10441
+ UNAVAILABLE: true
10442
+ };
10443
+ function classifyHttp(status, serverCode) {
10444
+ const code = serverCode?.toLowerCase() ?? "";
10445
+ if (code === "idempotency_key_reused") return "IDEMPOTENCY_KEY_REUSED";
10446
+ if (code === "reminders_unsupported_for_external_agents") return "UNSUPPORTED_FOR_EXTERNAL_AGENTS";
10447
+ if (code === "capability_not_authorized") return "CAPABILITY_NOT_AUTHORIZED";
10448
+ if (code === "scope_denied") return "SCOPE_DENIED";
10449
+ if (status === 404) return "NOT_FOUND";
10450
+ if (status === 409) return "CONFLICT";
10451
+ if (status === 503) return "UNAVAILABLE";
10452
+ if (status >= 500) return "UNAVAILABLE";
10453
+ return "HTTP_ERROR";
10454
+ }
10455
+ function opErrorFromClientError(error, status) {
10456
+ if (error.kind === "transport") return {
10457
+ code: "TRANSPORT_ERROR",
10458
+ message: DEFAULT_MESSAGES.TRANSPORT_ERROR,
10459
+ nextAction: DEFAULT_NEXT_ACTION.TRANSPORT_ERROR,
10460
+ retryable: true
10461
+ };
10462
+ if (error.kind === "http") {
10463
+ const serverCode = typeof error.errorCode === "string" && SERVER_ERROR_CODE.test(error.errorCode) ? error.errorCode : void 0;
10464
+ const code = classifyHttp(error.status, serverCode);
10465
+ const nextAction = typeof error.suggestedNextAction === "string" && error.suggestedNextAction.trim() ? error.suggestedNextAction.trim() : DEFAULT_NEXT_ACTION[code];
10466
+ return {
10467
+ code,
10468
+ message: DEFAULT_MESSAGES[code],
10469
+ status: error.status,
10470
+ ...serverCode ? { serverCode } : {},
10471
+ nextAction,
10472
+ retryable: RETRYABLE[code]
10473
+ };
10474
+ }
10475
+ const code = error.reason === "request_contract_mismatch" || error.reason === "missing_path_param" || error.reason === "missing_route" ? "INVALID_REQUEST" : "INVALID_RESPONSE";
10476
+ return {
10477
+ code,
10478
+ message: DEFAULT_MESSAGES[code],
10479
+ ...status === void 0 ? {} : { status },
10480
+ nextAction: DEFAULT_NEXT_ACTION[code],
10481
+ retryable: false
10482
+ };
10483
+ }
10484
+ function opError(code, overrides = {}) {
10485
+ return {
10486
+ code,
10487
+ message: DEFAULT_MESSAGES[code],
10488
+ nextAction: DEFAULT_NEXT_ACTION[code],
10489
+ retryable: RETRYABLE[code],
10490
+ ...overrides
7745
10491
  };
7746
10492
  }
10493
+ /** Render an error the way an agent should read it: what failed, the code, and the next action. */
10494
+ function formatOpErrorText(error) {
10495
+ const lines = [`Error: ${error.message}`, `Code: ${error.code}`];
10496
+ if (error.serverCode) lines.push(`Server code: ${error.serverCode}`);
10497
+ lines.push(`Retryable: ${error.retryable ? "yes" : "no"}`);
10498
+ lines.push(`Next action: ${error.nextAction}`);
10499
+ return lines.join("\n");
10500
+ }
10501
+ function failureOutcome(error) {
10502
+ return {
10503
+ ok: false,
10504
+ state: "error",
10505
+ error,
10506
+ next: {
10507
+ kind: "recover",
10508
+ why: error.nextAction
10509
+ },
10510
+ text: formatOpErrorText(error)
10511
+ };
10512
+ }
10513
+ /** Map a shared-client failure to a failure outcome. */
10514
+ function failureFromClientResult(result) {
10515
+ return failureOutcome(opErrorFromClientError(result.error, result.status));
10516
+ }
7747
10517
  //#endregion
7748
- //#region ../shared/src/clock.ts
7749
- function currentTimeMs() {
7750
- return Date.now();
10518
+ //#region ../shared/src/agentInbox.ts
10519
+ const NON_MEMBER_MENTION_REPLY_GUIDANCE = "[Raft notice: You were notified as a non-member, so you cannot reply in that channel. If no reply is needed, no action is required. Otherwise, DM the person who mentioned you or join the channel to participate.]";
10520
+ function projectAgentReplyAffordance(facts) {
10521
+ if (facts.non_member_mention !== true) return null;
10522
+ return {
10523
+ kind: "non_member_mention",
10524
+ guidance: NON_MEMBER_MENTION_REPLY_GUIDANCE
10525
+ };
7751
10526
  }
7752
- function currentDate() {
7753
- return new Date(currentTimeMs());
10527
+ function formatAgentReplyAffordance(facts) {
10528
+ return projectAgentReplyAffordance(facts)?.guidance ?? "";
10529
+ }
10530
+ function formatAgentReplyAffordanceSuffix(facts) {
10531
+ const guidance = formatAgentReplyAffordance(facts);
10532
+ return guidance ? `\n${guidance}` : "";
10533
+ }
10534
+ //#endregion
10535
+ //#region ../shared/src/raftRefs.ts
10536
+ const RAFT_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
10537
+ const RAFT_REF_USER_NAME_PATTERN = RAFT_REF_CHANNEL_NAME_PATTERN;
10538
+ const RAFT_REF_DM_PEER_PATTERN = String.raw`[\w.-]+(?:~(?:agent|human))?(?![\w.~-])`;
10539
+ const RAFT_REF_THREAD_SHORT_ID_PATTERN = String.raw`[\da-f]{6,8}`;
10540
+ const RAFT_REF_MESSAGE_ID_PATTERN = String.raw`[A-Za-z0-9][A-Za-z0-9-]{1,63}`;
10541
+ const RAFT_REF_TASK_NUMBER_PATTERN = String.raw`[1-9]\d*`;
10542
+ const RAFT_REF_COMPUTER_ID_PATTERN = String.raw`[\da-f]{8}-[\da-f]{4}-[1-5][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}`;
10543
+ const RAFT_REF_APP_ID_PATTERN = String.raw`[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+`;
10544
+ function createRaftUserRefRegex() {
10545
+ return new RegExp(`(^|[^\\p{L}\\p{N}_-])@(${RAFT_REF_USER_NAME_PATTERN})`, "gu");
10546
+ }
10547
+ function createRaftChannelThreadRefRegex() {
10548
+ return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})`, "giu");
10549
+ }
10550
+ function createRaftChannelRefRegex() {
10551
+ return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN})`, "gu");
10552
+ }
10553
+ function createRaftDmThreadRefRegex() {
10554
+ return new RegExp(`dm:@(${RAFT_REF_DM_PEER_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})`, "giu");
10555
+ }
10556
+ function createRaftDmRefRegex() {
10557
+ return new RegExp(`dm:@(${RAFT_REF_DM_PEER_PATTERN})`, "giu");
10558
+ }
10559
+ /**
10560
+ * A `dm:@name~<suffix>` token whose suffix is not a known peer kind
10561
+ * (`~bot`, `~agentXYZ`, a bare `~`). Renderers claim it as plain text so it
10562
+ * never degrades into a link to a different target (`dm:@name` or `@name`).
10563
+ * Run after the valid DM passes; it also matches valid refs.
10564
+ */
10565
+ function createRaftMalformedDmKindRefRegex() {
10566
+ return new RegExp(String.raw`dm:@[\w.-]+~[\w.~-]*(?::${RAFT_REF_THREAD_SHORT_ID_PATTERN})?`, "giu");
10567
+ }
10568
+ function createRaftMessageRefRegex() {
10569
+ return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})`, "giu");
10570
+ }
10571
+ function createRaftBareTaskRefRegex() {
10572
+ return new RegExp(`(^|[^\\w/])(?:(task\\s+))?#(${RAFT_REF_TASK_NUMBER_PATTERN})\\b`, "giu");
10573
+ }
10574
+ const USER_RE = new RegExp(String.raw`^@(${RAFT_REF_USER_NAME_PATTERN})$`, "u");
10575
+ const COMPUTER_RE = new RegExp(String.raw`^computer:(${RAFT_REF_COMPUTER_ID_PATTERN})$`, "iu");
10576
+ const APP_RE = new RegExp(String.raw`^app:(${RAFT_REF_APP_ID_PATTERN})$`, "u");
10577
+ const CHANNEL_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN})$`, "u");
10578
+ const CHANNEL_THREAD_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})$`, "iu");
10579
+ const DM_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN})$`, "iu");
10580
+ const DM_THREAD_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})$`, "iu");
10581
+ const TASK_RE = new RegExp(String.raw`^task\s+#(${RAFT_REF_TASK_NUMBER_PATTERN})$`, "iu");
10582
+ const CHANNEL_MESSAGE_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})$`, "iu");
10583
+ const DM_MESSAGE_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})$`, "iu");
10584
+ function parseRaftRefTarget(rawTarget) {
10585
+ const target = rawTarget.trim();
10586
+ if (!target) return null;
10587
+ const channelMessage = CHANNEL_MESSAGE_RE.exec(target);
10588
+ if (channelMessage) return {
10589
+ kind: "message",
10590
+ channelName: channelMessage[1],
10591
+ threadParentShortId: channelMessage[2] ?? null,
10592
+ messageId: channelMessage[3]
10593
+ };
10594
+ const dmMessage = DM_MESSAGE_RE.exec(target);
10595
+ if (dmMessage) return {
10596
+ kind: "dm-message",
10597
+ peerName: dmMessage[1],
10598
+ threadParentShortId: dmMessage[2] ?? null,
10599
+ messageId: dmMessage[3]
10600
+ };
10601
+ const channelThread = CHANNEL_THREAD_RE.exec(target);
10602
+ if (channelThread) return {
10603
+ kind: "channel-thread",
10604
+ channelName: channelThread[1],
10605
+ threadShortId: channelThread[2]
10606
+ };
10607
+ const dmThread = DM_THREAD_RE.exec(target);
10608
+ if (dmThread) return {
10609
+ kind: "dm-thread",
10610
+ peerName: dmThread[1],
10611
+ threadShortId: dmThread[2]
10612
+ };
10613
+ const task = TASK_RE.exec(target);
10614
+ if (task) return {
10615
+ kind: "task",
10616
+ taskNumber: Number(task[1])
10617
+ };
10618
+ const computer = COMPUTER_RE.exec(target);
10619
+ if (computer) return {
10620
+ kind: "computer",
10621
+ machineId: computer[1].toLowerCase()
10622
+ };
10623
+ const app = APP_RE.exec(target);
10624
+ if (app) return {
10625
+ kind: "app",
10626
+ appId: app[1]
10627
+ };
10628
+ const user = USER_RE.exec(target);
10629
+ if (user) return {
10630
+ kind: "user",
10631
+ name: user[1]
10632
+ };
10633
+ const channel = CHANNEL_RE.exec(target);
10634
+ if (channel) return {
10635
+ kind: "channel",
10636
+ channelName: channel[1]
10637
+ };
10638
+ const dm = DM_RE.exec(target);
10639
+ if (dm) return {
10640
+ kind: "dm",
10641
+ peerName: dm[1]
10642
+ };
10643
+ return null;
10644
+ }
10645
+ function formatRaftRefTarget(target) {
10646
+ switch (target.kind) {
10647
+ case "user": return `@${target.name}`;
10648
+ case "computer": return `computer:${target.machineId}`;
10649
+ case "app": return `app:${target.appId}`;
10650
+ case "channel": return `#${target.channelName}`;
10651
+ case "channel-thread": return `#${target.channelName}:${target.threadShortId}`;
10652
+ case "dm": return `dm:@${target.peerName}`;
10653
+ case "dm-thread": return `dm:@${target.peerName}:${target.threadShortId}`;
10654
+ case "task": return `task #${target.taskNumber}`;
10655
+ case "message": return target.threadParentShortId ? `#${target.channelName}:${target.threadParentShortId} msg=${target.messageId}` : `#${target.channelName} msg=${target.messageId}`;
10656
+ case "dm-message": return target.threadParentShortId ? `dm:@${target.peerName}:${target.threadParentShortId} msg=${target.messageId}` : `dm:@${target.peerName} msg=${target.messageId}`;
10657
+ }
10658
+ }
10659
+ function markdownCodeSpans(source) {
10660
+ const spans = [];
10661
+ for (const match of source.matchAll(/```[\s\S]*?```/g)) spans.push({
10662
+ start: match.index ?? 0,
10663
+ end: (match.index ?? 0) + match[0].length
10664
+ });
10665
+ const fences = [...spans];
10666
+ const scanInline = (from, to) => {
10667
+ if (from >= to) return;
10668
+ const segment = source.slice(from, to);
10669
+ for (const match of segment.matchAll(/``[^`]+``|`[^`]+`/g)) {
10670
+ const start = from + (match.index ?? 0);
10671
+ spans.push({
10672
+ start,
10673
+ end: start + match[0].length
10674
+ });
10675
+ }
10676
+ };
10677
+ let cursor = 0;
10678
+ for (const fence of fences) {
10679
+ scanInline(cursor, fence.start);
10680
+ cursor = fence.end;
10681
+ }
10682
+ scanInline(cursor, source.length);
10683
+ return spans.sort((a, b) => a.start - b.start);
10684
+ }
10685
+ function visitMarkdownProseChunks(source, visitor) {
10686
+ const spans = markdownCodeSpans(source);
10687
+ if (spans.length === 0) {
10688
+ visitor(source, 0);
10689
+ return;
10690
+ }
10691
+ let cursor = 0;
10692
+ for (const span of spans) {
10693
+ if (span.end <= cursor) continue;
10694
+ const start = span.start > cursor ? span.start : cursor;
10695
+ if (start > cursor) visitor(source.slice(cursor, start), cursor);
10696
+ cursor = span.end;
10697
+ }
10698
+ if (cursor < source.length) visitor(source.slice(cursor), cursor);
10699
+ }
10700
+ function collectParsedRef(refs, raw, start, end, dedupe) {
10701
+ const target = parseRaftRefTarget(raw);
10702
+ if (!target) return;
10703
+ const canonical = formatRaftRefTarget(target);
10704
+ refs.set(dedupe ? canonical : `${start}:${end}:${canonical}`, {
10705
+ raw,
10706
+ target,
10707
+ start,
10708
+ end
10709
+ });
10710
+ }
10711
+ function extractRaftRefTargetsFromChunk(chunk, chunkOffset, dedupe) {
10712
+ const refs = /* @__PURE__ */ new Map();
10713
+ const occupiedRanges = [];
10714
+ function occupy(match) {
10715
+ if (match.index === void 0) return;
10716
+ occupiedRanges.push({
10717
+ start: match.index,
10718
+ end: match.index + match[0].length
10719
+ });
10720
+ }
10721
+ function isInsideOccupied(match, offset = 0) {
10722
+ if (match.index === void 0) return false;
10723
+ const start = match.index + offset;
10724
+ const end = start + match[0].length - offset;
10725
+ return occupiedRanges.some((range) => start >= range.start && end <= range.end);
10726
+ }
10727
+ for (const match of chunk.matchAll(/\[((?:\\.|[^\]\\])*)\]\(<([^<>\n]+)>\)/g)) {
10728
+ const target = parseRaftRefTarget(match[2] ?? "");
10729
+ if (target?.kind === "computer" || target?.kind === "app") occupy(match);
10730
+ }
10731
+ for (const match of chunk.matchAll(new RegExp(String.raw`<([^<>\n]+)>`, "gu"))) {
10732
+ const raw = match[1] ?? "";
10733
+ const rawStart = (match.index ?? 0) + 1;
10734
+ collectParsedRef(refs, raw, chunkOffset + rawStart, chunkOffset + rawStart + raw.length, dedupe);
10735
+ occupy(match);
10736
+ }
10737
+ for (const match of chunk.matchAll(createRaftMessageRefRegex())) {
10738
+ collectParsedRef(refs, match[2] ? `#${match[1]}:${match[2]} msg=${match[3]}` : `#${match[1]} msg=${match[3]}`, chunkOffset + (match.index ?? 0), chunkOffset + (match.index ?? 0) + match[0].length, dedupe);
10739
+ occupy(match);
10740
+ }
10741
+ for (const match of chunk.matchAll(createRaftDmThreadRefRegex())) {
10742
+ collectParsedRef(refs, `dm:@${match[1]}:${match[2]}`, chunkOffset + (match.index ?? 0), chunkOffset + (match.index ?? 0) + match[0].length, dedupe);
10743
+ occupy(match);
10744
+ }
10745
+ for (const match of chunk.matchAll(createRaftDmRefRegex())) {
10746
+ if (isInsideOccupied(match)) continue;
10747
+ collectParsedRef(refs, `dm:@${match[1]}`, chunkOffset + (match.index ?? 0), chunkOffset + (match.index ?? 0) + match[0].length, dedupe);
10748
+ occupy(match);
10749
+ }
10750
+ for (const match of chunk.matchAll(createRaftMalformedDmKindRefRegex())) if (!isInsideOccupied(match)) occupy(match);
10751
+ for (const match of chunk.matchAll(createRaftChannelThreadRefRegex())) {
10752
+ if (isInsideOccupied(match)) continue;
10753
+ collectParsedRef(refs, `#${match[1]}:${match[2]}`, chunkOffset + (match.index ?? 0), chunkOffset + (match.index ?? 0) + match[0].length, dedupe);
10754
+ occupy(match);
10755
+ }
10756
+ for (const match of chunk.matchAll(createRaftBareTaskRefRegex())) {
10757
+ const raw = `task #${match[3]}`;
10758
+ const rawStart = (match.index ?? 0) + match[1].length;
10759
+ collectParsedRef(refs, raw, chunkOffset + rawStart, chunkOffset + rawStart + match[0].length - match[1].length, dedupe);
10760
+ occupy(match);
10761
+ }
10762
+ for (const match of chunk.matchAll(createRaftUserRefRegex())) {
10763
+ if (isInsideOccupied(match)) continue;
10764
+ const rawStart = (match.index ?? 0) + match[1].length;
10765
+ collectParsedRef(refs, `@${match[2]}`, chunkOffset + rawStart, chunkOffset + (match.index ?? 0) + match[0].length, dedupe);
10766
+ occupy(match);
10767
+ }
10768
+ for (const match of chunk.matchAll(createRaftChannelRefRegex())) {
10769
+ if (isInsideOccupied(match)) continue;
10770
+ collectParsedRef(refs, `#${match[1]}`, chunkOffset + (match.index ?? 0), chunkOffset + (match.index ?? 0) + match[0].length, dedupe);
10771
+ occupy(match);
10772
+ }
10773
+ return refs;
10774
+ }
10775
+ function extractRaftRefTargets(source, options = {}) {
10776
+ const refs = /* @__PURE__ */ new Map();
10777
+ const dedupe = options.dedupe ?? true;
10778
+ const collectChunk = (chunk, offset) => {
10779
+ const chunkRefs = extractRaftRefTargetsFromChunk(chunk, offset, dedupe);
10780
+ for (const [key, value] of chunkRefs) refs.set(key, value);
10781
+ };
10782
+ if (options.includeMarkdownCode) collectChunk(source, 0);
10783
+ else visitMarkdownProseChunks(source, collectChunk);
10784
+ return [...refs.values()].sort((a, b) => a.start - b.start || a.end - b.end);
10785
+ }
10786
+ //#endregion
10787
+ //#region ../shared/src/thirdPartyInertRenderer.ts
10788
+ /**
10789
+ * Shared inert renderer for app-controlled third-party text.
10790
+ *
10791
+ * Contract:
10792
+ * - Input is app-controlled free text such as app name, developer display name,
10793
+ * description, and declared data-access wording.
10794
+ * - Output is canonical agent-facing text. It is safe to place in marketplace,
10795
+ * install disclosure, app metadata, and tool-result readouts.
10796
+ * - Markdown inline/fenced code is literal data; ref-shaped text inside it is
10797
+ * never parsed or rewritten.
10798
+ * - Structured values such as normalized domains, callback URLs, categories, and
10799
+ * closed scope enums do not belong here; those should be rendered from
10800
+ * server-normalized values at the call site.
10801
+ */
10802
+ function escapeAgentMarkupLiterals(value) {
10803
+ return value.replace(/<\/?(?:result|preview|match)\b[^>]*>|<omit\s*\/>/gi, (tag) => tag.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
10804
+ }
10805
+ function neutralizeRaftRefLiterals(value) {
10806
+ const refs = extractRaftRefTargets(value, {
10807
+ dedupe: false,
10808
+ includeMarkdownCode: false
10809
+ }).filter((ref) => ref.start < ref.end).sort((a, b) => a.start - b.start || b.end - a.end);
10810
+ if (refs.length === 0) return value;
10811
+ let output = "";
10812
+ let cursor = 0;
10813
+ for (let index = 0; index < refs.length;) {
10814
+ const first = refs[index];
10815
+ if (first.start < cursor) {
10816
+ index++;
10817
+ continue;
10818
+ }
10819
+ const cluster = [first];
10820
+ let clusterEnd = first.end;
10821
+ index++;
10822
+ while (index < refs.length && refs[index].start < clusterEnd) {
10823
+ const next = refs[index];
10824
+ cluster.push(next);
10825
+ clusterEnd = Math.max(clusterEnd, next.end);
10826
+ index++;
10827
+ }
10828
+ output += value.slice(cursor, first.start);
10829
+ output += cluster.map((ref) => neutralRaftRefLabel(ref.target)).join(" ");
10830
+ cursor = clusterEnd;
10831
+ }
10832
+ output += value.slice(cursor);
10833
+ return output;
10834
+ }
10835
+ function neutralRaftRefLabel(target) {
10836
+ switch (target.kind) {
10837
+ case "user": return `user:${target.name}`;
10838
+ case "computer": return `computer:${target.machineId}`;
10839
+ case "app": return `app:${target.appId}`;
10840
+ case "channel": return `channel:${target.channelName}`;
10841
+ case "channel-thread": return `channel:${target.channelName}:${target.threadShortId}`;
10842
+ case "dm": return `dm:user:${target.peerName}`;
10843
+ case "dm-thread": return `dm:user:${target.peerName}:${target.threadShortId}`;
10844
+ case "task": return `task:${target.taskNumber}`;
10845
+ case "message": return target.threadParentShortId ? `channel:${target.channelName}:${target.threadParentShortId} msg=${target.messageId}` : `channel:${target.channelName} msg=${target.messageId}`;
10846
+ case "dm-message": return target.threadParentShortId ? `dm:user:${target.peerName}:${target.threadParentShortId} msg=${target.messageId}` : `dm:user:${target.peerName} msg=${target.messageId}`;
10847
+ }
10848
+ }
10849
+ /** Render an ingress-bounded JSON payload without hiding fields from the agent. */
10850
+ function renderThirdPartyInertJson(value) {
10851
+ return neutralizeRaftRefLiterals(escapeAgentMarkupLiterals(JSON.stringify(value, null, 2)));
10852
+ }
10853
+ //#endregion
10854
+ //#region ../shared/src/utcTimestamp.ts
10855
+ /**
10856
+ * Format an agent-facing timestamp as an explicitly-labelled UTC value.
10857
+ *
10858
+ * Keep the space separator used by existing Raft message/log output while
10859
+ * adding the `Z` suffix that makes the timezone unambiguous.
10860
+ */
10861
+ function formatUtcTimestamp(value) {
10862
+ const date = value instanceof Date ? value : new Date(value);
10863
+ if (Number.isNaN(date.getTime())) return typeof value === "string" ? value : String(value);
10864
+ return `${date.toISOString().slice(0, 19).replace("T", " ")}Z`;
10865
+ }
10866
+ //#endregion
10867
+ //#region ../shared/src/agentMessageText.ts
10868
+ /**
10869
+ * The target an agent passes back to reply where a message came from:
10870
+ * `#channel`, `#channel:shortid`, `dm:@peer`, `dm:@peer:shortid`, or
10871
+ * `agent-event:shortid` for third-party app events.
10872
+ */
10873
+ function formatAgentMessageTarget(m) {
10874
+ if (m.third_party_event) return `agent-event:${m.third_party_event.id.slice(0, 8)}`;
10875
+ if (m.channel_type === "thread" && m.parent_channel_name) {
10876
+ const shortId = m.channel_name?.startsWith("thread-") ? m.channel_name.slice(7) : m.channel_name;
10877
+ if (m.parent_channel_type === "dm") return `dm:@${m.parent_channel_name}:${shortId}`;
10878
+ return `#${m.parent_channel_name}:${shortId}`;
10879
+ }
10880
+ if (m.channel_type === "dm") return `dm:@${m.channel_name}`;
10881
+ return `#${m.channel_name}`;
10882
+ }
10883
+ function formatAgentSenderHandle(m) {
10884
+ const name = m.sender_name ?? "unknown";
10885
+ const desc = m.sender_description ?? null;
10886
+ return desc ? `@${name} — ${desc}` : `@${name}`;
10887
+ }
10888
+ function formatAgentAttachmentSuffix(attachments) {
10889
+ if (!attachments?.length) return "";
10890
+ return ` [${attachments.length} attachment${attachments.length > 1 ? "s" : ""}: ${attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} — use raft attachment view to download]`;
10891
+ }
10892
+ function formatAgentTaskAssigneeSuffix(assigneeId, assigneeName) {
10893
+ if (!assigneeId) return "";
10894
+ return assigneeName ? ` assignee=@${assigneeName}` : " assignee=<unresolved>";
10895
+ }
10896
+ /**
10897
+ * Superseded-task projection suffix. `neutralize`, when given, rewrites quoted
10898
+ * title/description/actor text so pasted refs cannot route attention (the CLI
10899
+ * passes its search-preview neutraliser); the actor handle is then `user:<name>`.
10900
+ */
10901
+ function formatAgentTaskCurrentProjection(projection, taskNumber, neutralize) {
10902
+ const neutralizeRefs = neutralize !== void 0;
10903
+ const renderPreviewText = neutralize ?? ((text) => text);
10904
+ if (!projection?.superseded) return "";
10905
+ const revision = Number.isInteger(projection.revision) ? projection.revision : "?";
10906
+ const source = projection.source ?? "tasks_current_projection";
10907
+ const actorName = projection.amendedByName ?? projection.amended_by_name ?? null;
10908
+ const actorType = projection.amendedByType ?? projection.amended_by_type ?? null;
10909
+ const actor = actorName ? neutralizeRefs ? `user:${actorName}` : `@${actorName}` : actorType === "system" ? "system" : "<unresolved>";
10910
+ const amendedAt = projection.amendedAt ?? projection.amended_at ?? null;
10911
+ const lines = [`[${taskNumber ? `task #${taskNumber} ` : "task "}superseded: current projection rev=${revision} source=${source} actor=${actor} time=${amendedAt ? formatUtcTimestamp(amendedAt) : "-"}]`, `Current title: ${neutralizeRefs ? renderPreviewText(projection.title ?? "") : projection.title ?? ""}`];
10912
+ if (projection.description != null) lines.push(`Current description: ${neutralizeRefs ? renderPreviewText(projection.description) : projection.description}`);
10913
+ return `\n${lines.join("\n")}`;
10914
+ }
10915
+ /** One received-message line: header bracket + sender + content + suffixes. */
10916
+ function formatAgentMessageLine(m) {
10917
+ if (m.third_party_event) {
10918
+ const msgId = m.message_id ? m.message_id.slice(0, 8) : m.third_party_event.id.slice(0, 8);
10919
+ const time = m.timestamp ? formatUtcTimestamp(m.timestamp) : "-";
10920
+ const event = m.third_party_event;
10921
+ const content = m.content ?? "";
10922
+ const source = event.source;
10923
+ const sourceSuffix = source?.resource ? `; resource=${source.resource}${source.access_token_id_hash ? `; access_token_id_hash=${source.access_token_id_hash}` : ""}` : "";
10924
+ const provenance = `kind=${event.kind}; payload_hash=${event.payload_hash}${sourceSuffix}`;
10925
+ return `[target=agent-event:${event.id.slice(0, 8)} msg=${msgId} time=${time} type=third_party_app] @${event.client_id} — ${event.client_name}: ${provenance}\n${content}${event.payload ? `\npayload:\n${renderThirdPartyInertJson(event.payload)}` : ""}`;
10926
+ }
10927
+ const target = formatAgentMessageTarget(m);
10928
+ const msgId = m.message_id ? m.message_id.slice(0, 8) : "-";
10929
+ const time = m.timestamp ? formatUtcTimestamp(m.timestamp) : "-";
10930
+ const senderType = ` type=${m.sender_type}`;
10931
+ const content = m.content ?? "";
10932
+ const attachSuffix = formatAgentAttachmentSuffix(m.attachments);
10933
+ const taskSuffix = m.task_status ? ` [task #${m.task_number} status=${m.task_status}${formatAgentTaskAssigneeSuffix(m.task_assignee_id, m.task_assignee_name)}]` : "";
10934
+ return `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatAgentSenderHandle(m)}: ${content}${attachSuffix}${taskSuffix}${formatAgentReplyAffordanceSuffix(m)}${formatAgentTaskCurrentProjection(m.task_current_projection)}`;
10935
+ }
10936
+ /** Batch of received-message lines (`raft message check` output). */
10937
+ function formatAgentMessages(messages) {
10938
+ if (messages.length === 0) return "No new inbox messages.";
10939
+ return messages.map(formatAgentMessageLine).join("\n");
10940
+ }
10941
+ /**
10942
+ * `message check` hands over a bounded batch, oldest first per conversation;
10943
+ * the server reports how many conversations still have unread after it.
10944
+ */
10945
+ function formatAgentInboxHint(hint) {
10946
+ const n = hint.unread_conversations;
10947
+ return `Still unread: ${n} ${n === 1 ? "conversation" : "conversations"}. Run \`raft inbox check\` to list them.`;
10948
+ }
10949
+ //#endregion
10950
+ //#region ../shared/src/agentOps/message.ts
10951
+ function senderType(value) {
10952
+ return value === "human" || value === "agent" || value === "system" || value === "third_party_app" ? value : "unknown";
10953
+ }
10954
+ function nullableNumber(value) {
10955
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
10956
+ }
10957
+ function nullableString(value) {
10958
+ return typeof value === "string" ? value : null;
10959
+ }
10960
+ /** Normalise an envelope (snake_case wire keys win, camelCase legacy keys fill in) into the formatter's shape. */
10961
+ function toAgentMessageLike(envelope) {
10962
+ return {
10963
+ ...envelope,
10964
+ message_id: envelope.message_id ?? envelope.id,
10965
+ timestamp: envelope.timestamp ?? envelope.createdAt,
10966
+ sender_type: envelope.sender_type ?? envelope.senderType,
10967
+ sender_name: envelope.sender_name ?? envelope.senderName,
10968
+ sender_description: envelope.sender_description ?? envelope.senderDescription ?? null,
10969
+ task_status: envelope.task_status ?? envelope.taskStatus ?? null,
10970
+ task_number: envelope.task_number ?? envelope.taskNumber ?? null,
10971
+ task_assignee_id: envelope.task_assignee_id ?? envelope.taskAssigneeId ?? null,
10972
+ task_assignee_type: envelope.task_assignee_type ?? envelope.taskAssigneeType ?? null,
10973
+ task_assignee_name: envelope.task_assignee_name ?? envelope.taskAssigneeName ?? null,
10974
+ task_current_projection: envelope.task_current_projection ?? envelope.taskCurrentProjection ?? null
10975
+ };
10976
+ }
10977
+ function projectRaftMessage(envelope) {
10978
+ const like = toAgentMessageLike(envelope);
10979
+ const id = nullableString(like.message_id);
10980
+ const taskNumber = nullableNumber(like.task_number);
10981
+ const taskStatus = nullableString(like.task_status);
10982
+ const raw = envelope;
10983
+ return {
10984
+ id,
10985
+ shortId: id ? id.slice(0, 8) : null,
10986
+ seq: nullableNumber(envelope.seq),
10987
+ target: formatAgentMessageTarget(like),
10988
+ timestamp: nullableString(like.timestamp),
10989
+ sender: {
10990
+ type: senderType(like.sender_type),
10991
+ name: nullableString(like.sender_name),
10992
+ description: nullableString(like.sender_description)
10993
+ },
10994
+ content: envelope.content ?? "",
10995
+ attachments: (envelope.attachments ?? []).map((a) => ({
10996
+ id: a.id,
10997
+ filename: a.filename
10998
+ })),
10999
+ mentioned: typeof raw.mentioned === "boolean" ? raw.mentioned : null,
11000
+ task: taskNumber !== null || taskStatus !== null ? {
11001
+ number: taskNumber,
11002
+ status: taskStatus,
11003
+ assigneeName: nullableString(like.task_assignee_name)
11004
+ } : null,
11005
+ thread: {
11006
+ id: nullableString(envelope.threadId),
11007
+ replyCount: nullableNumber(envelope.replyCount)
11008
+ },
11009
+ text: formatAgentMessageLine(like),
11010
+ raw: envelope
11011
+ };
11012
+ }
11013
+ function sortBySeq(messages) {
11014
+ return [...messages].sort((a, b) => (a.seq ?? Number.MAX_SAFE_INTEGER) - (b.seq ?? Number.MAX_SAFE_INTEGER));
11015
+ }
11016
+ //#endregion
11017
+ //#region ../shared/src/agentOps/frontier.ts
11018
+ const MAX_EXACT_SEQS = 2500;
11019
+ function positiveInt(value) {
11020
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
11021
+ }
11022
+ var SeenFrontier = class SeenFrontier {
11023
+ targets = /* @__PURE__ */ new Map();
11024
+ aliases = /* @__PURE__ */ new Map();
11025
+ static fromSnapshot(snapshot) {
11026
+ const frontier = new SeenFrontier();
11027
+ if (!snapshot || snapshot.version !== 1) return frontier;
11028
+ for (const [target, state] of Object.entries(snapshot.targets ?? {})) {
11029
+ if (positiveInt(state.upTo)) frontier.recordUpTo(target, state.upTo);
11030
+ if (Array.isArray(state.exact)) frontier.recordExact(target, state.exact);
11031
+ }
11032
+ for (const [alias, canonical] of Object.entries(snapshot.aliases ?? {})) frontier.recordAlias(alias, canonical);
11033
+ return frontier;
11034
+ }
11035
+ /** Remember that the Server resolved `requested` to `canonical` (a thread spelling, a `~agent` suffix, …). */
11036
+ recordAlias(requested, canonical) {
11037
+ if (requested && canonical && requested !== canonical) this.aliases.set(requested, canonical);
11038
+ }
11039
+ canonical(target) {
11040
+ return this.aliases.get(target) ?? target;
11041
+ }
11042
+ /** Advance the contiguous boundary for a target; never lowers it. */
11043
+ recordUpTo(target, seq) {
11044
+ if (!positiveInt(seq)) return;
11045
+ const state = this.state(this.canonical(target));
11046
+ if (state.upTo === void 0 || seq > state.upTo) state.upTo = seq;
11047
+ for (const exact of state.exact) if (exact <= state.upTo) state.exact.delete(exact);
11048
+ }
11049
+ /** Record bodies that were rendered without proving contiguity. */
11050
+ recordExact(target, seqs) {
11051
+ const state = this.state(this.canonical(target));
11052
+ for (const seq of seqs) {
11053
+ if (!positiveInt(seq)) continue;
11054
+ if (state.upTo !== void 0 && seq <= state.upTo) continue;
11055
+ state.exact.add(seq);
11056
+ }
11057
+ if (state.exact.size > MAX_EXACT_SEQS) {
11058
+ const keep = [...state.exact].sort((a, b) => b - a).slice(0, MAX_EXACT_SEQS);
11059
+ state.exact.clear();
11060
+ for (const seq of keep) state.exact.add(seq);
11061
+ }
11062
+ }
11063
+ /** What to attest on a send or claim to `target`; omits `seenUpToSeq` when nothing contiguous is known. */
11064
+ attestation(target) {
11065
+ const state = this.targets.get(this.canonical(target));
11066
+ if (!state) return { seenExactSeqs: [] };
11067
+ return {
11068
+ ...state.upTo === void 0 ? {} : { seenUpToSeq: state.upTo },
11069
+ seenExactSeqs: [...state.exact].sort((a, b) => a - b)
11070
+ };
11071
+ }
11072
+ snapshot() {
11073
+ const targets = {};
11074
+ for (const [target, state] of this.targets) targets[target] = {
11075
+ ...state.upTo === void 0 ? {} : { upTo: state.upTo },
11076
+ ...state.exact.size === 0 ? {} : { exact: [...state.exact].sort((a, b) => a - b) }
11077
+ };
11078
+ return {
11079
+ version: 1,
11080
+ targets,
11081
+ aliases: Object.fromEntries(this.aliases)
11082
+ };
11083
+ }
11084
+ state(target) {
11085
+ let state = this.targets.get(target);
11086
+ if (!state) {
11087
+ state = { exact: /* @__PURE__ */ new Set() };
11088
+ this.targets.set(target, state);
11089
+ }
11090
+ return state;
11091
+ }
11092
+ };
11093
+ //#endregion
11094
+ //#region ../shared/src/agentOps/inbox.ts
11095
+ const MAX_DRAIN_ROUNDS = 50;
11096
+ function batchNext(batch) {
11097
+ if (batch.hasMore) return {
11098
+ kind: "check_inbox_again",
11099
+ command: "raft message check",
11100
+ ...batch.cursor === null ? {} : { args: { since: batch.cursor } },
11101
+ why: "The Server trimmed this batch; more messages are pending."
11102
+ };
11103
+ if ((batch.stillUnreadConversations ?? 0) > 0) return {
11104
+ kind: "list_inbox",
11105
+ command: "raft inbox check",
11106
+ why: "Conversations remain unread beyond this batch."
11107
+ };
11108
+ if (batch.messages.length > 0) return {
11109
+ kind: "reply_or_act",
11110
+ args: { target: batch.messages[0].target },
11111
+ why: "Handle the messages above; reply where each one came from."
11112
+ };
11113
+ return null;
11114
+ }
11115
+ function batchText(batch) {
11116
+ const lines = [formatAgentMessages(batch.messages.map((m) => toAgentMessageLike(m.raw)))];
11117
+ if (batch.hasMore) lines.push("More messages are pending. Run `raft message check` again.");
11118
+ else if (batch.messages.length > 0) lines.push("No more new inbox messages.");
11119
+ if ((batch.stillUnreadConversations ?? 0) > 0) lines.push(formatAgentInboxHint({ unread_conversations: batch.stillUnreadConversations }));
11120
+ return lines.join("\n");
11121
+ }
11122
+ function recordExactSeen(frontier, messages) {
11123
+ if (!frontier) return;
11124
+ const byTarget = /* @__PURE__ */ new Map();
11125
+ for (const message of messages) {
11126
+ if (message.seq === null) continue;
11127
+ byTarget.set(message.target, [...byTarget.get(message.target) ?? [], message.seq]);
11128
+ }
11129
+ for (const [target, seqs] of byTarget) frontier.recordExact(target, seqs);
11130
+ }
11131
+ /** One bounded pull. Records exact seen seqs on `frontier` (sparse drains never advance the high-water mark). */
11132
+ async function checkInbox(client, request = {}, frontier) {
11133
+ const ack = request.ack ?? "cursor";
11134
+ const result = await client.events.get({
11135
+ since: request.since === void 0 ? "latest" : String(request.since),
11136
+ ...request.limit === void 0 ? {} : { limit: String(request.limit) },
11137
+ ack
11138
+ });
11139
+ if (!result.ok) return failureFromClientResult(result);
11140
+ const data = result.data;
11141
+ const messages = sortBySeq(data.events.map(projectRaftMessage));
11142
+ recordExactSeen(frontier, messages);
11143
+ const batch = {
11144
+ messages,
11145
+ cursor: typeof data.last_seen_seq === "number" ? data.last_seen_seq : null,
11146
+ ackMode: data.ack_mode ?? "immediate",
11147
+ hasMore: data.has_more === true,
11148
+ stillUnreadConversations: data.inbox_hint?.unread_conversations ?? null,
11149
+ replyTarget: data.reply_target ?? null
11150
+ };
11151
+ return {
11152
+ ok: true,
11153
+ state: messages.length > 0 ? "batch" : "empty",
11154
+ data: batch,
11155
+ next: batchNext(batch),
11156
+ text: batchText(batch)
11157
+ };
11158
+ }
11159
+ /**
11160
+ * Pull until the Server reports nothing more, like `raft message check`, but
11161
+ * as an async iterator so that acknowledgement follows consumption: under
11162
+ * cursor acks, the pull that acknowledges batch N is only sent when the
11163
+ * consumer asks for batch N+1, i.e. after it has processed batch N. A consumer
11164
+ * that stops midway leaves the current batch unacknowledged, and a later pull
11165
+ * without a cursor returns it again. Capped at 50 rounds. The generator's
11166
+ * return value summarises the drain; the batch that ends the iteration is
11167
+ * acknowledged only by the caller's next pull (`summary.cursor`).
11168
+ */
11169
+ async function* drainInbox(client, request = {}, frontier) {
11170
+ const ack = request.ack ?? "cursor";
11171
+ let cursor = request.since ?? null;
11172
+ let ackMode = ack;
11173
+ let stillUnread = null;
11174
+ let rounds = 0;
11175
+ while (rounds < MAX_DRAIN_ROUNDS) {
11176
+ const sent = cursor;
11177
+ rounds += 1;
11178
+ const round = await checkInbox(client, {
11179
+ ...sent === null ? {} : { since: sent },
11180
+ limit: request.limit,
11181
+ ack
11182
+ }, frontier);
11183
+ if (!round.ok) return {
11184
+ cursor,
11185
+ ackMode,
11186
+ rounds,
11187
+ hasMore: true,
11188
+ stillUnreadConversations: stillUnread,
11189
+ error: round
11190
+ };
11191
+ const batch = round.data;
11192
+ ackMode = batch.ackMode;
11193
+ stillUnread = batch.stillUnreadConversations;
11194
+ if (batch.messages.length > 0) {
11195
+ cursor = batch.cursor ?? sent;
11196
+ yield batch;
11197
+ if (ack === "cursor" && batch.ackMode === "cursor") continue;
11198
+ if (batch.hasMore) continue;
11199
+ return {
11200
+ cursor,
11201
+ ackMode,
11202
+ rounds,
11203
+ hasMore: false,
11204
+ stillUnreadConversations: stillUnread,
11205
+ error: null
11206
+ };
11207
+ }
11208
+ if (ack === "cursor" && batch.ackMode === "cursor" && batch.cursor !== null && batch.cursor !== sent) {
11209
+ cursor = batch.cursor;
11210
+ continue;
11211
+ }
11212
+ return {
11213
+ cursor,
11214
+ ackMode,
11215
+ rounds,
11216
+ hasMore: batch.hasMore,
11217
+ stillUnreadConversations: stillUnread,
11218
+ error: null
11219
+ };
11220
+ }
11221
+ return {
11222
+ cursor,
11223
+ ackMode,
11224
+ rounds,
11225
+ hasMore: true,
11226
+ stillUnreadConversations: stillUnread,
11227
+ error: null
11228
+ };
11229
+ }
11230
+ function openCommandFor(item) {
11231
+ return `raft message read --target "${item.target}" --after ${item.lastReadSeq}`;
11232
+ }
11233
+ function listingNext(listing) {
11234
+ const first = listing.conversations[0];
11235
+ if (first) return {
11236
+ kind: "read_target",
11237
+ command: first.openCommand,
11238
+ args: {
11239
+ target: first.target,
11240
+ after: first.lastReadSeq
11241
+ },
11242
+ why: "Open the newest unread conversation from your read position."
11243
+ };
11244
+ return null;
11245
+ }
11246
+ function listingText(listing) {
11247
+ const t = listing.totals;
11248
+ const head = t.conversations === 0 ? "Inbox: nothing unread." : `Inbox: ${t.conversations} unread ${t.conversations === 1 ? "conversation" : "conversations"} (${t.dms} DMs, ${t.mentions} with mentions).${listing.conversations.length > 0 ? " Newest activity first." : ""}`;
11249
+ const rows = listing.conversations.map((c) => {
11250
+ const flags = [c.mentions > 0 ? "mentions you" : null, c.kind === "thread" ? "thread" : null].filter(Boolean).join(" · ");
11251
+ return `${c.target} · ${c.unread} unread${flags ? ` · ${flags}` : ""}${c.latestSenderName ? ` · latest @${c.latestSenderName}` : ""}\n open: ${c.openCommand}`;
11252
+ });
11253
+ const trailer = [];
11254
+ if (listing.hasMore && listing.nextBeforeSeq !== null) trailer.push(`More: raft inbox check${listing.view === "mentions" ? " --view mentions" : ""} --before ${listing.nextBeforeSeq}`);
11255
+ const next = listingNext(listing);
11256
+ trailer.push(next?.command ? `Next: open the first conversation above: ${next.command}` : "Next: nothing to do.");
11257
+ return [
11258
+ head,
11259
+ ...rows,
11260
+ ...trailer
11261
+ ].join("\n");
11262
+ }
11263
+ /** The agent's Activity panel. Lists only; nothing is consumed. */
11264
+ async function listInbox(client, request = {}) {
11265
+ const result = await client.inbox.list({
11266
+ ...request.view && request.view !== "unread" ? { view: request.view } : {},
11267
+ ...request.before === void 0 ? {} : { before_seq: String(request.before) },
11268
+ ...request.limit === void 0 ? {} : { limit: String(request.limit) }
11269
+ });
11270
+ if (!result.ok) return failureFromClientResult(result);
11271
+ const data = result.data;
11272
+ const listing = {
11273
+ view: data.view,
11274
+ conversations: data.items.map((item) => ({
11275
+ target: item.target,
11276
+ kind: item.kind,
11277
+ unread: item.unread,
11278
+ mentions: item.mentions,
11279
+ lastReadSeq: item.lastReadSeq,
11280
+ activitySeq: item.activitySeq,
11281
+ latestSenderName: item.latestSenderName,
11282
+ latestAt: item.latestAt,
11283
+ openCommand: openCommandFor(item)
11284
+ })),
11285
+ totals: data.totals,
11286
+ hasMore: data.hasMore,
11287
+ nextBeforeSeq: data.nextBeforeSeq
11288
+ };
11289
+ return {
11290
+ ok: true,
11291
+ state: listing.conversations.length > 0 ? "listed" : "empty",
11292
+ data: listing,
11293
+ next: listingNext(listing),
11294
+ text: listingText(listing)
11295
+ };
11296
+ }
11297
+ //#endregion
11298
+ //#region ../shared/src/agentOps/messages.ts
11299
+ function historyNext(page) {
11300
+ const seqs = page.messages.map((m) => m.seq).filter((s) => s !== null);
11301
+ if (page.hasNewer && seqs.length > 0) {
11302
+ const max = Math.max(...seqs);
11303
+ return {
11304
+ kind: "read_newer",
11305
+ command: `raft message read --target "${page.target}" --after ${max}`,
11306
+ args: {
11307
+ target: page.target,
11308
+ after: max
11309
+ },
11310
+ why: "Newer messages exist beyond this window."
11311
+ };
11312
+ }
11313
+ if (page.hasOlder && seqs.length > 0) {
11314
+ const min = Math.min(...seqs);
11315
+ return {
11316
+ kind: "read_older",
11317
+ command: `raft message read --target "${page.target}" --before ${min}`,
11318
+ args: {
11319
+ target: page.target,
11320
+ before: min
11321
+ },
11322
+ why: "Older messages exist before this window; read them only if the task needs them."
11323
+ };
11324
+ }
11325
+ return null;
11326
+ }
11327
+ function historyText(page) {
11328
+ if (page.messages.length === 0) return `No messages in ${page.target}.`;
11329
+ const lines = [formatAgentMessages(page.messages.map((m) => toAgentMessageLike(m.raw)))];
11330
+ const next = historyNext(page);
11331
+ if (next?.command) lines.push(`${next.kind === "read_newer" ? "Newer" : "Older"} exist: ${next.command}`);
11332
+ return lines.join("\n");
11333
+ }
11334
+ /**
11335
+ * Frontier rules (FH-EXT-001, mirrored from the CLI): a contiguous window may
11336
+ * advance the high-water mark to the Server's `model_seen_up_to_seq`; an
11337
+ * `around` lookup and a legacy Server without that field record exact seqs.
11338
+ */
11339
+ function recordHistorySeen(frontier, requested, page, around) {
11340
+ if (!frontier) return;
11341
+ frontier.recordAlias(requested, page.target);
11342
+ const seqs = page.messages.map((m) => m.seq).filter((s) => s !== null);
11343
+ if (seqs.length === 0) return;
11344
+ if (!around && page.modelSeenUpToSeq !== null && page.modelSeenUpToSeq > 0) {
11345
+ frontier.recordUpTo(page.target, page.modelSeenUpToSeq);
11346
+ return;
11347
+ }
11348
+ frontier.recordExact(page.target, seqs);
11349
+ }
11350
+ async function readHistory(client, request, frontier) {
11351
+ if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target is required to read history." }));
11352
+ const result = await client.history.read({
11353
+ channel: request.target,
11354
+ ...request.after === void 0 ? {} : { after: String(request.after) },
11355
+ ...request.before === void 0 ? {} : { before: String(request.before) },
11356
+ ...request.around === void 0 ? {} : { around: String(request.around) },
11357
+ ...request.limit === void 0 ? {} : { limit: String(request.limit) }
11358
+ });
11359
+ if (!result.ok) return failureFromClientResult(result);
11360
+ const data = result.data;
11361
+ const page = {
11362
+ target: typeof data.target === "string" && data.target ? data.target : request.target,
11363
+ messages: sortBySeq(data.messages.map(projectRaftMessage)),
11364
+ hasOlder: data.has_older === true,
11365
+ hasNewer: data.has_newer === true,
11366
+ lastReadSeq: typeof data.last_read_seq === "number" ? data.last_read_seq : null,
11367
+ modelSeenUpToSeq: typeof data.model_seen_up_to_seq === "number" ? data.model_seen_up_to_seq : null
11368
+ };
11369
+ recordHistorySeen(frontier, request.target, page, request.around !== void 0);
11370
+ return {
11371
+ ok: true,
11372
+ state: page.messages.length > 0 ? "page" : "empty",
11373
+ data: page,
11374
+ next: historyNext(page),
11375
+ text: historyText(page)
11376
+ };
11377
+ }
11378
+ /** Attempts for a keyed send whose request never reached the Server (transport failure only). */
11379
+ const SEND_TRANSPORT_ATTEMPTS = 3;
11380
+ function heldText(held, action) {
11381
+ const noun = held.newMessageCount === 1 ? "message" : "messages";
11382
+ const head = `Held — ${held.newMessageCount} unread ${noun} in ${held.target}. ${action}`;
11383
+ if (held.withheld) return `${head}\nContext withheld (reviewer isolation).`;
11384
+ const lines = [head];
11385
+ if (held.formalMentionCount > 0) lines.push(`Note: ${held.formalMentionCount} of these messages formally @mention you.`);
11386
+ if (held.omittedMessageCount > 0) lines.push(`${held.omittedMessageCount} earlier ${held.omittedMessageCount === 1 ? "message" : "messages"} not shown.`);
11387
+ if (held.heldMessages.length > 0) lines.push(formatAgentMessages(held.heldMessages.map((m) => toAgentMessageLike(m.raw))));
11388
+ lines.push(`Full text: raft message read --target "${held.target}"`);
11389
+ return lines.join("\n");
11390
+ }
11391
+ function heldNext(held) {
11392
+ return {
11393
+ kind: "review_then_resend",
11394
+ command: `raft message read --target "${held.target}"`,
11395
+ args: {
11396
+ target: held.target,
11397
+ newMessageCount: held.newMessageCount
11398
+ },
11399
+ why: "Newer messages arrived in this conversation; read them, then resend if the message still applies."
11400
+ };
11401
+ }
11402
+ function projectHeld(target, data) {
11403
+ const withheld = data.freshnessContextMode === "withheld";
11404
+ const heldMessages = (data.heldMessages ?? []).map(projectRaftMessage);
11405
+ return {
11406
+ target,
11407
+ newMessageCount: withheld ? data.withheldMessageCount ?? 0 : data.newMessageCount ?? heldMessages.length,
11408
+ heldMessages,
11409
+ omittedMessageCount: data.omittedMessageCount ?? 0,
11410
+ formalMentionCount: data.mentionAnnotation?.formalMentionCount ?? 0,
11411
+ seenUpToSeq: typeof data.seenUpToSeq === "number" ? data.seenUpToSeq : null,
11412
+ withheld
11413
+ };
11414
+ }
11415
+ function isHeldResponse(data) {
11416
+ return Boolean(data) && typeof data === "object" && data.state === "held";
11417
+ }
11418
+ function sentOutcome(target, data) {
11419
+ const sent = {
11420
+ messageId: data.messageId,
11421
+ messageSeq: typeof data.messageSeq === "number" ? data.messageSeq : null,
11422
+ unresolvedMentionHandles: data.unresolvedMentionHandles ?? [],
11423
+ recentUnread: (data.recentUnread ?? []).map(projectRaftMessage)
11424
+ };
11425
+ const warn = sent.unresolvedMentionHandles.length > 0 ? ` Unresolved @handles: ${sent.unresolvedMentionHandles.map((h) => `@${h}`).join(", ")}.` : "";
11426
+ return {
11427
+ ok: true,
11428
+ state: "sent",
11429
+ data: sent,
11430
+ next: sent.recentUnread.length > 0 ? {
11431
+ kind: "read_target",
11432
+ command: `raft message read --target "${target}"`,
11433
+ args: { target },
11434
+ why: "Newer messages arrived while you were sending."
11435
+ } : null,
11436
+ text: `Message sent to ${target}. Message ID: ${sent.messageId}${warn}`
11437
+ };
11438
+ }
11439
+ async function sendMessage(client, request, frontier, internal = {}) {
11440
+ if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target is required to send a message." }));
11441
+ if (typeof request.content !== "string" || !request.content.trim() && !request.attachmentIds?.length) return failureOutcome(opError("INVALID_REQUEST", { message: "Message content (or an attachment) is required." }));
11442
+ const idempotencyKey = request.idempotencyKey?.trim() || crypto.randomUUID();
11443
+ const attestation = request.seen ? {
11444
+ ...request.seen.upToSeq === void 0 ? {} : { seenUpToSeq: request.seen.upToSeq },
11445
+ seenExactSeqs: request.seen.exactSeqs ?? []
11446
+ } : frontier?.attestation(request.target) ?? { seenExactSeqs: [] };
11447
+ const body = {
11448
+ target: request.target,
11449
+ content: request.content,
11450
+ ...request.attachmentIds?.length ? { attachmentIds: request.attachmentIds } : {},
11451
+ idempotencyKey,
11452
+ ...request.mentions?.length ? { mentions: request.mentions } : {},
11453
+ ...attestation.seenUpToSeq === void 0 ? {} : { seenUpToSeq: attestation.seenUpToSeq },
11454
+ ...attestation.seenExactSeqs.length > 0 ? { seenExactSeqs: attestation.seenExactSeqs.slice(0, 2500) } : {},
11455
+ ...internal.sendDraft ? { sendDraft: true } : {},
11456
+ ...internal.continueAnyway ? { continueAnyway: true } : {},
11457
+ ...internal.reholdCount ? { draftReholdCount: internal.reholdCount } : {}
11458
+ };
11459
+ let result = await client.messages.sendV2(body);
11460
+ for (let attempt = 1; !result.ok && result.error.kind === "transport" && attempt < SEND_TRANSPORT_ATTEMPTS; attempt += 1) result = await client.messages.sendV2(body);
11461
+ if (!result.ok) return failureFromClientResult(result);
11462
+ const data = result.data;
11463
+ if (isHeldResponse(data)) {
11464
+ const reholdCount = (internal.reholdCount ?? 0) + 1;
11465
+ const keyed = {
11466
+ ...request,
11467
+ idempotencyKey
11468
+ };
11469
+ const held = {
11470
+ ...projectHeld(request.target, data),
11471
+ resend: (options) => sendMessage(client, options.seen === "held" && typeof data.seenUpToSeq === "number" ? {
11472
+ ...keyed,
11473
+ seen: {
11474
+ upToSeq: data.seenUpToSeq,
11475
+ exactSeqs: attestation.seenExactSeqs
11476
+ }
11477
+ } : keyed, frontier, {
11478
+ sendDraft: true,
11479
+ reholdCount,
11480
+ ...options.seen === "anyway" ? { continueAnyway: true } : {}
11481
+ })
11482
+ };
11483
+ return {
11484
+ ok: true,
11485
+ state: "held",
11486
+ data: held,
11487
+ next: heldNext(held),
11488
+ text: heldText(held, "Your message was not sent.")
11489
+ };
11490
+ }
11491
+ if (data.state === "sent") return sentOutcome(request.target, data);
11492
+ return failureOutcome(opError("INVALID_RESPONSE", { message: `Unexpected send state "${data.state}".` }));
11493
+ }
11494
+ /** Reply where a received message came from. */
11495
+ function replyTo(client, message, request, frontier) {
11496
+ return sendMessage(client, {
11497
+ ...request,
11498
+ target: message.target
11499
+ }, frontier);
7754
11500
  }
7755
11501
  //#endregion
7756
- //#region src/credential.ts
7757
- var RaftCredentialError = class extends Error {
7758
- code;
7759
- name = "RaftCredentialError";
7760
- constructor(code, message) {
7761
- super(message);
7762
- this.code = code;
11502
+ //#region ../shared/src/agentOps/tasks.ts
11503
+ const TASK_CLAIM_REASON_ALREADY_CLAIMED_BY_YOU = "already claimed by you";
11504
+ function rowState(result) {
11505
+ if (result.success) return "claimed";
11506
+ if (result.reason === TASK_CLAIM_REASON_ALREADY_CLAIMED_BY_YOU) return "already_yours";
11507
+ if (result.conflict?.kind === "claim_conflict") return "conflict";
11508
+ return "refused";
11509
+ }
11510
+ function claimNext(claim) {
11511
+ const first = claim.rows.find((row) => row.mayWork);
11512
+ if (first) {
11513
+ const suffix = first.messageId ? first.messageId.slice(0, 8) : null;
11514
+ return {
11515
+ kind: "start_work",
11516
+ ...suffix ? {
11517
+ command: `raft message send --target "${claim.target}:${suffix}"`,
11518
+ args: { thread: `${claim.target}:${suffix}` }
11519
+ } : {},
11520
+ why: "The claim is yours; post progress in the task's thread."
11521
+ };
7763
11522
  }
7764
- };
7765
- function isNonEmptyString(value) {
7766
- return typeof value === "string" && value.trim().length > 0;
11523
+ return {
11524
+ kind: "do_not_start",
11525
+ why: "No claim authorised work. Do not retry the identical claim and do not start conflicting execution; if you own this lane, correct the routing in the original thread."
11526
+ };
7767
11527
  }
7768
- function parseIdentityResponse(value) {
7769
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
7770
- const input = value;
7771
- if (!isNonEmptyString(input.agentId) || !isNonEmptyString(input.agentName) || input.agentDisplayName !== null && typeof input.agentDisplayName !== "string" || !isNonEmptyString(input.serverId) || !isNonEmptyString(input.credentialId) || !Array.isArray(input.scopes) || !input.scopes.every(isNonEmptyString)) return null;
11528
+ function claimText(claim) {
11529
+ const lines = claim.rows.map((row) => {
11530
+ const label = row.taskNumber ? `#${row.taskNumber}` : `msg:${row.ref}`;
11531
+ if (row.state === "claimed") return `${label}: claimed`;
11532
+ if (row.state === "already_yours") return `${label}: already claimed by you`;
11533
+ if (row.state === "conflict") return `${label}: held by ${row.holder?.name ? `@${row.holder.name}` : "another assignee"}`;
11534
+ return `${label}: refused${row.reason ? ` (${row.reason})` : ""}`;
11535
+ });
11536
+ const head = `Claim results (${claim.rows.filter((row) => row.mayWork).length} of ${claim.rows.length} authorise work):`;
11537
+ const next = claimNext(claim);
11538
+ return [
11539
+ head,
11540
+ ...lines,
11541
+ next?.command ? `Follow up in the task's thread: ${next.command}` : next?.why ?? ""
11542
+ ].filter(Boolean).join("\n");
11543
+ }
11544
+ async function claimTasks(client, request) {
11545
+ const numbers = (request.taskNumbers ?? []).filter((n) => Number.isInteger(n) && n > 0);
11546
+ const ids = (request.messageIds ?? []).map((id) => id.trim()).filter(Boolean);
11547
+ if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target is required to claim tasks." }));
11548
+ if (numbers.length === 0 && ids.length === 0) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass at least one task number or message id to claim." }));
11549
+ const result = await client.tasks.claim({
11550
+ channel: request.target,
11551
+ ...numbers.length ? { task_numbers: numbers } : {},
11552
+ ...ids.length ? { message_ids: ids } : {}
11553
+ });
11554
+ if (!result.ok) return failureFromClientResult(result);
11555
+ const data = result.data;
11556
+ if (isHeldResponse(data)) {
11557
+ const held = {
11558
+ ...projectHeld(request.target, data),
11559
+ retry: () => claimTasks(client, request)
11560
+ };
11561
+ return {
11562
+ ok: true,
11563
+ state: "held",
11564
+ data: held,
11565
+ next: {
11566
+ kind: "review_then_retry",
11567
+ command: `raft message read --target "${request.target}"`,
11568
+ args: {
11569
+ target: request.target,
11570
+ newMessageCount: held.newMessageCount
11571
+ },
11572
+ why: "Unread messages in this channel may change the task; read them, then claim again if it is still right."
11573
+ },
11574
+ text: `Held — ${held.newMessageCount} unread ${held.newMessageCount === 1 ? "message" : "messages"} in ${request.target}. Your task claim was not applied.\nRead them with: raft message read --target "${request.target}"\nAfter reviewing, rerun the claim if it is still correct.`
11575
+ };
11576
+ }
11577
+ const rows = (data.results ?? []).map((row) => {
11578
+ const state = rowState(row);
11579
+ return {
11580
+ ref: row.taskNumber ? String(row.taskNumber) : row.messageId ?? "?",
11581
+ taskNumber: row.taskNumber ?? null,
11582
+ messageId: row.messageId ?? null,
11583
+ state,
11584
+ mayWork: state === "claimed" || state === "already_yours",
11585
+ reason: row.reason ?? null,
11586
+ holder: row.conflict?.currentAssignee ?? null,
11587
+ conflict: row.conflict ?? null
11588
+ };
11589
+ });
11590
+ const claim = {
11591
+ target: request.target,
11592
+ rows,
11593
+ anyAuthorised: rows.some((row) => row.mayWork)
11594
+ };
7772
11595
  return {
7773
- agentId: input.agentId,
7774
- agentName: input.agentName,
7775
- agentDisplayName: input.agentDisplayName,
7776
- serverId: input.serverId,
7777
- credentialId: input.credentialId,
7778
- scopes: [...input.scopes]
11596
+ ok: true,
11597
+ state: !claim.anyAuthorised ? "refused" : rows.every((row) => row.mayWork) ? "claimed" : "partial",
11598
+ data: claim,
11599
+ next: claimNext(claim),
11600
+ text: claimText(claim)
7779
11601
  };
7780
11602
  }
7781
- function parseStoredRaftCredential(value) {
11603
+ //#endregion
11604
+ //#region ../shared/src/agentOps/wake.ts
11605
+ const RAFT_INBOX_NOTICE_SCHEMA = "raft-agent-inbox-notice.v1";
11606
+ const RAFT_NOTICE_SIGNATURE_HEADER = "x-raft-signature-256";
11607
+ const RAFT_NOTICE_DELIVERY_ID_HEADER = "x-raft-delivery-id";
11608
+ function headerValue(headers, name) {
11609
+ if (typeof headers.get === "function") return headers.get(name);
11610
+ const record = headers;
11611
+ const key = Object.keys(record).find((k) => k.toLowerCase() === name);
11612
+ const value = key ? record[key] : void 0;
11613
+ return Array.isArray(value) ? value[0] ?? null : value ?? null;
11614
+ }
11615
+ function hexToBytes(hex) {
11616
+ if (hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) return null;
11617
+ const out = new Uint8Array(hex.length / 2);
11618
+ for (let i = 0; i < out.length; i += 1) out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
11619
+ return out;
11620
+ }
11621
+ function constantTimeEqual(a, b) {
11622
+ if (a.length !== b.length) return false;
11623
+ let diff = 0;
11624
+ for (let i = 0; i < a.length; i += 1) diff |= a[i] ^ b[i];
11625
+ return diff === 0;
11626
+ }
11627
+ async function hmacSha256(secret, body) {
11628
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {
11629
+ name: "HMAC",
11630
+ hash: "SHA-256"
11631
+ }, false, ["sign"]);
11632
+ const bytes = new Uint8Array(body.byteLength);
11633
+ bytes.set(body);
11634
+ return new Uint8Array(await crypto.subtle.sign("HMAC", key, bytes.buffer));
11635
+ }
11636
+ const SENDER_TYPES = /* @__PURE__ */ new Set([
11637
+ "human",
11638
+ "agent",
11639
+ "system",
11640
+ "third_party_app"
11641
+ ]);
11642
+ const FLAGS = /* @__PURE__ */ new Set([
11643
+ "mention",
11644
+ "non_member_mention",
11645
+ "thread",
11646
+ "dm",
11647
+ "task"
11648
+ ]);
11649
+ function parseNotice(value, deliveryId) {
7782
11650
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
7783
- const input = value;
7784
- if (input.schemaVersion !== 1 || !isNonEmptyString(input.serverUrl) || !isNonEmptyString(input.credential) || !isNonEmptyString(input.storedAt)) return null;
7785
- const identity = parseIdentityResponse(input);
7786
- if (!identity || Number.isNaN(Date.parse(input.storedAt))) return null;
7787
- let serverUrl;
7788
- let credential;
7789
- try {
7790
- serverUrl = requireServerUrl(input.serverUrl);
7791
- credential = requireAgentCredential(input.credential);
7792
- } catch {
7793
- return null;
11651
+ const v = value;
11652
+ if (typeof v.noticeId !== "string" || typeof v.recipientAgentId !== "string" || typeof v.occurredAt !== "string" || !Array.isArray(v.targets)) return null;
11653
+ const targets = [];
11654
+ for (const raw of v.targets) {
11655
+ if (!raw || typeof raw !== "object") return null;
11656
+ const t = raw;
11657
+ if (typeof t.target !== "string") return null;
11658
+ const flags = Array.isArray(t.flags) ? t.flags.filter((f) => typeof f === "string" && FLAGS.has(f)) : [];
11659
+ targets.push({
11660
+ target: t.target,
11661
+ channelId: typeof t.channelId === "string" ? t.channelId : null,
11662
+ channelType: typeof t.channelType === "string" ? t.channelType : null,
11663
+ pendingCount: typeof t.pendingCount === "number" ? t.pendingCount : 0,
11664
+ firstPendingMsgId: typeof t.firstPendingMsgId === "string" ? t.firstPendingMsgId : null,
11665
+ latestMsgId: typeof t.latestMsgId === "string" ? t.latestMsgId : null,
11666
+ latestSenderName: typeof t.latestSenderName === "string" ? t.latestSenderName : null,
11667
+ latestSenderType: typeof t.latestSenderType === "string" && SENDER_TYPES.has(t.latestSenderType) ? t.latestSenderType : null,
11668
+ flags,
11669
+ mentionsYou: flags.includes("mention") || flags.includes("non_member_mention")
11670
+ });
7794
11671
  }
7795
11672
  return {
7796
- schemaVersion: 1,
7797
- serverUrl,
7798
- credential,
7799
- storedAt: input.storedAt,
7800
- ...identity
11673
+ noticeId: v.noticeId,
11674
+ deliveryId,
11675
+ recipientAgentId: v.recipientAgentId,
11676
+ occurredAt: v.occurredAt,
11677
+ text: typeof v.text === "string" ? v.text : "",
11678
+ targets
7801
11679
  };
7802
11680
  }
7803
11681
  /**
7804
- * Validate an existing External Agent credential against Raft, derive its
7805
- * bound identity from the credential-authenticated `whoami` surface, and save
7806
- * it through the caller's explicit store. This does not mint or rotate a
7807
- * credential and never returns the credential bytes.
11682
+ * Verify and parse a push notice. Async, WebCrypto, raw bytes. Never rejects
11683
+ * on time: treat every accepted notice as "pull your inbox now".
7808
11684
  */
7809
- async function bootstrapRaftCredential(options) {
7810
- const serverUrl = requireServerUrl(options.serverUrl);
7811
- const credential = requireAgentCredential(options.credential);
7812
- const httpFetch = options.fetch ?? globalThis.fetch;
7813
- let existing;
7814
- try {
7815
- existing = await options.store.load();
7816
- } catch {
7817
- throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential could not be checked before saving");
7818
- }
7819
- if (existing) {
7820
- const validatedExisting = parseStoredRaftCredential(existing);
7821
- if (!validatedExisting) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential record is invalid");
7822
- existing = validatedExisting;
7823
- if (existing.serverUrl !== serverUrl || existing.credential !== credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
7824
- }
7825
- let response;
7826
- try {
7827
- response = await httpFetch(`${serverUrl}/internal/agent-api/`, {
7828
- method: "GET",
7829
- headers: { authorization: `Bearer ${credential}` }
7830
- });
7831
- } catch {
7832
- throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", "Could not reach the Raft Server to verify the Agent credential");
7833
- }
7834
- if (!response.ok) {
7835
- if (response.status === 401 || response.status === 403) throw new RaftCredentialError("CREDENTIAL_REJECTED", "The Raft Server rejected the Agent credential");
7836
- throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", `The Raft Server could not verify the Agent credential (status ${response.status})`);
7837
- }
7838
- let payload;
11685
+ async function verifyInboxNotice(input) {
11686
+ const header = headerValue(input.headers, "x-raft-signature-256")?.trim() ?? "";
11687
+ if (!header) return {
11688
+ ok: false,
11689
+ reason: "missing_signature",
11690
+ message: `Missing ${RAFT_NOTICE_SIGNATURE_HEADER} header.`
11691
+ };
11692
+ const presented = header.toLowerCase().startsWith("sha256=") ? hexToBytes(header.slice(7)) : null;
11693
+ if (!presented) return {
11694
+ ok: false,
11695
+ reason: "bad_signature",
11696
+ message: "Signature header is not `sha256=<hex>`."
11697
+ };
11698
+ if (!constantTimeEqual(presented, await hmacSha256(input.secret, input.body))) return {
11699
+ ok: false,
11700
+ reason: "bad_signature",
11701
+ message: "Signature does not match the body."
11702
+ };
11703
+ let parsed;
7839
11704
  try {
7840
- payload = await response.json();
11705
+ parsed = JSON.parse(new TextDecoder().decode(input.body));
7841
11706
  } catch {
7842
- throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
11707
+ return {
11708
+ ok: false,
11709
+ reason: "invalid_body",
11710
+ message: "Body is not JSON."
11711
+ };
7843
11712
  }
7844
- const identity = parseIdentityResponse(payload);
7845
- if (!identity) throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
7846
- if (!identity.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The Raft Agent credential does not include the send capability required by this SDK");
7847
- if (existing && (existing.serverId !== identity.serverId || existing.agentId !== identity.agentId)) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store is already bound to a different Raft Agent identity");
7848
- if (existing) return {
7849
- serverUrl,
7850
- ...identity
11713
+ if ((parsed && typeof parsed === "object" ? parsed.schema : void 0) !== "raft-agent-inbox-notice.v1") return {
11714
+ ok: false,
11715
+ reason: "unsupported_schema",
11716
+ message: `Expected schema ${RAFT_INBOX_NOTICE_SCHEMA}.`
7851
11717
  };
7852
- const record = {
7853
- schemaVersion: 1,
7854
- serverUrl,
7855
- credential,
7856
- storedAt: currentDate().toISOString(),
7857
- ...identity
11718
+ const notice = parseNotice(parsed, headerValue(input.headers, RAFT_NOTICE_DELIVERY_ID_HEADER));
11719
+ if (!notice) return {
11720
+ ok: false,
11721
+ reason: "invalid_body",
11722
+ message: "Notice body does not match the contract."
7858
11723
  };
7859
- try {
7860
- await options.store.save(record);
7861
- } catch (error) {
7862
- if (error instanceof RaftCredentialError && error.code === "CREDENTIAL_STORE_CONFLICT") throw error;
7863
- throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The verified Agent credential could not be saved");
7864
- }
7865
11724
  return {
7866
- serverUrl,
7867
- ...identity
11725
+ ok: true,
11726
+ notice
7868
11727
  };
7869
11728
  }
7870
- /** Load the caller-selected credential store and create a normal Raft client. */
7871
- async function createRaftClientFromStore(options) {
7872
- let record;
7873
- try {
7874
- record = await options.store.load();
7875
- } catch {
7876
- throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential could not be loaded");
7877
- }
7878
- if (!record) throw new RaftCredentialError("CREDENTIAL_NOT_FOUND", "No stored Raft Agent credential was found");
7879
- const validated = parseStoredRaftCredential(record);
7880
- if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential is invalid");
7881
- if (!validated.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The stored Raft Agent credential does not include the send capability required by this SDK");
7882
- return createRaftClient({
7883
- serverUrl: validated.serverUrl,
7884
- credential: validated.credential,
7885
- fetch: options.fetch,
7886
- headers: options.headers,
7887
- retry: options.retry,
7888
- throttle: options.throttle
7889
- });
11729
+ function statusText(status) {
11730
+ if (!status.registered) return "Push webhook: not registered.";
11731
+ return `Push webhook: ${status.url ?? "(url hidden)"} · ${status.enabled ? "enabled" : `disabled (${status.disabledReason ?? "unknown"})`}${status.lastDeliveryAt ? ` · last delivery ${status.lastDeliveryAt}` : ""}${status.consecutiveFailures > 0 ? ` · ${status.consecutiveFailures} consecutive failures` : ""}`;
7890
11732
  }
7891
- //#endregion
7892
- //#region src/fileCredentialStore.ts
7893
- const MAX_CREDENTIAL_FILE_BYTES = 65536;
7894
- /**
7895
- * Create a Node.js credential store at one explicit absolute path. The SDK
7896
- * never discovers CLI profiles or a home directory. Writes use a same-directory
7897
- * temporary file followed by atomic rename, and the final file is mode 0600.
7898
- */
7899
- function createFileCredentialStore(filePath) {
7900
- if (!filePath.trim() || !isAbsolute(filePath)) throw new RaftSdkConfigurationError("INVALID_CREDENTIAL_PATH", "Raft credential file path must be an explicit absolute path");
11733
+ async function webhookStatus(client) {
11734
+ const result = await client.pushWebhook.status();
11735
+ if (!result.ok) return failureFromClientResult(result);
7901
11736
  return {
7902
- load: () => loadFileCredential(filePath),
7903
- async save(record) {
7904
- const validated = parseStoredRaftCredential(record);
7905
- if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "Refusing to write an invalid Raft Agent credential record");
7906
- const directory = dirname(filePath);
7907
- const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
7908
- let handle;
7909
- try {
7910
- await mkdir(directory, {
7911
- recursive: true,
7912
- mode: 448
7913
- });
7914
- const existing = await loadFileCredential(filePath);
7915
- if (existing) {
7916
- assertSameCredential(existing, validated);
7917
- return;
7918
- }
7919
- handle = await open(temporaryPath, "wx", 384);
7920
- await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
7921
- await handle.sync();
7922
- await chmod(temporaryPath, 384);
7923
- await handle.close();
7924
- handle = void 0;
7925
- try {
7926
- await link(temporaryPath, filePath);
7927
- } catch (error) {
7928
- if (error.code !== "EEXIST") throw error;
7929
- const winner = await loadFileCredential(filePath);
7930
- if (!winner) throw error;
7931
- assertSameCredential(winner, validated);
7932
- }
7933
- await unlink(temporaryPath);
7934
- } catch (error) {
7935
- if (handle) await handle.close().catch(() => void 0);
7936
- await unlink(temporaryPath).catch(() => void 0);
7937
- if (error instanceof RaftCredentialError) throw error;
7938
- throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The Raft Agent credential could not be written atomically");
7939
- }
7940
- }
11737
+ ok: true,
11738
+ state: "status",
11739
+ data: result.data,
11740
+ next: null,
11741
+ text: statusText(result.data)
7941
11742
  };
7942
11743
  }
7943
- async function loadFileCredential(filePath) {
7944
- let initialMetadata;
7945
- try {
7946
- initialMetadata = await lstat(filePath);
7947
- } catch (error) {
7948
- if (error.code === "ENOENT") return null;
7949
- throw fileReadError();
7950
- }
7951
- if (!initialMetadata.isFile() || initialMetadata.isSymbolicLink() || initialMetadata.size > MAX_CREDENTIAL_FILE_BYTES) throw fileReadError();
7952
- let parsed;
7953
- let handle;
7954
- try {
7955
- const noFollow = process.platform === "win32" ? 0 : constants.O_NOFOLLOW;
7956
- handle = await open(filePath, constants.O_RDONLY | noFollow);
7957
- const openedMetadata = await handle.stat();
7958
- if (!openedMetadata.isFile() || openedMetadata.size > MAX_CREDENTIAL_FILE_BYTES || initialMetadata.dev !== openedMetadata.dev || initialMetadata.ino !== openedMetadata.ino) throw fileReadError();
7959
- if (process.platform !== "win32" && (openedMetadata.mode & 63) !== 0) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file permissions are too broad; expected mode 0600");
7960
- parsed = JSON.parse(await handle.readFile("utf8"));
7961
- } catch (error) {
7962
- if (error instanceof RaftCredentialError) throw error;
7963
- throw fileReadError();
7964
- } finally {
7965
- await handle?.close().catch(() => void 0);
7966
- }
7967
- const record = parseStoredRaftCredential(parsed);
7968
- if (!record) throw fileReadError();
7969
- return record;
11744
+ async function registerWebhook(client, request) {
11745
+ const result = await client.pushWebhook.register({
11746
+ url: request.url,
11747
+ secret: request.secret
11748
+ });
11749
+ if (!result.ok) return failureFromClientResult(result);
11750
+ return {
11751
+ ok: true,
11752
+ state: "registered",
11753
+ data: result.data,
11754
+ next: null,
11755
+ text: statusText(result.data)
11756
+ };
7970
11757
  }
7971
- function assertSameCredential(existing, candidate) {
7972
- if (existing.credential !== candidate.credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
11758
+ async function unregisterWebhook(client) {
11759
+ const result = await client.pushWebhook.unregister();
11760
+ if (!result.ok) return failureFromClientResult(result);
11761
+ return {
11762
+ ok: true,
11763
+ state: "unregistered",
11764
+ data: null,
11765
+ next: null,
11766
+ text: "Push webhook: removed."
11767
+ };
7973
11768
  }
7974
- function fileReadError() {
7975
- return new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file is invalid or unreadable");
11769
+ //#endregion
11770
+ //#region src/raft.ts
11771
+ function createRaft(options) {
11772
+ const serverUrl = requireServerUrl(options.serverUrl);
11773
+ const authorization = `Bearer ${requireAgentCredential(options.credential)}`;
11774
+ const frontier = SeenFrontier.fromSnapshot(options.frontier);
11775
+ const routes = createRaftRoutes({
11776
+ serverUrl,
11777
+ fetch: options.fetch,
11778
+ headers: options.headers,
11779
+ authorization,
11780
+ readAttempts: options.retry?.attempts,
11781
+ beforeRequest: options.throttle?.beforeRequest
11782
+ });
11783
+ const inboxApi = createAgentApiClient({ fetch: {
11784
+ baseUrl: serverUrl,
11785
+ fetch: (input, init) => (options.fetch ?? fetch)(input, {
11786
+ ...init,
11787
+ cache: "no-store",
11788
+ redirect: "error"
11789
+ }),
11790
+ headers: Object.fromEntries(new Headers(options.headers)),
11791
+ retry: { attempts: 1 },
11792
+ throttle: options.throttle,
11793
+ auth: { authorization }
11794
+ } });
11795
+ return {
11796
+ identity: { whoami: () => getRaftContext(routes) },
11797
+ wake: {
11798
+ verifyNotice: (input) => verifyInboxNotice(input),
11799
+ webhook: {
11800
+ status: () => webhookStatus(routes),
11801
+ register: (request) => registerWebhook(routes, request),
11802
+ unregister: () => unregisterWebhook(routes)
11803
+ }
11804
+ },
11805
+ inbox: {
11806
+ check: (request) => checkInbox(inboxApi, request, frontier),
11807
+ drain: (request) => drainInbox(inboxApi, request, frontier),
11808
+ list: (request) => listInbox(routes, request)
11809
+ },
11810
+ messages: {
11811
+ read: (request) => readHistory(routes, request, frontier),
11812
+ send: (request) => sendMessage(routes, request, frontier),
11813
+ reply: (message, request) => replyTo(routes, message, request, frontier)
11814
+ },
11815
+ tasks: { claim: (request) => claimTasks(routes, request) },
11816
+ frontier,
11817
+ routes
11818
+ };
7976
11819
  }
7977
11820
  //#endregion
7978
- export { RaftCredentialError, RaftSdkConfigurationError, bootstrapRaftCredential, createFileCredentialStore, createRaftClient, createRaftClientFromStore, joinRaftChannelByTarget, parseRaftRegularChannelTarget };
11821
+ export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, bootstrapRaftCredential, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, describeRaftRoute, joinRaftChannelByTarget, listRaftRoutes, parseRaftRegularChannelTarget, verifyInboxNotice };