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