@botiverse/raft-sdk 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -4752,6 +4752,7 @@ const AGENT_API_ROUTE_META = {
4752
4752
  attachmentUploadSessionCancel: naturalDestructive,
4753
4753
  attachmentUploadSessionStatus: read,
4754
4754
  attachmentDownload: read,
4755
+ attachmentDownloadUrl: read,
4755
4756
  attachmentCommentsList: read,
4756
4757
  pushWebhookStatus: {
4757
4758
  sideEffect: "read",
@@ -5280,6 +5281,18 @@ const agentApiMessageReactionBodySchema = passthroughObject({ emoji: string().tr
5280
5281
  const agentApiChannelMembershipParamsSchema = passthroughObject({ channelId: string().trim().min(1).transform(asChannelId) });
5281
5282
  const agentApiChannelLifecycleBodySchema = passthroughObject({ target: string().trim().min(1) });
5282
5283
  const agentApiAttachmentDownloadParamsSchema = passthroughObject({ attachmentId: string().trim().min(1) });
5284
+ /**
5285
+ * Short-lived download URL for an attachment visible to the bound agent
5286
+ * credential (`GET /attachments/:attachmentId/url`). For runtimes that cannot
5287
+ * take binary tool results: the agent fetches the bytes itself from `url`
5288
+ * before `expiresAt`. The URL is a bearer capability; never log it.
5289
+ */
5290
+ const agentApiAttachmentDownloadUrlResponseSchema = passthroughObject({
5291
+ url: string().min(1),
5292
+ expiresAt: string().datetime(),
5293
+ filename: string(),
5294
+ mimeType: string()
5295
+ });
5283
5296
  const agentApiAttachmentCommentsParamsSchema = passthroughObject({ attachmentId: string().trim().min(1) });
5284
5297
  const agentApiAttachmentCommentsQuerySchema = passthroughObject({ limit: optionalStringSchema });
5285
5298
  const agentApiAttachmentCommentAnchorSchema = passthroughObject({
@@ -7336,6 +7349,19 @@ const agentApiContract = {
7336
7349
  request: { params: agentApiAttachmentDownloadParamsSchema },
7337
7350
  response: { kind: "binary" }
7338
7351
  }),
7352
+ attachmentDownloadUrl: route({
7353
+ key: "attachmentDownloadUrl",
7354
+ method: "GET",
7355
+ path: "/attachments/:attachmentId/url",
7356
+ client: {
7357
+ resource: "attachments",
7358
+ method: "downloadUrl"
7359
+ },
7360
+ capability: "read",
7361
+ description: "Mint a short-lived (5 minute) download URL for an attachment visible to the bound agent credential, with its filename and MIME type. 409 download_url_unavailable when this Server's storage cannot presign; use the binary download instead.",
7362
+ request: { params: agentApiAttachmentDownloadParamsSchema },
7363
+ response: { body: agentApiAttachmentDownloadUrlResponseSchema }
7364
+ }),
7339
7365
  attachmentCommentsList: route({
7340
7366
  key: "attachmentCommentsList",
7341
7367
  method: "GET",
@@ -9731,6 +9757,31 @@ const AGENT_API_ROUTE_MANIFEST = [
9731
9757
  "body": false
9732
9758
  }
9733
9759
  },
9760
+ {
9761
+ "key": "attachmentDownloadUrl",
9762
+ "method": "GET",
9763
+ "path": "/attachments/:attachmentId/url",
9764
+ "fullPath": "/internal/agent-api/attachments/:attachmentId/url",
9765
+ "client": {
9766
+ "resource": "attachments",
9767
+ "method": "downloadUrl"
9768
+ },
9769
+ "capability": "read",
9770
+ "description": "Mint a short-lived (5 minute) download URL for an attachment visible to the bound agent credential, with its filename and MIME type. 409 download_url_unavailable when this Server's storage cannot presign; use the binary download instead.",
9771
+ "sideEffect": "read",
9772
+ "idempotency": "natural",
9773
+ "destructive": false,
9774
+ "audience": "both",
9775
+ "request": {
9776
+ "params": true,
9777
+ "query": false,
9778
+ "body": false
9779
+ },
9780
+ "response": {
9781
+ "kind": "json",
9782
+ "body": true
9783
+ }
9784
+ },
9734
9785
  {
9735
9786
  "key": "attachmentCommentsList",
9736
9787
  "method": "GET",
@@ -9858,7 +9909,7 @@ const AGENT_API_ROUTE_MANIFEST = [
9858
9909
  }
9859
9910
  ];
9860
9911
  /** Content hash of AGENT_API_ROUTE_MANIFEST; see computeAgentApiManifestVersion. */
9861
- const AGENT_API_MANIFEST_VERSION = "2a3c2201ea57a9d1";
9912
+ const AGENT_API_MANIFEST_VERSION = "c762a744e3542f89";
9862
9913
  //#endregion
9863
9914
  //#region src/routes.ts
9864
9915
  const ROUTE_INFO = Object.fromEntries(AGENT_API_ROUTE_MANIFEST.map((entry) => {
@@ -10453,6 +10504,277 @@ function fileReadError() {
10453
10504
  return new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file is invalid or unreadable");
10454
10505
  }
10455
10506
  //#endregion
10507
+ //#region ../shared/src/agentOps/hint.ts
10508
+ /** `tasks.updateStatus` → `tasks_update_status`: the manifest's tool name for an operation name. */
10509
+ function raftToolNameFor(name) {
10510
+ return name.replace(/\./g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
10511
+ }
10512
+ /** Tool-form wording for a CLI command that has no operation (channel / server admin writes). */
10513
+ const RAFT_NO_TOOL_WORDING = "ask a human via an action card (`actions_prepare`)";
10514
+ const ELLIPSIS = "…";
10515
+ function q(value) {
10516
+ return `"${value}"`;
10517
+ }
10518
+ function hint(cli, op, fill) {
10519
+ if (!op) return { cli };
10520
+ const args = Object.fromEntries(Object.entries(op.args).filter(([, value]) => value !== void 0));
10521
+ return fill && fill.length > 0 ? {
10522
+ cli,
10523
+ op: {
10524
+ name: op.name,
10525
+ args,
10526
+ partial: true
10527
+ },
10528
+ fill
10529
+ } : {
10530
+ cli,
10531
+ op: {
10532
+ name: op.name,
10533
+ args
10534
+ }
10535
+ };
10536
+ }
10537
+ function toolArgs(hint) {
10538
+ const parts = Object.entries(hint.op?.args ?? {}).map(([key, value]) => `${key}: ${JSON.stringify(value)}`);
10539
+ for (const key of hint.fill ?? []) parts.push(`${key}: ${ELLIPSIS}`);
10540
+ return parts.length > 0 ? `{ ${parts.join(", ")} }` : "{}";
10541
+ }
10542
+ /** Render a hint: `raft message read --target "#ops"` (cli) or `messages_read({ target: "#ops" })` (tool). */
10543
+ function formatHint(hint, style = "cli") {
10544
+ if (style === "cli") return ["raft", ...hint.cli].join(" ");
10545
+ if (!hint.op) return RAFT_NO_TOOL_WORDING;
10546
+ if (hint.codeOnly) return `raft.${hint.op.name}(${toolArgs(hint)})`;
10547
+ return `${raftToolNameFor(hint.op.name)}(${toolArgs(hint)})`;
10548
+ }
10549
+ /** Just the command's name, for prose that lists commands: `raft channel join` (cli) or `channels_join` (tool). */
10550
+ function formatHintName(hint, style = "cli") {
10551
+ if (style === "cli") return ["raft", ...hint.cli].join(" ");
10552
+ if (!hint.op) return RAFT_NO_TOOL_WORDING;
10553
+ return hint.codeOnly ? `raft.${hint.op.name}` : raftToolNameFor(hint.op.name);
10554
+ }
10555
+ /** A flag named in prose: `--limit` (cli) or `limit` (tool, the argument name). */
10556
+ function formatHintFlag(flag, arg, style = "cli") {
10557
+ return style === "cli" ? `--${flag}` : arg;
10558
+ }
10559
+ /**
10560
+ * The hint as a next step: `command` is the rendered hint, `operation` the
10561
+ * structured call (when there is one). `args` keeps the step's own arguments.
10562
+ */
10563
+ function hintStep(kind, hint, why, style = "cli", args) {
10564
+ return {
10565
+ kind,
10566
+ command: formatHint(hint, style),
10567
+ ...args ? { args } : {},
10568
+ ...hint.op ? { operation: hint.op } : {},
10569
+ why
10570
+ };
10571
+ }
10572
+ /**
10573
+ * Every hint the shared formatters and operations produce. Placeholder
10574
+ * arguments (`<name>`, `"…"`) are `fill` keys: the CLI form prints the
10575
+ * placeholder, the tool form `name: …`.
10576
+ */
10577
+ const RAFT_HINTS = {
10578
+ messageRead: ({ target, after, before, around }) => hint([
10579
+ "message",
10580
+ "read",
10581
+ "--target",
10582
+ q(target),
10583
+ ...after === void 0 ? [] : ["--after", String(after)],
10584
+ ...before === void 0 ? [] : ["--before", String(before)],
10585
+ ...around === void 0 ? [] : ["--around", around.shown]
10586
+ ], {
10587
+ name: "messages.read",
10588
+ args: {
10589
+ target,
10590
+ after,
10591
+ before,
10592
+ around: around?.id
10593
+ }
10594
+ }),
10595
+ /** Send into a conversation; `content` is always the caller's. Without a target the target is left too. */
10596
+ messageSend: ({ target, attachmentId }) => hint([
10597
+ "message",
10598
+ "send",
10599
+ ...target === void 0 ? [] : ["--target", q(target)],
10600
+ ...attachmentId === void 0 ? [] : ["--attachment-id", attachmentId]
10601
+ ], {
10602
+ name: "messages.send",
10603
+ args: {
10604
+ target,
10605
+ attachmentIds: attachmentId === void 0 ? void 0 : [attachmentId]
10606
+ }
10607
+ }, target === void 0 ? ["target", "content"] : ["content"]),
10608
+ /** The send command's name alone (prose, never a call). */
10609
+ messageSendName: () => hint(["message", "send"], {
10610
+ name: "messages.send",
10611
+ args: {}
10612
+ }, ["target", "content"]),
10613
+ messageCheck: () => hint(["message", "check"], {
10614
+ name: "inbox.check",
10615
+ args: {}
10616
+ }),
10617
+ inboxList: ({ view, before } = {}) => hint([
10618
+ "inbox",
10619
+ "check",
10620
+ ...view === void 0 ? [] : ["--view", view],
10621
+ ...before === void 0 ? [] : ["--before", String(before)]
10622
+ ], {
10623
+ name: "inbox.list",
10624
+ args: {
10625
+ view,
10626
+ before
10627
+ }
10628
+ }),
10629
+ /** `query: true` is the `<name>` placeholder. */
10630
+ serverInfo: ({ view, offset, limit, query, joined } = {}) => hint([
10631
+ "server",
10632
+ "info",
10633
+ ...view === void 0 ? [] : [`--${view}`],
10634
+ ...offset === void 0 ? [] : ["--offset", String(offset)],
10635
+ ...limit === void 0 ? [] : ["--limit", String(limit)],
10636
+ ...query === void 0 ? [] : ["--query", query === true ? "<name>" : JSON.stringify(query)],
10637
+ ...joined ? ["--joined"] : []
10638
+ ], {
10639
+ name: "server.info",
10640
+ args: {
10641
+ view,
10642
+ offset,
10643
+ limit,
10644
+ query: typeof query === "string" ? query : void 0,
10645
+ joined: joined ? true : void 0
10646
+ }
10647
+ }, query === true ? ["query"] : void 0),
10648
+ /** `name` undefined is the `<name>` placeholder. */
10649
+ userInfo: ({ name, offset, limit } = {}) => hint([
10650
+ "user",
10651
+ "info",
10652
+ name === void 0 ? "<name>" : `@${name}`,
10653
+ ...offset === void 0 ? [] : ["--offset", String(offset)],
10654
+ ...limit === void 0 ? [] : ["--limit", String(limit)]
10655
+ ], {
10656
+ name: "users.info",
10657
+ args: {
10658
+ name: name === void 0 ? void 0 : `@${name}`,
10659
+ offset,
10660
+ limit
10661
+ }
10662
+ }, name === void 0 ? ["name"] : void 0),
10663
+ /** The `<name>` placeholder form. */
10664
+ channelInfo: () => hint([
10665
+ "channel",
10666
+ "info",
10667
+ "<name>"
10668
+ ], {
10669
+ name: "channels.info",
10670
+ args: {}
10671
+ }, ["target"]),
10672
+ channelMembers: (target) => hint([
10673
+ "channel",
10674
+ "members",
10675
+ q(target)
10676
+ ], {
10677
+ name: "channels.members",
10678
+ args: { target }
10679
+ }),
10680
+ /** Channel attention commands by name (prose). */
10681
+ channelJoinName: () => hint(["channel", "join"], {
10682
+ name: "channels.join",
10683
+ args: {}
10684
+ }, ["target"]),
10685
+ channelLeaveName: () => hint(["channel", "leave"], {
10686
+ name: "channels.leave",
10687
+ args: {}
10688
+ }, ["target"]),
10689
+ channelMuteName: () => hint(["channel", "mute"], {
10690
+ name: "channels.mute",
10691
+ args: {}
10692
+ }, ["target"]),
10693
+ channelUnmuteName: () => hint(["channel", "unmute"], {
10694
+ name: "channels.unmute",
10695
+ args: {}
10696
+ }, ["target"]),
10697
+ threadUnfollowName: () => hint(["thread", "unfollow"], {
10698
+ name: "threads.unfollow",
10699
+ args: {}
10700
+ }, ["target"]),
10701
+ /** Channel / server admin writes: no operation by policy (a human acts through an action card). */
10702
+ channelCreateName: () => hint(["channel", "create"]),
10703
+ serverUpdateName: () => hint(["server", "update"]),
10704
+ taskClaim: ({ target, taskNumber }) => hint([
10705
+ "task",
10706
+ "claim",
10707
+ "--target",
10708
+ q(target),
10709
+ "--number",
10710
+ String(taskNumber)
10711
+ ], {
10712
+ name: "tasks.claim",
10713
+ args: {
10714
+ target,
10715
+ taskNumbers: [taskNumber]
10716
+ }
10717
+ }),
10718
+ taskListAll: (target) => hint([
10719
+ "task",
10720
+ "list",
10721
+ "--target",
10722
+ q(target),
10723
+ "--status",
10724
+ "all"
10725
+ ], {
10726
+ name: "tasks.list",
10727
+ args: {
10728
+ target,
10729
+ status: "all"
10730
+ }
10731
+ }),
10732
+ mentionAction: (action, resolutionId) => hint([
10733
+ "mention",
10734
+ action,
10735
+ resolutionId
10736
+ ], {
10737
+ name: action === "notify" ? "mentions.notify" : "mentions.add",
10738
+ args: { resolutionIds: [resolutionId] }
10739
+ }),
10740
+ mentionPending: () => hint(["mention", "pending"], {
10741
+ name: "mentions.pending",
10742
+ args: {}
10743
+ }),
10744
+ /** `intent` and `reason` are always the caller's. */
10745
+ manualGet: (topic) => hint([
10746
+ "manual",
10747
+ "get",
10748
+ topic,
10749
+ "--intent",
10750
+ q(ELLIPSIS),
10751
+ "--reason",
10752
+ q(ELLIPSIS)
10753
+ ], {
10754
+ name: "manual.get",
10755
+ args: { topic }
10756
+ }, ["intent", "reason"]),
10757
+ /** The download pointer on message lines; the tool form mints a URL (one attachment: its id; several: left to fill). */
10758
+ attachmentView: (attachmentId) => hint(["attachment", "view"], {
10759
+ name: "attachments.downloadUrl",
10760
+ args: { attachmentId }
10761
+ }, attachmentId === void 0 ? ["attachmentId"] : void 0),
10762
+ /** The binary download (typed method only): where an attachment whose URL cannot be minted is fetched. */
10763
+ attachmentDownload: (attachmentId) => ({
10764
+ ...hint([
10765
+ "attachment",
10766
+ "view",
10767
+ attachmentId,
10768
+ "--output",
10769
+ "<path>"
10770
+ ], {
10771
+ name: "attachments.download",
10772
+ args: { attachmentId }
10773
+ }),
10774
+ codeOnly: true
10775
+ })
10776
+ };
10777
+ //#endregion
10456
10778
  //#region ../shared/src/agentOps/outcome.ts
10457
10779
  const SERVER_ERROR_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
10458
10780
  const DEFAULT_MESSAGES = {
@@ -10469,6 +10791,9 @@ const DEFAULT_MESSAGES = {
10469
10791
  UNAVAILABLE: "The Raft Server could not serve this operation right now.",
10470
10792
  MODEL_ONLY: "This operation only counts when the model sees its result, so it cannot be run from code; nothing was sent."
10471
10793
  };
10794
+ function notFoundNextAction(style) {
10795
+ return `Check the target spelling with \`${formatHint(RAFT_HINTS.serverInfo({ view: "channels" }), style)}\` or resolve the message id first.`;
10796
+ }
10472
10797
  const DEFAULT_NEXT_ACTION = {
10473
10798
  INVALID_REQUEST: "Fix the request arguments; nothing was sent.",
10474
10799
  TRANSPORT_ERROR: "Check connectivity to the Raft Server and retry if the operation is safe to repeat.",
@@ -10476,7 +10801,7 @@ const DEFAULT_NEXT_ACTION = {
10476
10801
  INVALID_RESPONSE: "Upgrade the SDK or report the Server version; the response shape is not the one this SDK knows.",
10477
10802
  CAPABILITY_NOT_AUTHORIZED: "Ask a human who can mint credentials to include the missing capability.",
10478
10803
  SCOPE_DENIED: "Ask a human with editAgents authority to extend this agent's scopes.",
10479
- NOT_FOUND: "Check the target spelling with `raft server info --channels` or resolve the message id first.",
10804
+ NOT_FOUND: notFoundNextAction("cli"),
10480
10805
  CONFLICT: "Read the current state before repeating this operation.",
10481
10806
  IDEMPOTENCY_KEY_REUSED: "Use a new idempotency key for a different request, or resend the identical request to reconcile.",
10482
10807
  UNSUPPORTED_FOR_EXTERNAL_AGENTS: "Use your own runtime for this; the Server does not provide it to External Agents.",
@@ -10568,6 +10893,31 @@ function failureOutcome(error) {
10568
10893
  };
10569
10894
  }
10570
10895
  /**
10896
+ * Render the SDK's own default next action in a hint style. Only NOT_FOUND's
10897
+ * default names a command; it is set deep in the shared client-error mapping,
10898
+ * so `createRaft({ hints: "tool" })` re-renders it here instead of threading
10899
+ * the style through every failure. A Server-sent next action is left as is.
10900
+ */
10901
+ function restyleDefaultNextAction(outcome, style) {
10902
+ if (style === "cli" || !outcome || typeof outcome !== "object") return outcome;
10903
+ const failure = outcome;
10904
+ if (failure.ok !== false || !failure.error || failure.error.nextAction !== DEFAULT_NEXT_ACTION.NOT_FOUND) return outcome;
10905
+ const error = {
10906
+ ...failure.error,
10907
+ nextAction: notFoundNextAction(style)
10908
+ };
10909
+ const next = failure.next && failure.next.why === DEFAULT_NEXT_ACTION.NOT_FOUND ? {
10910
+ ...failure.next,
10911
+ why: error.nextAction
10912
+ } : failure.next ?? null;
10913
+ return {
10914
+ ...failure,
10915
+ error,
10916
+ next,
10917
+ text: formatOpErrorText(error)
10918
+ };
10919
+ }
10920
+ /**
10571
10921
  * A failure of a keyed write (`idempotencyKey`). A retryable failure (the
10572
10922
  * request may not have reached the Server, or the Server was unavailable)
10573
10923
  * says to repeat the SAME request with the SAME key, and carries the key in
@@ -10990,9 +11340,10 @@ function formatAgentSenderHandle(m) {
10990
11340
  const desc = m.sender_description ?? null;
10991
11341
  return desc ? `@${name} — ${desc}` : `@${name}`;
10992
11342
  }
10993
- function formatAgentAttachmentSuffix(attachments) {
11343
+ function formatAgentAttachmentSuffix(attachments, style = "cli") {
10994
11344
  if (!attachments?.length) return "";
10995
- return ` [${attachments.length} attachment${attachments.length > 1 ? "s" : ""}: ${attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} — use raft attachment view to download]`;
11345
+ const download = formatHint(RAFT_HINTS.attachmentView(style === "tool" && attachments.length === 1 ? attachments[0].id : void 0), style);
11346
+ return ` [${attachments.length} attachment${attachments.length > 1 ? "s" : ""}: ${attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} — use ${download} to download]`;
10996
11347
  }
10997
11348
  function formatAgentTaskAssigneeSuffix(assigneeId, assigneeName) {
10998
11349
  if (!assigneeId) return "";
@@ -11018,7 +11369,7 @@ function formatAgentTaskCurrentProjection(projection, taskNumber, neutralize) {
11018
11369
  return `\n${lines.join("\n")}`;
11019
11370
  }
11020
11371
  /** One received-message line: header bracket + sender + content + suffixes. */
11021
- function formatAgentMessageLine(m) {
11372
+ function formatAgentMessageLine(m, style = "cli") {
11022
11373
  if (m.third_party_event) {
11023
11374
  const msgId = m.message_id ? m.message_id.slice(0, 8) : m.third_party_event.id.slice(0, 8);
11024
11375
  const time = m.timestamp ? formatUtcTimestamp(m.timestamp) : "-";
@@ -11034,22 +11385,22 @@ function formatAgentMessageLine(m) {
11034
11385
  const time = m.timestamp ? formatUtcTimestamp(m.timestamp) : "-";
11035
11386
  const senderType = ` type=${m.sender_type}`;
11036
11387
  const content = indentAgentBodyContinuationLines(m.content ?? "");
11037
- const attachSuffix = formatAgentAttachmentSuffix(m.attachments);
11388
+ const attachSuffix = formatAgentAttachmentSuffix(m.attachments, style);
11038
11389
  const taskSuffix = m.task_status ? ` [task #${m.task_number} status=${m.task_status}${formatAgentTaskAssigneeSuffix(m.task_assignee_id, m.task_assignee_name)}]` : "";
11039
11390
  return `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatAgentSenderHandle(m)}: ${content}${attachSuffix}${taskSuffix}${formatAgentReplyAffordanceSuffix(m)}${formatAgentTaskCurrentProjection(m.task_current_projection)}`;
11040
11391
  }
11041
11392
  /** Batch of received-message lines (`raft message check` output). */
11042
- function formatAgentMessages(messages) {
11393
+ function formatAgentMessages(messages, style = "cli") {
11043
11394
  if (messages.length === 0) return "No new inbox messages.";
11044
- return messages.map(formatAgentMessageLine).join("\n");
11395
+ return messages.map((m) => formatAgentMessageLine(m, style)).join("\n");
11045
11396
  }
11046
11397
  /**
11047
11398
  * `message check` hands over a bounded batch, oldest first per conversation;
11048
11399
  * the server reports how many conversations still have unread after it.
11049
11400
  */
11050
- function formatAgentInboxHint(hint) {
11401
+ function formatAgentInboxHint(hint, style = "cli") {
11051
11402
  const n = hint.unread_conversations;
11052
- return `Still unread: ${n} ${n === 1 ? "conversation" : "conversations"}. Run \`raft inbox check\` to list them.`;
11403
+ return `Still unread: ${n} ${n === 1 ? "conversation" : "conversations"}. Run \`${formatHint(RAFT_HINTS.inboxList(), style)}\` to list them.`;
11053
11404
  }
11054
11405
  //#endregion
11055
11406
  //#region ../shared/src/agentOps/message.ts
@@ -11093,7 +11444,7 @@ function hasAgentMessageIdentity(envelope) {
11093
11444
  return true;
11094
11445
  }
11095
11446
  /** Project one envelope; `null` when it has no conversation identity (skip it rather than render `#undefined`). */
11096
- function projectRaftMessage(envelope) {
11447
+ function projectRaftMessage(envelope, style = "cli") {
11097
11448
  if (!hasAgentMessageIdentity(envelope)) return null;
11098
11449
  const like = toAgentMessageLike(envelope);
11099
11450
  const id = nullableString(like.message_id);
@@ -11126,7 +11477,7 @@ function projectRaftMessage(envelope) {
11126
11477
  id: nullableString(envelope.threadId),
11127
11478
  replyCount: nullableNumber(envelope.replyCount)
11128
11479
  },
11129
- text: formatAgentMessageLine(like),
11480
+ text: formatAgentMessageLine(like, style),
11130
11481
  raw: envelope
11131
11482
  };
11132
11483
  }
@@ -11134,8 +11485,8 @@ function sortBySeq(messages) {
11134
11485
  return [...messages].sort((a, b) => (a.seq ?? Number.MAX_SAFE_INTEGER) - (b.seq ?? Number.MAX_SAFE_INTEGER));
11135
11486
  }
11136
11487
  /** Project a list, skipping envelopes without a conversation identity. */
11137
- function projectRaftMessages(envelopes) {
11138
- return envelopes.map(projectRaftMessage).filter((m) => m !== null);
11488
+ function projectRaftMessages(envelopes, style = "cli") {
11489
+ return envelopes.map((envelope) => projectRaftMessage(envelope, style)).filter((m) => m !== null);
11139
11490
  }
11140
11491
  /** The conversation fields a target string names; `null` for a shape this cannot read. */
11141
11492
  function identityFromTarget(target) {
@@ -11167,12 +11518,12 @@ function identityFromTarget(target) {
11167
11518
  * because the request already named the target; fill them from `target`
11168
11519
  * instead of dropping the message.
11169
11520
  */
11170
- function projectRaftMessagesInTarget(envelopes, target) {
11521
+ function projectRaftMessagesInTarget(envelopes, target, style = "cli") {
11171
11522
  const identity = identityFromTarget(target);
11172
11523
  return envelopes.map((envelope) => projectRaftMessage(identity && !hasAgentMessageIdentity(envelope) ? {
11173
11524
  ...envelope,
11174
11525
  ...identity
11175
- } : envelope)).filter((m) => m !== null);
11526
+ } : envelope, style)).filter((m) => m !== null);
11176
11527
  }
11177
11528
  //#endregion
11178
11529
  //#region ../shared/src/agentOps/interrupt.ts
@@ -11184,7 +11535,7 @@ function isInterrupted(outcome) {
11184
11535
  function unreadMessagesInterrupt(input) {
11185
11536
  const { hold, target } = input;
11186
11537
  const withheld = input.withheld === true || hold.freshnessContextMode === "withheld";
11187
- const heldMessages = withheld ? [] : projectRaftMessagesInTarget(hold.heldMessages ?? [], target);
11538
+ const heldMessages = withheld ? [] : projectRaftMessagesInTarget(hold.heldMessages ?? [], target, input.hints);
11188
11539
  const newMessageCount = withheld ? hold.withheldMessageCount ?? hold.newMessageCount ?? 0 : hold.newMessageCount ?? heldMessages.length;
11189
11540
  const omittedMessageCount = withheld ? 0 : hold.omittedMessageCount ?? 0;
11190
11541
  return {
@@ -11475,18 +11826,9 @@ const inboxPullFields = {
11475
11826
  };
11476
11827
  const checkInboxRequestSchema = requestSchema()(object(inboxPullFields));
11477
11828
  const MAX_DRAIN_ROUNDS = 50;
11478
- function batchNext(batch) {
11479
- if (batch.hasMore) return {
11480
- kind: "check_inbox_again",
11481
- command: "raft message check",
11482
- ...batch.cursor === null ? {} : { args: { since: batch.cursor } },
11483
- why: "The Server trimmed this batch; more messages are pending."
11484
- };
11485
- if ((batch.stillUnreadConversations ?? 0) > 0) return {
11486
- kind: "list_inbox",
11487
- command: "raft inbox check",
11488
- why: "Conversations remain unread beyond this batch."
11489
- };
11829
+ function batchNext(batch, style) {
11830
+ if (batch.hasMore) return hintStep("check_inbox_again", RAFT_HINTS.messageCheck(), "The Server trimmed this batch; more messages are pending.", style, batch.cursor === null ? void 0 : { since: batch.cursor });
11831
+ if ((batch.stillUnreadConversations ?? 0) > 0) return hintStep("list_inbox", RAFT_HINTS.inboxList(), "Conversations remain unread beyond this batch.", style);
11490
11832
  if (batch.messages.length > 0) return {
11491
11833
  kind: "reply_or_act",
11492
11834
  args: { target: batch.messages[0].target },
@@ -11494,11 +11836,11 @@ function batchNext(batch) {
11494
11836
  };
11495
11837
  return null;
11496
11838
  }
11497
- function batchText(batch) {
11498
- const lines = [formatAgentMessages(batch.messages.map((m) => toAgentMessageLike(m.raw)))];
11499
- if (batch.hasMore) lines.push("More messages are pending. Run `raft message check` again.");
11839
+ function batchText(batch, style) {
11840
+ const lines = [formatAgentMessages(batch.messages.map((m) => toAgentMessageLike(m.raw)), style)];
11841
+ if (batch.hasMore) lines.push(`More messages are pending. Run \`${formatHint(RAFT_HINTS.messageCheck(), style)}\` again.`);
11500
11842
  else if (batch.messages.length > 0) lines.push("No more new inbox messages.");
11501
- if ((batch.stillUnreadConversations ?? 0) > 0) lines.push(formatAgentInboxHint({ unread_conversations: batch.stillUnreadConversations }));
11843
+ if ((batch.stillUnreadConversations ?? 0) > 0) lines.push(formatAgentInboxHint({ unread_conversations: batch.stillUnreadConversations }, style));
11502
11844
  return lines.join("\n");
11503
11845
  }
11504
11846
  function recordExactSeen(frontier, messages) {
@@ -11511,7 +11853,8 @@ function recordExactSeen(frontier, messages) {
11511
11853
  for (const [target, seqs] of byTarget) frontier.recordExact(target, seqs);
11512
11854
  }
11513
11855
  /** One bounded pull. Records exact seen seqs on `frontier` (sparse drains never advance the high-water mark). */
11514
- async function checkInbox(client, request = {}, frontier) {
11856
+ async function checkInbox(client, request = {}, frontier, options = {}) {
11857
+ const style = options.hints ?? "cli";
11515
11858
  const invalid = validateOpRequest(checkInboxRequestSchema, request);
11516
11859
  if (invalid) return invalid;
11517
11860
  const ack = request.ack ?? "cursor";
@@ -11522,7 +11865,7 @@ async function checkInbox(client, request = {}, frontier) {
11522
11865
  });
11523
11866
  if (!result.ok) return failureFromClientResult(result);
11524
11867
  const data = result.data;
11525
- const messages = sortBySeq(projectRaftMessages(data.events));
11868
+ const messages = sortBySeq(projectRaftMessages(data.events, style));
11526
11869
  recordExactSeen(frontier, messages);
11527
11870
  const batch = {
11528
11871
  messages,
@@ -11536,8 +11879,8 @@ async function checkInbox(client, request = {}, frontier) {
11536
11879
  ok: true,
11537
11880
  state: messages.length > 0 ? "batch" : "empty",
11538
11881
  data: batch,
11539
- next: batchNext(batch),
11540
- text: batchText(batch)
11882
+ next: batchNext(batch, style),
11883
+ text: batchText(batch, style)
11541
11884
  };
11542
11885
  }
11543
11886
  const drainInboxRequestSchema = requestSchema()(object(inboxPullFields));
@@ -11551,7 +11894,7 @@ const drainInboxRequestSchema = requestSchema()(object(inboxPullFields));
11551
11894
  * return value summarises the drain; the batch that ends the iteration is
11552
11895
  * acknowledged only by the caller's next pull (`summary.cursor`).
11553
11896
  */
11554
- async function* drainInbox(client, request = {}, frontier, onPull) {
11897
+ async function* drainInbox(client, request = {}, frontier, onPull, options = {}) {
11555
11898
  const ack = request.ack ?? "cursor";
11556
11899
  const invalid = validateOpRequest(drainInboxRequestSchema, request);
11557
11900
  if (invalid) return {
@@ -11573,7 +11916,7 @@ async function* drainInbox(client, request = {}, frontier, onPull) {
11573
11916
  ...sent === null ? {} : { since: sent },
11574
11917
  limit: request.limit,
11575
11918
  ack
11576
- }, frontier);
11919
+ }, frontier, options);
11577
11920
  if (!round.ok) return {
11578
11921
  cursor,
11579
11922
  ackMode,
@@ -11623,25 +11966,18 @@ async function* drainInbox(client, request = {}, frontier, onPull) {
11623
11966
  };
11624
11967
  }
11625
11968
  /** Fold a finished drain into one outcome; a drain that failed before its first batch is that failure. */
11626
- function drainedInboxOutcome(batches, summary) {
11969
+ function drainedInboxOutcome(batches, summary, options = {}) {
11627
11970
  if (summary.error && batches.length === 0) return summary.error;
11971
+ const style = options.hints ?? "cli";
11628
11972
  const messages = batches.flatMap((batch) => batch.messages);
11629
- const lines = [formatAgentMessages(messages.map((m) => toAgentMessageLike(m.raw)))];
11973
+ const lines = [formatAgentMessages(messages.map((m) => toAgentMessageLike(m.raw)), style)];
11630
11974
  if (summary.error) lines.push("The drain stopped early on an error; more messages may be pending. Check the inbox again.");
11631
11975
  else if (summary.hasMore) lines.push("More messages are pending. Check the inbox again.");
11632
11976
  else if (messages.length > 0) lines.push("No more new inbox messages.");
11633
11977
  const stillUnread = summary.stillUnreadConversations ?? 0;
11634
- if (stillUnread > 0) lines.push(formatAgentInboxHint({ unread_conversations: stillUnread }));
11978
+ if (stillUnread > 0) lines.push(formatAgentInboxHint({ unread_conversations: stillUnread }, style));
11635
11979
  const first = messages[0];
11636
- const next = summary.hasMore ? {
11637
- kind: "check_inbox_again",
11638
- command: "raft message check",
11639
- why: "More messages are pending."
11640
- } : stillUnread > 0 ? {
11641
- kind: "list_inbox",
11642
- command: "raft inbox check",
11643
- why: "Conversations remain unread beyond this drain."
11644
- } : first ? {
11980
+ const next = summary.hasMore ? hintStep("check_inbox_again", RAFT_HINTS.messageCheck(), "More messages are pending.", style) : stillUnread > 0 ? hintStep("list_inbox", RAFT_HINTS.inboxList(), "Conversations remain unread beyond this drain.", style) : first ? {
11645
11981
  kind: "reply_or_act",
11646
11982
  args: { target: first.target },
11647
11983
  why: "Handle the messages above; reply where each one came from."
@@ -11663,23 +11999,21 @@ const listInboxRequestSchema = requestSchema()(object({
11663
11999
  before: number$1().int().nonnegative().optional().describe("Next page: the nextBeforeSeq of the previous page."),
11664
12000
  limit: number$1().int().positive().optional().describe("Conversations per page, 1..50 (Server default 20).")
11665
12001
  }));
11666
- function openCommandFor(item) {
11667
- return `raft message read --target "${item.target}" --after ${item.lastReadSeq}`;
12002
+ function openHint(item) {
12003
+ return RAFT_HINTS.messageRead({
12004
+ target: item.target,
12005
+ after: item.lastReadSeq
12006
+ });
11668
12007
  }
11669
- function listingNext(listing) {
12008
+ function listingNext(listing, style) {
11670
12009
  const first = listing.conversations[0];
11671
- if (first) return {
11672
- kind: "read_target",
11673
- command: first.openCommand,
11674
- args: {
11675
- target: first.target,
11676
- after: first.lastReadSeq
11677
- },
11678
- why: "Open the newest unread conversation from your read position."
11679
- };
12010
+ if (first) return hintStep("read_target", openHint(first), "Open the newest unread conversation from your read position.", style, {
12011
+ target: first.target,
12012
+ after: first.lastReadSeq
12013
+ });
11680
12014
  return null;
11681
12015
  }
11682
- function listingText(listing) {
12016
+ function listingText(listing, style) {
11683
12017
  const t = listing.totals;
11684
12018
  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." : ""}`;
11685
12019
  const rows = listing.conversations.map((c) => {
@@ -11687,8 +12021,14 @@ function listingText(listing) {
11687
12021
  return `${c.target} · ${c.unread} unread${flags ? ` · ${flags}` : ""}${c.latestSenderName ? ` · latest @${c.latestSenderName}` : ""}\n open: ${c.openCommand}`;
11688
12022
  });
11689
12023
  const trailer = [];
11690
- if (listing.hasMore && listing.nextBeforeSeq !== null) trailer.push(`More: raft inbox check${listing.view === "mentions" ? " --view mentions" : ""} --before ${listing.nextBeforeSeq}`);
11691
- const next = listingNext(listing);
12024
+ if (listing.hasMore && listing.nextBeforeSeq !== null) {
12025
+ const more = RAFT_HINTS.inboxList({
12026
+ ...listing.view === "mentions" ? { view: "mentions" } : {},
12027
+ before: listing.nextBeforeSeq
12028
+ });
12029
+ trailer.push(`More: ${formatHint(more, style)}`);
12030
+ }
12031
+ const next = listingNext(listing, style);
11692
12032
  trailer.push(next?.command ? `Next: open the first conversation above: ${next.command}` : "Next: nothing to do.");
11693
12033
  return [
11694
12034
  head,
@@ -11697,7 +12037,8 @@ function listingText(listing) {
11697
12037
  ].join("\n");
11698
12038
  }
11699
12039
  /** The agent's Activity panel. Lists only; nothing is consumed. */
11700
- async function listInbox(client, request = {}) {
12040
+ async function listInbox(client, request = {}, options = {}) {
12041
+ const style = options.hints ?? "cli";
11701
12042
  const invalid = validateOpRequest(listInboxRequestSchema, request);
11702
12043
  if (invalid) return invalid;
11703
12044
  const result = await client.inbox.list({
@@ -11718,7 +12059,7 @@ async function listInbox(client, request = {}) {
11718
12059
  activitySeq: item.activitySeq,
11719
12060
  latestSenderName: item.latestSenderName,
11720
12061
  latestAt: item.latestAt,
11721
- openCommand: openCommandFor(item)
12062
+ openCommand: formatHint(openHint(item), style)
11722
12063
  })),
11723
12064
  totals: data.totals,
11724
12065
  hasMore: data.hasMore,
@@ -11728,8 +12069,8 @@ async function listInbox(client, request = {}) {
11728
12069
  ok: true,
11729
12070
  state: listing.conversations.length > 0 ? "listed" : "empty",
11730
12071
  data: listing,
11731
- next: listingNext(listing),
11732
- text: listingText(listing)
12072
+ next: listingNext(listing, style),
12073
+ text: listingText(listing, style)
11733
12074
  };
11734
12075
  }
11735
12076
  //#endregion
@@ -11744,38 +12085,34 @@ const readHistoryRequestSchema = requestSchema()(object({
11744
12085
  limit: number$1().int().positive().optional().describe("Maximum messages in the window."),
11745
12086
  consume: boolean().optional().describe("false reads without consuming: the conversation is not marked read and nothing counts as seen.")
11746
12087
  }));
11747
- function historyNext(page) {
12088
+ function historyNext(page, style) {
11748
12089
  const seqs = page.messages.map((m) => m.seq).filter((s) => s !== null);
11749
12090
  if (page.hasNewer && seqs.length > 0) {
11750
12091
  const max = Math.max(...seqs);
11751
- return {
11752
- kind: "read_newer",
11753
- command: `raft message read --target "${page.target}" --after ${max}`,
11754
- args: {
11755
- target: page.target,
11756
- after: max
11757
- },
11758
- why: "Newer messages exist beyond this window."
11759
- };
12092
+ return hintStep("read_newer", RAFT_HINTS.messageRead({
12093
+ target: page.target,
12094
+ after: max
12095
+ }), "Newer messages exist beyond this window.", style, {
12096
+ target: page.target,
12097
+ after: max
12098
+ });
11760
12099
  }
11761
12100
  if (page.hasOlder && seqs.length > 0) {
11762
12101
  const min = Math.min(...seqs);
11763
- return {
11764
- kind: "read_older",
11765
- command: `raft message read --target "${page.target}" --before ${min}`,
11766
- args: {
11767
- target: page.target,
11768
- before: min
11769
- },
11770
- why: "Older messages exist before this window; read them only if the task needs them."
11771
- };
12102
+ return hintStep("read_older", RAFT_HINTS.messageRead({
12103
+ target: page.target,
12104
+ before: min
12105
+ }), "Older messages exist before this window; read them only if the task needs them.", style, {
12106
+ target: page.target,
12107
+ before: min
12108
+ });
11772
12109
  }
11773
12110
  return null;
11774
12111
  }
11775
- function historyText(page) {
12112
+ function historyText(page, style) {
11776
12113
  if (page.messages.length === 0) return `No messages in ${page.target}.`;
11777
- const lines = [formatAgentMessages(page.messages.map((m) => toAgentMessageLike(m.raw)))];
11778
- const next = historyNext(page);
12114
+ const lines = [formatAgentMessages(page.messages.map((m) => toAgentMessageLike(m.raw)), style)];
12115
+ const next = historyNext(page, style);
11779
12116
  if (next?.command) lines.push(`${next.kind === "read_newer" ? "Newer" : "Older"} exist: ${next.command}`);
11780
12117
  return lines.join("\n");
11781
12118
  }
@@ -11795,7 +12132,8 @@ function recordHistorySeen(frontier, requested, page, around) {
11795
12132
  }
11796
12133
  frontier.recordExact(page.target, seqs);
11797
12134
  }
11798
- async function readHistory(client, request, frontier) {
12135
+ async function readHistory(client, request, frontier, options = {}) {
12136
+ const style = options.hints ?? "cli";
11799
12137
  const invalid = validateOpRequest(readHistoryRequestSchema, request);
11800
12138
  if (invalid) return invalid;
11801
12139
  if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target is required to read history." }));
@@ -11811,7 +12149,7 @@ async function readHistory(client, request, frontier) {
11811
12149
  const data = result.data;
11812
12150
  const page = {
11813
12151
  target: typeof data.target === "string" && data.target ? data.target : request.target,
11814
- messages: sortBySeq(projectRaftMessages(data.messages)),
12152
+ messages: sortBySeq(projectRaftMessages(data.messages, style)),
11815
12153
  hasOlder: data.has_older === true,
11816
12154
  hasNewer: data.has_newer === true,
11817
12155
  lastReadSeq: typeof data.last_read_seq === "number" ? data.last_read_seq : null,
@@ -11822,8 +12160,8 @@ async function readHistory(client, request, frontier) {
11822
12160
  ok: true,
11823
12161
  state: page.messages.length > 0 ? "page" : "empty",
11824
12162
  data: page,
11825
- next: historyNext(page),
11826
- text: historyText(page)
12163
+ next: historyNext(page, style),
12164
+ text: historyText(page, style)
11827
12165
  };
11828
12166
  }
11829
12167
  /** Attempts for a keyed send whose request never reached the Server (transport failure only). */
@@ -11847,68 +12185,60 @@ const replyToRequestSchema = requestSchema()(object({
11847
12185
  ...sendMessageFields
11848
12186
  }));
11849
12187
  /** Today's held text (unchanged); it is the interrupt's `context` and the outcome's `text`. */
11850
- function heldText(held, action) {
12188
+ function heldText(held, action, style) {
11851
12189
  const noun = held.newMessageCount === 1 ? "message" : "messages";
11852
12190
  const head = `Held — ${held.newMessageCount} unread ${noun} in ${held.target}. ${action}`;
11853
12191
  if (held.withheld) return `${head}\nContext withheld (reviewer isolation).`;
11854
12192
  const lines = [head];
11855
12193
  if (held.formalMentionCount > 0) lines.push(`Note: ${held.formalMentionCount} of these messages formally @mention you.`);
11856
12194
  if (held.omittedMessageCount > 0) lines.push(`${held.omittedMessageCount} earlier ${held.omittedMessageCount === 1 ? "message" : "messages"} not shown.`);
11857
- if (held.heldMessages.length > 0) lines.push(formatAgentMessages(held.heldMessages.map((m) => toAgentMessageLike(m.raw))));
12195
+ if (held.heldMessages.length > 0) lines.push(formatAgentMessages(held.heldMessages.map((m) => toAgentMessageLike(m.raw)), style));
11858
12196
  if (!held.contextComplete) lines.push("Not all of them are shown here; read the conversation before sending again.");
11859
- lines.push(`Full text: raft message read --target "${held.target}"`);
12197
+ lines.push(`Full text: ${formatHint(RAFT_HINTS.messageRead({ target: held.target }), style)}`);
11860
12198
  return lines.join("\n");
11861
12199
  }
11862
12200
  /**
11863
12201
  * A Server freshness hold as the `unread_messages` interrupt: the structured
11864
12202
  * details, today's held text as `context`, and the given resume / cancel.
11865
12203
  */
11866
- function heldInterrupt(target, data, action, resume, cancel) {
12204
+ function heldInterrupt(target, data, action, resume, cancel, style = "cli") {
11867
12205
  const interrupt = unreadMessagesInterrupt({
11868
12206
  target,
11869
12207
  hold: data,
11870
12208
  context: "",
11871
12209
  resume,
11872
- ...cancel ? { cancel } : {}
12210
+ ...cancel ? { cancel } : {},
12211
+ hints: style
11873
12212
  });
11874
12213
  return {
11875
12214
  ...interrupt,
11876
- context: heldText(interrupt, action)
12215
+ context: heldText(interrupt, action, style)
11877
12216
  };
11878
12217
  }
11879
- function heldSendNext(interrupt) {
11880
- return {
11881
- kind: "resend",
11882
- command: `raft message read --target "${interrupt.target}"`,
11883
- args: { target: interrupt.target },
11884
- why: interrupt.withheld ? "Newer messages exist in this conversation but were withheld; read them, then resume the send (interrupt.resume, same idempotencyKey) or cancel it." : !interrupt.contextComplete ? "Newer messages arrived in this conversation and not all of them are shown here; read the conversation, then resume the send (interrupt.resume, same idempotencyKey) or cancel it." : "Newer messages arrived in this conversation. Show interrupt.context to the model, call frontier.recordHeld(interrupt) to attest that, then resume the send (interrupt.resume, same idempotencyKey) or cancel it."
11885
- };
12218
+ function heldSendNext(interrupt, style) {
12219
+ return hintStep("resend", RAFT_HINTS.messageRead({ target: interrupt.target }), interrupt.withheld ? "Newer messages exist in this conversation but were withheld; read them, then resume the send (interrupt.resume, same idempotencyKey) or cancel it." : !interrupt.contextComplete ? "Newer messages arrived in this conversation and not all of them are shown here; read the conversation, then resume the send (interrupt.resume, same idempotencyKey) or cancel it." : "Newer messages arrived in this conversation. Show interrupt.context to the model, call frontier.recordHeld(interrupt) to attest that, then resume the send (interrupt.resume, same idempotencyKey) or cancel it.", style, { target: interrupt.target });
11886
12220
  }
11887
12221
  function isHeldResponse(data) {
11888
12222
  return Boolean(data) && typeof data === "object" && data.state === "held";
11889
12223
  }
11890
- function sentOutcome(target, data) {
12224
+ function sentOutcome(target, data, style) {
11891
12225
  const sent = {
11892
12226
  messageId: data.messageId,
11893
12227
  messageSeq: typeof data.messageSeq === "number" ? data.messageSeq : null,
11894
12228
  unresolvedMentionHandles: data.unresolvedMentionHandles ?? [],
11895
- recentUnread: projectRaftMessages(data.recentUnread ?? [])
12229
+ recentUnread: projectRaftMessages(data.recentUnread ?? [], style)
11896
12230
  };
11897
12231
  const warn = sent.unresolvedMentionHandles.length > 0 ? ` Unresolved @handles: ${sent.unresolvedMentionHandles.map((h) => `@${h}`).join(", ")}.` : "";
11898
12232
  return {
11899
12233
  ok: true,
11900
12234
  state: "sent",
11901
12235
  data: sent,
11902
- next: sent.recentUnread.length > 0 ? {
11903
- kind: "read_target",
11904
- command: `raft message read --target "${target}"`,
11905
- args: { target },
11906
- why: "Newer messages arrived while you were sending."
11907
- } : null,
12236
+ next: sent.recentUnread.length > 0 ? hintStep("read_target", RAFT_HINTS.messageRead({ target }), "Newer messages arrived while you were sending.", style, { target }) : null,
11908
12237
  text: `Message sent to ${target}. Message ID: ${sent.messageId}${warn}`
11909
12238
  };
11910
12239
  }
11911
- async function sendMessage(client, request, frontier) {
12240
+ async function sendMessage(client, request, frontier, options = {}) {
12241
+ const style = options.hints ?? "cli";
11912
12242
  const invalid = validateOpRequest(sendMessageRequestSchema, request);
11913
12243
  if (invalid) return invalid;
11914
12244
  if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target is required to send a message." }));
@@ -11932,16 +12262,16 @@ async function sendMessage(client, request, frontier) {
11932
12262
  if (!result.ok) return failureFromClientResult(result);
11933
12263
  const data = result.data;
11934
12264
  if (isHeldResponse(data)) {
11935
- const interrupt = heldInterrupt(request.target, data, "Your message was not sent.", inProcessSendResume(idempotencyKey));
12265
+ const interrupt = heldInterrupt(request.target, data, "Your message was not sent.", inProcessSendResume(idempotencyKey), void 0, style);
11936
12266
  return {
11937
12267
  ok: true,
11938
12268
  state: "interrupted",
11939
12269
  interrupt,
11940
- next: heldSendNext(interrupt),
12270
+ next: heldSendNext(interrupt, style),
11941
12271
  text: interrupt.context
11942
12272
  };
11943
12273
  }
11944
- if (data.state === "sent") return sentOutcome(request.target, data);
12274
+ if (data.state === "sent") return sentOutcome(request.target, data, style);
11945
12275
  return failureOutcome(opError("INVALID_RESPONSE", { message: `Unexpected send state "${data.state}".` }));
11946
12276
  }
11947
12277
  function taskTimestamps(t) {
@@ -12023,13 +12353,13 @@ function formatAgentMyTaskList(data, statusFilter) {
12023
12353
  ].join("\n");
12024
12354
  }
12025
12355
  /** Receipt for task create. */
12026
- function formatAgentTasksCreated(channel, data) {
12356
+ function formatAgentTasksCreated(channel, data, style = "cli") {
12027
12357
  const created = data.tasks.map((t) => {
12028
12358
  const assignee = t.claimedById ? t.claimedByName ? `@${t.claimedByName}` : "<unresolved>" : "unassigned";
12029
12359
  const resourceReceipt = t.requiresResourceReceipt ? " resource-receipt=pending" : "";
12030
12360
  return `#${t.taskNumber} [${t.status}] assignee=${assignee} claimedAt=${t.claimedAt ?? "null"} msg=${t.messageId.slice(0, 8)}${resourceReceipt} "${t.title}"`;
12031
12361
  }).join("\n");
12032
- const threadHints = data.tasks.map((t) => `#${t.taskNumber} → raft message send --target "${channel}:${t.messageId.slice(0, 8)}"`).join("\n");
12362
+ const threadHints = data.tasks.map((t) => `#${t.taskNumber} → ${formatHint(RAFT_HINTS.messageSend({ target: `${channel}:${t.messageId.slice(0, 8)}` }), style)}`).join("\n");
12033
12363
  const receipt = data.assignmentReceipt ? `\n\nAssignment receipt (msg=${data.assignmentReceipt.messageId.slice(0, 8)}):\n${data.assignmentReceipt.content}` : "";
12034
12364
  return `Created ${data.tasks.length} task(s) in ${channel}:\n${created}${receipt}\n\nTo follow up in each task's thread:\n${threadHints}`;
12035
12365
  }
@@ -12052,7 +12382,7 @@ function agentTaskThreadTarget(target, messageId) {
12052
12382
  return `${canonicalAgentTaskTarget(target)}:${messageId.slice(0, 8)}`;
12053
12383
  }
12054
12384
  /** Claim results incl. concurrency-lock guidance on failed claims. */
12055
- function formatAgentClaimResults(channel, data) {
12385
+ function formatAgentClaimResults(channel, data, style = "cli") {
12056
12386
  const lines = data.results.map((r) => {
12057
12387
  const label = r.taskNumber ? `#${r.taskNumber}` : `msg:${r.messageId}`;
12058
12388
  if (r.success) return `${label} (msg:${r.messageId ? r.messageId.slice(0, 8) : ""}): claimed`;
@@ -12064,7 +12394,7 @@ function formatAgentClaimResults(channel, data) {
12064
12394
  const failed = data.results.length - succeeded;
12065
12395
  let summary = `${succeeded} claimed`;
12066
12396
  if (failed > 0) summary += `, ${failed} failed`;
12067
- const claimedMsgs = data.results.filter((r) => r.success && r.messageId).map((r) => `#${r.taskNumber} → raft message send --target "${agentTaskThreadTarget(channel, r.messageId)}"`).join("\n");
12397
+ const claimedMsgs = data.results.filter((r) => r.success && r.messageId).map((r) => `#${r.taskNumber} → ${formatHint(RAFT_HINTS.messageSend({ target: agentTaskThreadTarget(channel, r.messageId) }), style)}`).join("\n");
12068
12398
  const threadHint = claimedMsgs ? `\n\nFollow up in each task's thread:\n${claimedMsgs}` : "";
12069
12399
  return `Claim results (${summary}):\n${lines.join("\n")}${threadHint}`;
12070
12400
  }
@@ -12081,13 +12411,13 @@ function formatAgentTaskDeleted(taskNumber) {
12081
12411
  return `#${taskNumber} deleted.`;
12082
12412
  }
12083
12413
  /** Message→task conversion receipt. */
12084
- function formatAgentTaskConverted(channel, task) {
12414
+ function formatAgentTaskConverted(channel, task, style = "cli") {
12085
12415
  const target = `${canonicalAgentTaskTarget(channel)}:${task.messageId.slice(0, 8)}`;
12086
12416
  return [
12087
12417
  `Converted msg=${task.messageId.slice(0, 8)} to task #${task.taskNumber} [${task.status}] assignee=unassigned "${task.title}"`,
12088
12418
  "",
12089
12419
  `To follow up in the task's thread:`,
12090
- `raft message send --target "${target}"`
12420
+ formatHint(RAFT_HINTS.messageSend({ target }), style)
12091
12421
  ].join("\n");
12092
12422
  }
12093
12423
  function formatAgentTaskAmended(data) {
@@ -12149,15 +12479,16 @@ const claimTasksRequestSchema = requestSchema()(object({
12149
12479
  * A held task call: the interrupt whose resume is the identical command, with
12150
12480
  * no cancel (a held task call saved nothing). `context` is today's held text.
12151
12481
  */
12152
- function heldTaskInterrupt(target, data, action, after, argv) {
12482
+ function heldTaskInterrupt(target, data, action, after, argv, style) {
12153
12483
  const interrupt = unreadMessagesInterrupt({
12154
12484
  target,
12155
12485
  hold: data,
12156
12486
  context: "",
12157
- resume: { argv }
12487
+ resume: { argv },
12488
+ hints: style
12158
12489
  });
12159
12490
  const noun = interrupt.newMessageCount === 1 ? "message" : "messages";
12160
- const context = `Held — ${interrupt.newMessageCount} unread ${noun} in ${target}. ${action}\nRead them with: raft message read --target "${target}"\n${after}`;
12491
+ const context = `Held — ${interrupt.newMessageCount} unread ${noun} in ${target}. ${action}\nRead them with: ${formatHint(RAFT_HINTS.messageRead({ target }), style)}\n${after}`;
12161
12492
  return {
12162
12493
  ...interrupt,
12163
12494
  context
@@ -12169,17 +12500,14 @@ function rowState(result) {
12169
12500
  if (result.conflict?.kind === "claim_conflict") return "conflict";
12170
12501
  return "refused";
12171
12502
  }
12172
- function claimNext(claim) {
12503
+ function claimNext(claim, style) {
12173
12504
  const first = claim.rows.find((row) => row.mayWork);
12174
12505
  if (first) {
12175
12506
  const thread = first.messageId ? agentTaskThreadTarget(claim.target, first.messageId) : null;
12176
- return {
12507
+ const why = "The claim is yours; post progress in the task's thread.";
12508
+ return thread ? hintStep("start_work", RAFT_HINTS.messageSend({ target: thread }), why, style, { target: thread }) : {
12177
12509
  kind: "start_work",
12178
- ...thread ? {
12179
- command: `raft message send --target "${thread}"`,
12180
- args: { thread }
12181
- } : {},
12182
- why: "The claim is yours; post progress in the task's thread."
12510
+ why
12183
12511
  };
12184
12512
  }
12185
12513
  return {
@@ -12187,10 +12515,11 @@ function claimNext(claim) {
12187
12515
  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."
12188
12516
  };
12189
12517
  }
12190
- function claimText(claim) {
12191
- return formatAgentClaimResults(claim.target, { results: claim.rows.map((row) => row.raw) });
12518
+ function claimText(claim, style) {
12519
+ return formatAgentClaimResults(claim.target, { results: claim.rows.map((row) => row.raw) }, style);
12192
12520
  }
12193
- async function claimTasks(client, request) {
12521
+ async function claimTasks(client, request, options = {}) {
12522
+ const style = options.hints ?? "cli";
12194
12523
  const numbers = (request.taskNumbers ?? []).filter((n) => Number.isInteger(n) && n > 0);
12195
12524
  const ids = (request.messageIds ?? []).map((id) => id.trim()).filter(Boolean);
12196
12525
  const invalid = validateOpRequest(claimTasksRequestSchema, request);
@@ -12205,17 +12534,12 @@ async function claimTasks(client, request) {
12205
12534
  if (!result.ok) return failureFromClientResult(result);
12206
12535
  const data = result.data;
12207
12536
  if (isHeldResponse(data)) {
12208
- const interrupt = heldTaskInterrupt(request.target, data, "Your task claim was not applied.", "After reviewing, rerun the claim if it is still correct.", taskClaimArgv(request.target, numbers, ids));
12537
+ const interrupt = heldTaskInterrupt(request.target, data, "Your task claim was not applied.", "After reviewing, rerun the claim if it is still correct.", taskClaimArgv(request.target, numbers, ids), style);
12209
12538
  return {
12210
12539
  ok: true,
12211
12540
  state: "interrupted",
12212
12541
  interrupt,
12213
- next: {
12214
- kind: "retry_claim",
12215
- command: `raft message read --target "${request.target}"`,
12216
- args: { target: request.target },
12217
- why: "Unread messages in this channel may change the task; read them (frontier.recordHeld(interrupt) once the model saw them), then resume the claim (interrupt.resume) if it is still right."
12218
- },
12542
+ next: hintStep("retry_claim", RAFT_HINTS.messageRead({ target: request.target }), "Unread messages in this channel may change the task; read them (frontier.recordHeld(interrupt) once the model saw them), then resume the claim (interrupt.resume) if it is still right.", style, { target: request.target }),
12219
12543
  text: interrupt.context
12220
12544
  };
12221
12545
  }
@@ -12242,8 +12566,8 @@ async function claimTasks(client, request) {
12242
12566
  ok: true,
12243
12567
  state: !claim.anyAuthorised ? "refused" : rows.every((row) => row.mayWork) ? "claimed" : "partial",
12244
12568
  data: claim,
12245
- next: claimNext(claim),
12246
- text: claimText(claim)
12569
+ next: claimNext(claim, style),
12570
+ text: claimText(claim, style)
12247
12571
  };
12248
12572
  }
12249
12573
  const listTasksRequestSchema = requestSchema()(object({
@@ -12258,7 +12582,7 @@ const listTasksRequestSchema = requestSchema()(object({
12258
12582
  "all"
12259
12583
  ]).optional().describe("Filter by status; all includes done and closed.")
12260
12584
  }));
12261
- async function listTasks(client, request) {
12585
+ async function listTasks(client, request, options = {}) {
12262
12586
  const invalid = validateOpRequest(listTasksRequestSchema, request);
12263
12587
  if (invalid) return invalid;
12264
12588
  const mine = request.mine === true;
@@ -12277,15 +12601,13 @@ async function listTasks(client, request) {
12277
12601
  };
12278
12602
  const text = mine ? formatAgentMyTaskList(data, request.status) : formatAgentTaskList(request.target, data, request.status);
12279
12603
  const open = board.tasks.find((t) => t.status === "todo" && !t.claimedByName);
12280
- const next = open?.taskNumber && board.target ? {
12281
- kind: "claim_task",
12282
- command: `raft task claim --target "${board.target}" --number ${open.taskNumber}`,
12283
- args: {
12284
- target: board.target,
12285
- taskNumbers: [open.taskNumber]
12286
- },
12287
- why: "An unassigned todo task is open; claim it before working on it."
12288
- } : null;
12604
+ const next = open?.taskNumber && board.target ? hintStep("claim_task", RAFT_HINTS.taskClaim({
12605
+ target: board.target,
12606
+ taskNumber: open.taskNumber
12607
+ }), "An unassigned todo task is open; claim it before working on it.", options.hints, {
12608
+ target: board.target,
12609
+ taskNumbers: [open.taskNumber]
12610
+ }) : null;
12289
12611
  return {
12290
12612
  ok: true,
12291
12613
  state: board.tasks.length > 0 ? "board" : "empty",
@@ -12303,7 +12625,7 @@ const createTasksRequestSchema = requestSchema()(object({
12303
12625
  assignee: string().optional().describe("`@handle`: yourself to start in_progress, or (owner/admin) someone else to reserve a todo."),
12304
12626
  idempotencyKey: string().optional().describe("One key per logical create; generated when omitted and returned. Repeat the same request with the same key within 24 hours to retry without creating the tasks twice.")
12305
12627
  }));
12306
- async function createTasks(client, request) {
12628
+ async function createTasks(client, request, options = {}) {
12307
12629
  const invalid = validateOpRequest(createTasksRequestSchema, request);
12308
12630
  if (invalid) return invalid;
12309
12631
  if (!request.target?.trim() || !request.tasks?.length) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target and at least one task title are required." }));
@@ -12328,15 +12650,15 @@ async function createTasks(client, request) {
12328
12650
  target: request.target,
12329
12651
  idempotencyKey
12330
12652
  },
12331
- next: first ? {
12332
- kind: "post_in_task_thread",
12333
- command: `raft message send --target "${agentTaskThreadTarget(request.target, first.messageId)}"`,
12334
- args: { thread: agentTaskThreadTarget(request.target, first.messageId) },
12335
- why: "Follow up in each task's thread."
12336
- } : null,
12337
- text: formatAgentTasksCreated(request.target, data)
12653
+ next: first ? taskThreadStep(request.target, first.messageId, "Follow up in each task's thread.", options.hints) : null,
12654
+ text: formatAgentTasksCreated(request.target, data, options.hints)
12338
12655
  };
12339
12656
  }
12657
+ /** Post in a task's thread (a send: `content` is the caller's). */
12658
+ function taskThreadStep(target, messageId, why, style = "cli") {
12659
+ const thread = agentTaskThreadTarget(target, messageId);
12660
+ return hintStep("post_in_task_thread", RAFT_HINTS.messageSend({ target: thread }), why, style, { target: thread });
12661
+ }
12340
12662
  const taskRefFields = {
12341
12663
  target: taskChannelSchema,
12342
12664
  taskNumber: taskNumberSchema
@@ -12426,22 +12748,17 @@ const updateTaskStatusRequestSchema = requestSchema()(object({
12426
12748
  status: raftTaskStatusSchema.describe("todo → in_progress → in_review → done; closed from anywhere.")
12427
12749
  }));
12428
12750
  /** A held task write: the interrupt whose resume is the identical command; nothing to cancel. */
12429
- function heldTaskWrite(target, data, action, kind, argv) {
12430
- const interrupt = heldTaskInterrupt(target, data, action, "After reviewing, repeat the operation if it is still correct.", argv);
12751
+ function heldTaskWrite(target, data, action, kind, argv, style) {
12752
+ const interrupt = heldTaskInterrupt(target, data, action, "After reviewing, repeat the operation if it is still correct.", argv, style);
12431
12753
  return {
12432
12754
  ok: true,
12433
12755
  state: "interrupted",
12434
12756
  interrupt,
12435
- next: {
12436
- kind,
12437
- command: `raft message read --target "${target}"`,
12438
- args: { target },
12439
- why: "Unread messages in this channel may change the task; read them (frontier.recordHeld(interrupt) once the model saw them), then resume (interrupt.resume) if it is still right."
12440
- },
12757
+ next: hintStep(kind, RAFT_HINTS.messageRead({ target }), "Unread messages in this channel may change the task; read them (frontier.recordHeld(interrupt) once the model saw them), then resume (interrupt.resume) if it is still right.", style, { target }),
12441
12758
  text: interrupt.context
12442
12759
  };
12443
12760
  }
12444
- async function updateTaskStatus(client, request) {
12761
+ async function updateTaskStatus(client, request, options = {}) {
12445
12762
  const invalid = requireTaskRef(request, updateTaskStatusRequestSchema);
12446
12763
  if (invalid) return invalid;
12447
12764
  const result = await client.tasks.updateStatus({
@@ -12450,7 +12767,7 @@ async function updateTaskStatus(client, request) {
12450
12767
  status: request.status
12451
12768
  });
12452
12769
  if (!result.ok) return failureFromClientResult(result);
12453
- if (isHeldResponse(result.data)) return heldTaskWrite(request.target, result.data, "The status change was not applied.", "retry_update_status", taskUpdateArgv(request.target, request.taskNumber, request.status));
12770
+ if (isHeldResponse(result.data)) return heldTaskWrite(request.target, result.data, "The status change was not applied.", "retry_update_status", taskUpdateArgv(request.target, request.taskNumber, request.status), options.hints ?? "cli");
12454
12771
  return {
12455
12772
  ok: true,
12456
12773
  state: "updated",
@@ -12479,7 +12796,7 @@ function taskAmendArgv(request) {
12479
12796
  ...request.description === void 0 ? [] : request.description === null ? ["--clear-description"] : ["--description", request.description]
12480
12797
  ];
12481
12798
  }
12482
- async function amendTask(client, request) {
12799
+ async function amendTask(client, request, options = {}) {
12483
12800
  const invalid = requireTaskRef(request, amendTaskRequestSchema);
12484
12801
  if (invalid) return invalid;
12485
12802
  if (request.title === void 0 && request.description === void 0) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass a new title, a new description, or description: null to clear it." }));
@@ -12490,7 +12807,7 @@ async function amendTask(client, request) {
12490
12807
  ...request.description === void 0 ? {} : { description: request.description }
12491
12808
  });
12492
12809
  if (!result.ok) return failureFromClientResult(result);
12493
- if (isHeldResponse(result.data)) return heldTaskWrite(request.target, result.data, "The amendment was not applied.", "retry_amend", taskAmendArgv(request));
12810
+ if (isHeldResponse(result.data)) return heldTaskWrite(request.target, result.data, "The amendment was not applied.", "retry_amend", taskAmendArgv(request), options.hints ?? "cli");
12494
12811
  return {
12495
12812
  ok: true,
12496
12813
  state: "amended",
@@ -12527,7 +12844,7 @@ async function taskHistory(client, request) {
12527
12844
  * and picks the task; a miss says whether the Server asserted the list is
12528
12845
  * complete, exactly as the CLI does.
12529
12846
  */
12530
- async function showTask(client, request) {
12847
+ async function showTask(client, request, options = {}) {
12531
12848
  const invalid = requireTaskRef(request);
12532
12849
  if (invalid) return invalid;
12533
12850
  const { target, taskNumber } = request;
@@ -12540,7 +12857,7 @@ async function showTask(client, request) {
12540
12857
  const task = tasks.find((candidate) => candidate.taskNumber === taskNumber);
12541
12858
  if (!task) return failureOutcome(opError("NOT_FOUND", {
12542
12859
  message: result.data.pagination?.mode === "complete" && result.data.pagination.truncated === false ? `task #${taskNumber} not found in ${target} (searched ${tasks.length} task(s), status=all; server asserts this list is complete)` : tasks.length === 0 ? `task #${taskNumber} not found in ${target}: the server returned 0 tasks and did not assert the list is complete. That can mean the channel has no tasks, or that the server failed to read them (it currently reports some read errors as an empty list), and this command cannot tell which — so this is not evidence that task #${taskNumber} does not exist` : `task #${taskNumber} not found in ${target} (searched ${tasks.length} task(s), status=all; this surface does NOT assert completeness — so this is "absent from what was returned", not "does not exist")`,
12543
- nextAction: `Check the number with \`raft task list --target "${target}" --status all\`.`
12860
+ nextAction: `Check the number with \`${formatHint(RAFT_HINTS.taskListAll(target), options.hints)}\`.`
12544
12861
  }));
12545
12862
  return {
12546
12863
  ok: true,
@@ -12557,7 +12874,7 @@ const convertMessageToTaskRequestSchema = requestSchema()(object({
12557
12874
  target: taskChannelSchema,
12558
12875
  messageId: string().describe("Full or short id of a top-level message in that channel.")
12559
12876
  }));
12560
- async function convertMessageToTask(client, request) {
12877
+ async function convertMessageToTask(client, request, options = {}) {
12561
12878
  const invalid = validateOpRequest(convertMessageToTaskRequestSchema, request);
12562
12879
  if (invalid) return invalid;
12563
12880
  if (!request.target?.trim() || !request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target and a message id are required." }));
@@ -12574,13 +12891,8 @@ async function convertMessageToTask(client, request) {
12574
12891
  target: request.target,
12575
12892
  task
12576
12893
  },
12577
- next: {
12578
- kind: "post_in_task_thread",
12579
- command: `raft message send --target "${agentTaskThreadTarget(request.target, task.messageId)}"`,
12580
- args: { thread: agentTaskThreadTarget(request.target, task.messageId) },
12581
- why: "Follow up in the task's thread; it is unassigned until someone claims it."
12582
- },
12583
- text: formatAgentTaskConverted(request.target, task)
12894
+ next: taskThreadStep(request.target, task.messageId, "Follow up in the task's thread; it is unassigned until someone claims it.", options.hints),
12895
+ text: formatAgentTaskConverted(request.target, task, options.hints)
12584
12896
  };
12585
12897
  }
12586
12898
  async function deleteTask(client, request) {
@@ -12810,7 +13122,7 @@ function formatCurrentAgent(data) {
12810
13122
  return `${lines.join("\n")}\n\n`;
12811
13123
  }
12812
13124
  /** Server overview: runtime context, channels, agents, humans. */
12813
- function formatAgentServerInfo(data) {
13125
+ function formatAgentServerInfo(data, style = "cli") {
12814
13126
  let text = "## Server\n\n";
12815
13127
  const channels = data.channels ?? [];
12816
13128
  const agents = data.agents ?? [];
@@ -12818,8 +13130,14 @@ function formatAgentServerInfo(data) {
12818
13130
  text += formatRuntimeContext(data.runtimeContext);
12819
13131
  text += formatCurrentAgent(data);
12820
13132
  text += "### Channels\n";
12821
- text += "Visible public channels may appear even when `joined=false`. Private channels are shown only when you are a member; do not disclose private-channel names, membership, or content outside that channel. Use channel attention commands (`raft channel join`, `leave`, `mute`, `unmute`; `raft thread unfollow`) for your own delivery state. Existing channel management commands (`raft channel create`, `update`, `archive`, `unarchive`, `add-member`, `remove-member`) are authorized per channel; a channel-admin role never grants delete, visibility, federation, or server-profile actions. There is no Agent command for changing channel roles. Run any subcommand with `--help` for syntax.\n";
12822
- text += "Server-profile changes still use raft server update and remain server-role gated.\n";
13133
+ text += "Visible public channels may appear even when `joined=false`. Private channels are shown only when you are a member; do not disclose private-channel names, membership, or content outside that channel. ";
13134
+ text += style === "cli" ? `Use channel attention commands (\`${formatHintName(RAFT_HINTS.channelJoinName())}\`, \`leave\`, \`mute\`, \`unmute\`; \`${formatHintName(RAFT_HINTS.threadUnfollowName())}\`) for your own delivery state. Existing channel management commands (\`${formatHintName(RAFT_HINTS.channelCreateName())}\`, \`update\`, \`archive\`, \`unarchive\`, \`add-member\`, \`remove-member\`) are authorized per channel; a channel-admin role never grants delete, visibility, federation, or server-profile actions. There is no Agent command for changing channel roles. Run any subcommand with \`--help\` for syntax.\n` : `Use the channel attention tools (${[
13135
+ RAFT_HINTS.channelJoinName(),
13136
+ RAFT_HINTS.channelLeaveName(),
13137
+ RAFT_HINTS.channelMuteName(),
13138
+ RAFT_HINTS.channelUnmuteName()
13139
+ ].map((hint) => `\`${formatHintName(hint, style)}\``).join(", ")}; \`${formatHintName(RAFT_HINTS.threadUnfollowName(), style)}\`) for your own delivery state. Channel management (create, update, archive, unarchive, add-member, remove-member) has no tool: ${RAFT_NO_TOOL_WORDING}; a channel-admin role never grants delete, visibility, federation, or server-profile actions. There is no tool for changing channel roles.\n`;
13140
+ text += style === "cli" ? `Server-profile changes still use ${formatHintName(RAFT_HINTS.serverUpdateName())} and remain server-role gated.\n` : `Server-profile changes have no tool and remain server-role gated: ${RAFT_NO_TOOL_WORDING}.\n`;
12823
13141
  text += "Mute state is shown when the server provides it; otherwise it is omitted.\n";
12824
13142
  if (channels.length > 0) for (const t of channels) {
12825
13143
  const statusParts = [channelVisibility(t), t.joined ? "joined" : "not joined"];
@@ -12842,7 +13160,8 @@ function formatAgentServerInfo(data) {
12842
13160
  }
12843
13161
  else text += " (none)\n";
12844
13162
  text += "\n### Humans\n";
12845
- text += "To start a new DM: raft message send --target \"dm:@name\" <<'RAFTMSG' followed by the message body and RAFTMSG. To reply in an existing DM: reuse the target from received messages.\n";
13163
+ const newDm = formatHint(RAFT_HINTS.messageSend({ target: "dm:@name" }), style);
13164
+ text += style === "cli" ? `To start a new DM: ${newDm} <<'RAFTMSG' followed by the message body and RAFTMSG. To reply in an existing DM: reuse the target from received messages.\n` : `To start a new DM: ${newDm}. To reply in an existing DM: reuse the target from received messages.\n`;
12846
13165
  text += "Role labels show server-level owner/admin authority; no role label means ordinary member.\n";
12847
13166
  if (humans.length > 0) for (const u of humans) {
12848
13167
  const role = roleLabel(u.role);
@@ -12882,7 +13201,7 @@ function formatPageFooter(page) {
12882
13201
  return `${lines.join("\n")}\n`;
12883
13202
  }
12884
13203
  /** Single channel detail block. */
12885
- function formatAgentChannelInfo(channel, memberCounts) {
13204
+ function formatAgentChannelInfo(channel, memberCounts, style = "cli") {
12886
13205
  const lines = ["## Channel", ""];
12887
13206
  lines.push(`Channel: ${agentChannelRef(channel.name, channel.type)}`);
12888
13207
  if (channel.id) lines.push(`ID: ${channel.id}`);
@@ -12902,11 +13221,11 @@ function formatAgentChannelInfo(channel, memberCounts) {
12902
13221
  lines.push(`Members: ${agents + humans} (${agents} agents, ${humans} humans)`);
12903
13222
  }
12904
13223
  lines.push("");
12905
- lines.push(`More: raft channel members "${agentChannelRef(channel.name, channel.type)}"`);
13224
+ lines.push(`More: ${formatHint(RAFT_HINTS.channelMembers(agentChannelRef(channel.name, channel.type)), style)}`);
12906
13225
  return `${lines.join("\n")}\n`;
12907
13226
  }
12908
13227
  /** Compact server summary. */
12909
- function formatAgentServerSummary(data) {
13228
+ function formatAgentServerSummary(data, style = "cli") {
12910
13229
  const channels = data.channels ?? [];
12911
13230
  const agents = data.agents ?? [];
12912
13231
  const humans = data.humans ?? [];
@@ -12919,13 +13238,15 @@ function formatAgentServerSummary(data) {
12919
13238
  `Humans: ${humans.length}`,
12920
13239
  "",
12921
13240
  "Narrow queries:",
12922
- "- raft server info --channels",
12923
- "- raft server info --agents",
12924
- "- raft server info --humans",
12925
- "- raft channel info <name>",
12926
- "- raft user info <name>",
13241
+ ...[
13242
+ RAFT_HINTS.serverInfo({ view: "channels" }),
13243
+ RAFT_HINTS.serverInfo({ view: "agents" }),
13244
+ RAFT_HINTS.serverInfo({ view: "humans" }),
13245
+ RAFT_HINTS.channelInfo(),
13246
+ RAFT_HINTS.userInfo()
13247
+ ].map((hint) => `- ${formatHint(hint, style)}`),
12927
13248
  "",
12928
- "Full dump: raft server info --full"
13249
+ `Full dump: ${formatHint(RAFT_HINTS.serverInfo({ view: "full" }), style)}`
12929
13250
  ].join("\n")}\n`;
12930
13251
  }
12931
13252
  /** Channel listing page. */
@@ -13206,7 +13527,7 @@ const unfollowThreadRequestSchema = requestSchema()(object({
13206
13527
  reason: string().optional().describe("Short reason, kept with the unfollow.")
13207
13528
  }));
13208
13529
  const INVALID_CHANNEL_TARGET = "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported.";
13209
- async function resolveRegularChannel(client, target) {
13530
+ async function resolveRegularChannel(client, target, options) {
13210
13531
  const name = parseRaftRegularChannelTarget$1(target ?? "");
13211
13532
  if (!name) return failureOutcome(opError("INVALID_REQUEST", { message: INVALID_CHANNEL_TARGET }));
13212
13533
  const info = await client.server.info();
@@ -13214,7 +13535,7 @@ async function resolveRegularChannel(client, target) {
13214
13535
  const channel = info.data.channels.find((candidate) => candidate.name === name);
13215
13536
  if (!channel) return failureOutcome(opError("NOT_FOUND", {
13216
13537
  message: `Channel not found: ${target}`,
13217
- nextAction: "List visible channels with `raft server info --channels`; private channels need a human to add you."
13538
+ nextAction: `List visible channels with \`${formatHint(RAFT_HINTS.serverInfo({ view: "channels" }), options.hints)}\`; private channels need a human to add you.`
13218
13539
  }));
13219
13540
  return {
13220
13541
  id: channel.id,
@@ -13222,7 +13543,7 @@ async function resolveRegularChannel(client, target) {
13222
13543
  joined: channel.joined
13223
13544
  };
13224
13545
  }
13225
- async function joinChannel(client, request) {
13546
+ async function joinChannel(client, request, options = {}) {
13226
13547
  const invalid = validateOpRequest(channelTargetRequestSchema, request);
13227
13548
  if (invalid) return invalid;
13228
13549
  const result = await joinRaftChannelByTarget$1(client, { target: request.target });
@@ -13248,19 +13569,14 @@ async function joinChannel(client, request) {
13248
13569
  target: result.data.target,
13249
13570
  channelId: result.data.channelId
13250
13571
  },
13251
- next: {
13252
- kind: "read_target",
13253
- command: `raft message read --target "${result.data.target}"`,
13254
- args: { target: result.data.target },
13255
- why: "Read the channel before posting."
13256
- },
13572
+ next: hintStep("read_target", RAFT_HINTS.messageRead({ target: result.data.target }), "Read the channel before posting.", options.hints, { target: result.data.target }),
13257
13573
  text: state === "joined" ? `Joined ${result.data.target}.` : `Already a member of ${result.data.target}.`
13258
13574
  };
13259
13575
  }
13260
- async function leaveChannel(client, request) {
13576
+ async function leaveChannel(client, request, options = {}) {
13261
13577
  const invalid = validateOpRequest(channelTargetRequestSchema, request);
13262
13578
  if (invalid) return invalid;
13263
- const channel = await resolveRegularChannel(client, request.target);
13579
+ const channel = await resolveRegularChannel(client, request.target, options);
13264
13580
  if ("ok" in channel) return channel;
13265
13581
  if (!channel.joined) return {
13266
13582
  ok: true,
@@ -13291,10 +13607,10 @@ async function leaveChannel(client, request) {
13291
13607
  function formatSeq(value) {
13292
13608
  return value == null ? "none" : String(value);
13293
13609
  }
13294
- async function setChannelMute(client, request, action) {
13610
+ async function setChannelMute(client, request, action, options) {
13295
13611
  const invalid = validateOpRequest(channelTargetRequestSchema, request);
13296
13612
  if (invalid) return invalid;
13297
- const channel = await resolveRegularChannel(client, request.target);
13613
+ const channel = await resolveRegularChannel(client, request.target, options);
13298
13614
  if ("ok" in channel) return channel;
13299
13615
  const result = action === "mute" ? await client.channels.mute({ channelId: channel.id }, {}) : await client.channels.unmute({ channelId: channel.id });
13300
13616
  if (!result.ok) return failureFromClientResult(result);
@@ -13329,11 +13645,11 @@ async function setChannelMute(client, request, action) {
13329
13645
  text: lines.join("\n")
13330
13646
  };
13331
13647
  }
13332
- function muteChannel(client, request) {
13333
- return setChannelMute(client, request, "mute");
13648
+ function muteChannel(client, request, options = {}) {
13649
+ return setChannelMute(client, request, "mute", options);
13334
13650
  }
13335
- function unmuteChannel(client, request) {
13336
- return setChannelMute(client, request, "unmute");
13651
+ function unmuteChannel(client, request, options = {}) {
13652
+ return setChannelMute(client, request, "unmute", options);
13337
13653
  }
13338
13654
  async function channelMembers(client, request) {
13339
13655
  const invalid = validateOpRequest(channelMembersRequestSchema, request);
@@ -13383,7 +13699,7 @@ const channelInfoRequestSchema = requestSchema()(object({ target: string().descr
13383
13699
  * `raft channel info <target>`: the channel's facts from `server.info`, plus
13384
13700
  * member counts from its roster when the Server shows it.
13385
13701
  */
13386
- async function channelInfo(client, request) {
13702
+ async function channelInfo(client, request, options = {}) {
13387
13703
  const invalid = validateOpRequest(channelInfoRequestSchema, request);
13388
13704
  if (invalid) return invalid;
13389
13705
  const trimmed = request.target.trim();
@@ -13395,7 +13711,10 @@ async function channelInfo(client, request) {
13395
13711
  const channel = info.data.channels.find((candidate) => candidate.name === name);
13396
13712
  if (!channel) return failureOutcome(opError("NOT_FOUND", {
13397
13713
  message: `Channel not found or not visible: ${input}`,
13398
- nextAction: "Run `raft server info --channels --query <name>` to inspect visible channels, or ask a channel member to add you if this is private."
13714
+ nextAction: `Run \`${formatHint(RAFT_HINTS.serverInfo({
13715
+ view: "channels",
13716
+ query: true
13717
+ }), options.hints)}\` to inspect visible channels, or ask a channel member to add you if this is private.`
13399
13718
  }));
13400
13719
  const members = await client.channels.members({ channel: `#${name}` });
13401
13720
  const memberCounts = members.ok ? {
@@ -13410,7 +13729,7 @@ async function channelInfo(client, request) {
13410
13729
  memberCounts
13411
13730
  },
13412
13731
  next: null,
13413
- text: formatAgentChannelInfo(channel, memberCounts)
13732
+ text: formatAgentChannelInfo(channel, memberCounts, options.hints)
13414
13733
  };
13415
13734
  }
13416
13735
  //#endregion
@@ -13425,45 +13744,54 @@ const serverInfoRequestSchema = requestSchema()(object({
13425
13744
  ]).optional().describe("summary (default): counts; full: the whole overview; channels / agents / humans: one paged section."),
13426
13745
  offset: number$1().optional().describe("Section paging: rows to skip."),
13427
13746
  limit: number$1().optional().describe("Section paging: rows per page (default 50)."),
13428
- joined: boolean().optional().describe("channels view only: only channels you have joined.")
13747
+ joined: boolean().optional().describe("channels view only: only channels you have joined."),
13748
+ query: string().optional().describe("channels / agents / humans view only: keep rows whose name, description, or other visible text contains this (case-insensitive).")
13429
13749
  }));
13430
13750
  const showProfileRequestSchema = requestSchema()(object({ target: string().optional().describe("`@handle` of someone else; omit for your own profile.") }));
13431
13751
  /** The Agent API's profile body schema; at least one field is required (checked by the operation). */
13432
13752
  const updateProfileRequestSchema = agentApiProfileUpdateBodySchema;
13433
- function nextCommand(section, request, offset, limit, total) {
13753
+ /** The next page of a section, or null on the last page (`--query` / `--joined` carried, in the CLI's order). */
13754
+ function nextPageArgs(section, request, offset, limit, total) {
13434
13755
  const nextOffset = offset + limit;
13435
- if (nextOffset >= total) return void 0;
13436
- const parts = [
13437
- "raft server info",
13438
- `--${section}`,
13439
- `--offset ${nextOffset}`,
13440
- `--limit ${limit}`
13441
- ];
13442
- if (section === "channels" && request.joined) parts.push("--joined");
13443
- return parts.join(" ");
13756
+ if (nextOffset >= total) return null;
13757
+ const query = request.query?.trim() || void 0;
13758
+ const joined = section === "channels" && request.joined === true ? true : void 0;
13759
+ return {
13760
+ view: section,
13761
+ offset: nextOffset,
13762
+ limit,
13763
+ ...query === void 0 ? {} : { query },
13764
+ ...joined ? { joined } : {}
13765
+ };
13444
13766
  }
13445
- async function serverInfo(client, request = {}) {
13767
+ /** The CLI's `--query` match: any string field contains the needle, case-insensitively. */
13768
+ function includesQuery(row, query) {
13769
+ const needle = query?.trim().toLowerCase();
13770
+ if (!needle) return true;
13771
+ return Object.values(row).some((value) => typeof value === "string" && value.toLowerCase().includes(needle));
13772
+ }
13773
+ async function serverInfo(client, request = {}, options = {}) {
13446
13774
  const invalid = validateOpRequest(serverInfoRequestSchema, request);
13447
13775
  if (invalid) return invalid;
13776
+ const style = options.hints ?? "cli";
13448
13777
  const result = await client.server.info();
13449
13778
  if (!result.ok) return failureFromClientResult(result);
13450
13779
  const server = result.data;
13451
13780
  const view = request.view ?? "summary";
13452
- if (view === "summary") return {
13453
- ok: true,
13454
- state: "info",
13455
- data: {
13456
- view,
13457
- server,
13458
- page: null
13459
- },
13460
- next: {
13461
- kind: "list_channels",
13462
- command: "raft server info --channels",
13463
- why: "The summary only counts; list a section to see names."
13464
- },
13465
- text: formatAgentServerSummary(server)
13466
- };
13781
+ if (view === "summary") {
13782
+ const next = hintStep("list_channels", RAFT_HINTS.serverInfo({ view: "channels" }), "The summary only counts; list a section to see names.", style);
13783
+ return {
13784
+ ok: true,
13785
+ state: "info",
13786
+ data: {
13787
+ view,
13788
+ server,
13789
+ page: null
13790
+ },
13791
+ next,
13792
+ text: formatAgentServerSummary(server, style)
13793
+ };
13794
+ }
13467
13795
  if (view === "full") return {
13468
13796
  ok: true,
13469
13797
  state: "info",
@@ -13473,30 +13801,23 @@ async function serverInfo(client, request = {}) {
13473
13801
  page: null
13474
13802
  },
13475
13803
  next: null,
13476
- text: formatAgentServerInfo(server)
13804
+ text: formatAgentServerInfo(server, style)
13477
13805
  };
13478
13806
  const limit = Math.max(1, Math.trunc(request.limit ?? 50));
13479
13807
  const offset = Math.max(0, Math.trunc(request.offset ?? 0));
13480
- const rows = view === "channels" ? server.channels.filter((c) => !request.joined || c.joined) : view === "agents" ? server.agents : server.humans;
13808
+ const rows = (view === "channels" ? server.channels.filter((c) => !request.joined || c.joined) : view === "agents" ? server.agents : server.humans).filter((row) => includesQuery(row, request.query));
13481
13809
  const pageRows = rows.slice(offset, offset + limit);
13810
+ const nextArgs = nextPageArgs(view, request, offset, limit, rows.length);
13811
+ const nextHint = nextArgs ? RAFT_HINTS.serverInfo(nextArgs) : null;
13482
13812
  const page = {
13483
13813
  section: view,
13484
13814
  total: rows.length,
13485
13815
  offset,
13486
13816
  limit,
13487
- nextCommand: nextCommand(view, request, offset, limit, rows.length)
13817
+ nextCommand: nextHint ? formatHint(nextHint, style) : void 0
13488
13818
  };
13489
13819
  const text = view === "channels" ? formatAgentServerChannels(pageRows, page) : view === "agents" ? formatAgentServerAgents(pageRows, page) : formatAgentServerHumans(pageRows, page);
13490
- const next = page.nextCommand ? {
13491
- kind: "next_page",
13492
- command: page.nextCommand,
13493
- args: {
13494
- view,
13495
- offset: offset + limit,
13496
- limit
13497
- },
13498
- why: "More rows exist; one page is one page."
13499
- } : null;
13820
+ const next = nextArgs && nextHint ? hintStep("next_page", nextHint, "More rows exist; one page is one page.", style, nextArgs) : null;
13500
13821
  return {
13501
13822
  ok: true,
13502
13823
  state: "info",
@@ -13546,7 +13867,7 @@ const userInfoRequestSchema = requestSchema()(object({
13546
13867
  * their memberships among one page of the visible channels, checked one
13547
13868
  * channel roster at a time (a rejected roster is skipped and counted).
13548
13869
  */
13549
- async function userInfo(client, request) {
13870
+ async function userInfo(client, request, options = {}) {
13550
13871
  const invalid = validateOpRequest(userInfoRequestSchema, request);
13551
13872
  if (invalid) return invalid;
13552
13873
  const trimmed = request.name.trim();
@@ -13567,7 +13888,13 @@ async function userInfo(client, request) {
13567
13888
  } : null;
13568
13889
  if (!user) return failureOutcome(opError("NOT_FOUND", {
13569
13890
  message: `User not found or not visible: @${name}`,
13570
- nextAction: "Run `raft server info --agents --query <name>` or `raft server info --humans --query <name>` to inspect visible users."
13891
+ nextAction: `Run \`${formatHint(RAFT_HINTS.serverInfo({
13892
+ view: "agents",
13893
+ query: true
13894
+ }), options.hints)}\` or \`${formatHint(RAFT_HINTS.serverInfo({
13895
+ view: "humans",
13896
+ query: true
13897
+ }), options.hints)}\` to inspect visible users.`
13571
13898
  }));
13572
13899
  const visibleChannels = info.data.channels;
13573
13900
  const memberships = [];
@@ -13586,22 +13913,22 @@ async function userInfo(client, request) {
13586
13913
  });
13587
13914
  }
13588
13915
  const nextOffset = offset + limit;
13916
+ const nextHint = nextOffset < visibleChannels.length ? RAFT_HINTS.userInfo({
13917
+ name,
13918
+ offset: nextOffset,
13919
+ limit
13920
+ }) : null;
13589
13921
  const page = {
13590
13922
  total: visibleChannels.length,
13591
13923
  offset,
13592
13924
  limit,
13593
- nextCommand: nextOffset < visibleChannels.length ? `raft user info @${name} --offset ${nextOffset} --limit ${limit}` : void 0
13925
+ nextCommand: nextHint ? formatHint(nextHint, options.hints) : void 0
13594
13926
  };
13595
- const next = page.nextCommand ? {
13596
- kind: "next_page",
13597
- command: page.nextCommand,
13598
- args: {
13599
- name: `@${name}`,
13600
- offset: nextOffset,
13601
- limit
13602
- },
13603
- why: "Only one page of visible channels was inspected for memberships."
13604
- } : null;
13927
+ const next = nextHint ? hintStep("next_page", nextHint, "Only one page of visible channels was inspected for memberships.", options.hints, {
13928
+ name: `@${name}`,
13929
+ offset: nextOffset,
13930
+ limit
13931
+ }) : null;
13605
13932
  return {
13606
13933
  ok: true,
13607
13934
  state: "info",
@@ -13618,8 +13945,9 @@ async function userInfo(client, request) {
13618
13945
  //#endregion
13619
13946
  //#region ../shared/src/agentText/attachments.ts
13620
13947
  /** Upload receipt with attachment id and send-usage hint. */
13621
- function formatAgentAttachmentUploaded(attachment) {
13622
- return `File uploaded: ${attachment.filename} (${(attachment.sizeBytes / 1024).toFixed(1)}KB)\nAttachment ID: ${attachment.id}\n\nUse this ID with raft message send --attachment-id ${attachment.id} to include it in a message.\n`;
13948
+ function formatAgentAttachmentUploaded(attachment, style = "cli") {
13949
+ const send = formatHint(RAFT_HINTS.messageSend({ attachmentId: attachment.id }), style);
13950
+ return `File uploaded: ${attachment.filename} (${(attachment.sizeBytes / 1024).toFixed(1)}KB)\nAttachment ID: ${attachment.id}\n\nUse this ID with ${send} to include it in a message.\n`;
13623
13951
  }
13624
13952
  function anchorSummary(anchor) {
13625
13953
  if (anchor.type === "md-section") {
@@ -13661,6 +13989,7 @@ const attachmentCommentsRequestSchema = requestSchema()(object({
13661
13989
  attachmentId: string().describe("The attachment id."),
13662
13990
  limit: number$1().int().positive().optional().describe("Maximum comments to return.")
13663
13991
  }));
13992
+ const downloadAttachmentUrlRequestSchema = requestSchema()(object({ attachmentId: string().describe("The attachment id, as message lines show it (`id:…`).") }));
13664
13993
  /** Compatibility fallback for Servers that predate the capability endpoint (the CLI's constant). */
13665
13994
  const AGENT_ATTACHMENT_UPLOAD_FALLBACK_MAX_BYTES = 52428800;
13666
13995
  const FILENAME_MIME_MAP = {
@@ -13713,7 +14042,7 @@ function inferAttachmentMimeType(filename, bytes, explicit) {
13713
14042
  * `POST /upload` below the Server's direct-upload threshold, an upload session
13714
14043
  * (create → PUT to a presigned URL → complete) at or above it.
13715
14044
  */
13716
- async function uploadAttachment(client, transport, request) {
14045
+ async function uploadAttachment(client, transport, request, options = {}) {
13717
14046
  if (!request.target?.trim() || !request.filename?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and a filename are required to upload." }));
13718
14047
  if (!(request.bytes instanceof Uint8Array) || request.bytes.byteLength === 0) return failureOutcome(opError("INVALID_REQUEST", { message: "Refusing to upload a 0-byte attachment." }));
13719
14048
  if (request.mimeType !== void 0 && !MIME_TYPE_RE.test(request.mimeType.trim())) return failureOutcome(opError("INVALID_REQUEST", { message: `mimeType must look like type/subtype, got: ${request.mimeType}` }));
@@ -13725,7 +14054,7 @@ async function uploadAttachment(client, transport, request) {
13725
14054
  if (!resolved.ok) return failureFromClientResult(resolved);
13726
14055
  const channelId = resolved.data.channelId;
13727
14056
  const mimeType = inferAttachmentMimeType(request.filename, request.bytes, request.mimeType);
13728
- if (threshold !== null && request.bytes.byteLength >= threshold) return uploadThroughSession(client, transport, request, channelId, mimeType);
14057
+ if (threshold !== null && request.bytes.byteLength >= threshold) return uploadThroughSession(client, transport, request, channelId, mimeType, options);
13729
14058
  const copy = new Uint8Array(request.bytes.byteLength);
13730
14059
  copy.set(request.bytes);
13731
14060
  const form = new FormData();
@@ -13765,9 +14094,9 @@ async function uploadAttachment(client, transport, request) {
13765
14094
  }
13766
14095
  const parsed = agentApiContract.attachmentUpload.response.body.safeParse(body);
13767
14096
  if (!parsed.success) return failureOutcome(opError("INVALID_RESPONSE"));
13768
- return uploadedOutcome(request.target, parsed.data, mimeType);
14097
+ return uploadedOutcome(request.target, parsed.data, mimeType, options);
13769
14098
  }
13770
- function uploadedOutcome(target, data, mimeType) {
14099
+ function uploadedOutcome(target, data, mimeType, options) {
13771
14100
  return {
13772
14101
  ok: true,
13773
14102
  state: "uploaded",
@@ -13776,16 +14105,14 @@ function uploadedOutcome(target, data, mimeType) {
13776
14105
  target,
13777
14106
  mimeType: data.mimeType ?? mimeType
13778
14107
  },
13779
- next: {
13780
- kind: "send_with_attachment",
13781
- command: `raft message send --target "${target}" --attachment-id ${data.id}`,
13782
- args: {
13783
- target,
13784
- attachmentIds: [data.id]
13785
- },
13786
- why: "The upload alone posts nothing; send a message that links the attachment id."
13787
- },
13788
- text: formatAgentAttachmentUploaded(data)
14108
+ next: hintStep("send_with_attachment", RAFT_HINTS.messageSend({
14109
+ target,
14110
+ attachmentId: data.id
14111
+ }), "The upload alone posts nothing; send a message that links the attachment id.", options.hints, {
14112
+ target,
14113
+ attachmentIds: [data.id]
14114
+ }),
14115
+ text: formatAgentAttachmentUploaded(data, options.hints)
13789
14116
  };
13790
14117
  }
13791
14118
  const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -13796,7 +14123,7 @@ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
13796
14123
  * verifies against the object store. A PUT that definitely failed cancels the
13797
14124
  * session; a PUT whose outcome is unknown leaves it for completion to verify.
13798
14125
  */
13799
- async function uploadThroughSession(client, transport, request, channelId, mimeType) {
14126
+ async function uploadThroughSession(client, transport, request, channelId, mimeType, options) {
13800
14127
  const created = await client.attachments.createUploadSession({
13801
14128
  channelId,
13802
14129
  filename: request.filename,
@@ -13843,7 +14170,7 @@ async function uploadThroughSession(client, transport, request, channelId, mimeT
13843
14170
  const completed = await client.attachments.completeUploadSession({ uploadId });
13844
14171
  if (completed.ok) {
13845
14172
  const attachment = completed.data.attachment;
13846
- return uploadedOutcome(request.target, attachment, mimeType);
14173
+ return uploadedOutcome(request.target, attachment, mimeType, options);
13847
14174
  }
13848
14175
  const code = completed.error.kind === "http" ? completed.error.errorCode : null;
13849
14176
  if (!(code === "UPLOAD_OBJECT_NOT_FOUND" || code === "UPLOAD_VERIFICATION_IN_PROGRESS") || attempt === 2) return failureFromClientResult(completed);
@@ -13867,6 +14194,46 @@ async function downloadAttachment(client, request) {
13867
14194
  text: `Downloaded attachment ${request.attachmentId.slice(0, 8)} (${bytes.byteLength} bytes).`
13868
14195
  };
13869
14196
  }
14197
+ /**
14198
+ * Mint a short-lived URL for an attachment's bytes, for runtimes whose tools
14199
+ * cannot return binary data: the runtime (not the model) fetches `url` before
14200
+ * `expiresAt`. The URL is a bearer capability; do not log it. A Server whose
14201
+ * storage cannot presign answers 409 `download_url_unavailable`; that failure's
14202
+ * `next` points at the binary download (`attachments.download`).
14203
+ */
14204
+ async function downloadAttachmentUrl(client, request, options = {}) {
14205
+ const invalid = validateOpRequest(downloadAttachmentUrlRequestSchema, request);
14206
+ if (invalid) return invalid;
14207
+ const attachmentId = request.attachmentId?.trim();
14208
+ if (!attachmentId) return failureOutcome(opError("INVALID_REQUEST", { message: "An attachment id is required." }));
14209
+ const result = await client.attachments.downloadUrl({ attachmentId });
14210
+ if (!result.ok) {
14211
+ const failure = failureFromClientResult(result);
14212
+ if (failure.error.serverCode !== "download_url_unavailable") return failure;
14213
+ const download = RAFT_HINTS.attachmentDownload(attachmentId);
14214
+ const nextAction = `This Server's storage cannot mint download URLs; download the bytes instead: \`${formatHint(download, options.hints)}\`.`;
14215
+ return {
14216
+ ...failureOutcome({
14217
+ ...failure.error,
14218
+ nextAction
14219
+ }),
14220
+ next: hintStep("download_bytes", download, nextAction, options.hints, { attachmentId })
14221
+ };
14222
+ }
14223
+ const { url, expiresAt, filename, mimeType } = result.data;
14224
+ return {
14225
+ ok: true,
14226
+ state: "url",
14227
+ data: {
14228
+ url,
14229
+ expiresAt,
14230
+ filename,
14231
+ mimeType
14232
+ },
14233
+ next: null,
14234
+ text: `Download URL for ${filename} (${mimeType}), valid until ${expiresAt}:\n${url}`
14235
+ };
14236
+ }
13870
14237
  async function attachmentComments(client, request) {
13871
14238
  const invalid = validateOpRequest(attachmentCommentsRequestSchema, request);
13872
14239
  if (invalid) return invalid;
@@ -14015,14 +14382,15 @@ function renderSearchPreview(content, query) {
14015
14382
  ].join("");
14016
14383
  }
14017
14384
  /** Search results with <match>/<omit /> preview markup. */
14018
- function formatAgentSearchResults(query, data, offset, sort, limit) {
14385
+ function formatAgentSearchResults(query, data, offset, sort, limit, style = "cli") {
14019
14386
  const trimmedQuery = query.trim();
14387
+ const flag = (name) => formatHintFlag(name, name, style);
14020
14388
  const oldestShown = data.results?.length ? data.results[data.results.length - 1]?.createdAt : void 0;
14021
- const nextPageHint = sort === "recent" && oldestShown ? `page with --before ${oldestShown} (pages OLDER only; NOT a complete traversal: stored times are microsecond but this key is millisecond, so rows inside the boundary millisecond can be skipped, and a full page sharing one timestamp can repeat indefinitely; copy the key verbatim)` : `page with --offset ${(offset ?? 0) + (data.results?.length ?? 0)}`;
14389
+ const nextPageHint = sort === "recent" && oldestShown ? `page with ${flag("before")} ${oldestShown} (pages OLDER only; NOT a complete traversal: stored times are microsecond but this key is millisecond, so rows inside the boundary millisecond can be skipped, and a full page sharing one timestamp can repeat indefinitely; copy the key verbatim)` : `page with ${flag("offset")} ${(offset ?? 0) + (data.results?.length ?? 0)}`;
14022
14390
  const effectiveLimit = Math.min(limit ?? 20, 50);
14023
14391
  const countEqualsLimit = (data.results?.length ?? 0) === effectiveLimit;
14024
- const limitSource = limit === void 0 ? `the server default of ${effectiveLimit}` : limit > 50 ? `the server cap of ${effectiveLimit} (your --limit ${limit} was clamped)` : `the --limit ${effectiveLimit} that was requested`;
14025
- const atCapRemedy = effectiveLimit < 50 ? "re-run with a higher --limit to tell the two apart" : `this is already the server's maximum page, so completeness CANNOT be determined from this result: a higher --limit is clamped back to ${effectiveLimit} and paging will not reveal it; narrow the query (add --sender, --target, --after or --before) until fewer than ${effectiveLimit} results match`;
14392
+ const limitSource = limit === void 0 ? `the server default of ${effectiveLimit}` : limit > 50 ? `the server cap of ${effectiveLimit} (your ${flag("limit")} ${limit} was clamped)` : `the ${flag("limit")} ${effectiveLimit} that was requested`;
14393
+ const atCapRemedy = effectiveLimit < 50 ? `re-run with a higher ${flag("limit")} to tell the two apart` : `this is already the server's maximum page, so completeness CANNOT be determined from this result: a higher ${flag("limit")} is clamped back to ${effectiveLimit} and paging will not reveal it; narrow the query (add ${flag("sender")}, ${flag("target")}, ${flag("after")} or ${flag("before")}) until fewer than ${effectiveLimit} results match`;
14026
14394
  const truncation = data.hasMore === true ? `truncated=true · more results exist, ${nextPageHint}` : data.hasMore === false ? countEqualsLimit ? `truncated=unknown · server reported hasMore=false, but this page returned exactly ${limitSource}, which is also what a capped page returns; ${atCapRemedy}` : "truncated=false" : "truncated=unknown · server did not report hasMore";
14027
14395
  if (!data.results || data.results.length === 0) return `No search results. (${truncation})`;
14028
14396
  const formatted = data.results.map((result) => {
@@ -14071,7 +14439,7 @@ const reactRequestSchema = requestSchema()(object({
14071
14439
  messageId: string().describe("Full or short message id."),
14072
14440
  emoji: string().describe("One reaction emoji.")
14073
14441
  }));
14074
- async function searchMessages(client, request) {
14442
+ async function searchMessages(client, request, options = {}) {
14075
14443
  const invalid = validateOpRequest(searchMessagesRequestSchema, request);
14076
14444
  if (invalid) return invalid;
14077
14445
  if (!request.query?.trim() && !request.target && !request.sender) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass a query, or filter by target or sender." }));
@@ -14093,11 +14461,16 @@ async function searchMessages(client, request) {
14093
14461
  results: data.results,
14094
14462
  hasMore
14095
14463
  };
14464
+ const nextArgs = {
14465
+ ...request,
14466
+ offset: (request.offset ?? 0) + data.results.length
14467
+ };
14096
14468
  const next = hasMore ? {
14097
14469
  kind: "next_search_page",
14098
- args: {
14099
- ...request,
14100
- offset: (request.offset ?? 0) + data.results.length
14470
+ args: nextArgs,
14471
+ operation: {
14472
+ name: "messages.search",
14473
+ args: nextArgs
14101
14474
  },
14102
14475
  why: "More results exist; page on, or narrow the query."
14103
14476
  } : null;
@@ -14109,31 +14482,32 @@ async function searchMessages(client, request) {
14109
14482
  text: formatAgentSearchResults(page.query, {
14110
14483
  results: data.results,
14111
14484
  ...hasMore === null ? {} : { hasMore }
14112
- }, request.offset, request.sort, request.limit)
14485
+ }, request.offset, request.sort, request.limit, options.hints)
14113
14486
  };
14114
14487
  }
14115
- async function resolveMessage(client, request) {
14488
+ async function resolveMessage(client, request, options = {}) {
14116
14489
  const invalid = validateOpRequest(resolveMessageRequestSchema, request);
14117
14490
  if (invalid) return invalid;
14118
14491
  if (!request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id is required." }));
14119
14492
  const result = await client.messages.resolve({ msgId: request.messageId });
14120
14493
  if (!result.ok) return failureFromClientResult(result);
14121
- const message = projectRaftMessage(result.data.message);
14494
+ const message = projectRaftMessage(result.data.message, options.hints);
14122
14495
  if (!message) return failureOutcome(opError("INVALID_RESPONSE", { message: "The resolved message carries no conversation identity." }));
14123
14496
  return {
14124
14497
  ok: true,
14125
14498
  state: "message",
14126
14499
  data: message,
14127
- next: {
14128
- kind: "read_target",
14129
- command: `raft message read --target "${message.target}" --around ${message.shortId ?? request.messageId}`,
14130
- args: {
14131
- target: message.target,
14132
- around: message.id ?? request.messageId
14133
- },
14134
- why: "Read the surrounding context before acting on one message."
14135
- },
14136
- text: formatAgentMessages([toAgentMessageLike(message.raw)])
14500
+ next: hintStep("read_target", RAFT_HINTS.messageRead({
14501
+ target: message.target,
14502
+ around: {
14503
+ shown: message.shortId ?? request.messageId,
14504
+ id: message.id ?? request.messageId
14505
+ }
14506
+ }), "Read the surrounding context before acting on one message.", options.hints, {
14507
+ target: message.target,
14508
+ around: message.id ?? request.messageId
14509
+ }),
14510
+ text: formatAgentMessages([toAgentMessageLike(message.raw)], options.hints)
14137
14511
  };
14138
14512
  }
14139
14513
  async function reactToMessage(client, request, action = "add") {
@@ -14163,17 +14537,20 @@ function normalizeAgentMentionAction(action) {
14163
14537
  if (action === "add" || action === "invite") return "add";
14164
14538
  return null;
14165
14539
  }
14166
- function formatActionCommands(action) {
14540
+ function actionVerbs(action) {
14167
14541
  const verbs = action.availableActions.map(normalizeAgentMentionAction).filter((verb) => verb !== null);
14168
- return Array.from(new Set(verbs)).map((verb) => ` ${verb}: raft mention ${verb} ${action.resolutionId}`);
14542
+ return Array.from(new Set(verbs));
14543
+ }
14544
+ function formatActionCommands(action, style) {
14545
+ return actionVerbs(action).map((verb) => ` ${verb}: ${formatHint(RAFT_HINTS.mentionAction(verb, action.resolutionId), style)}`);
14169
14546
  }
14170
14547
  function formatAuthoredMentionToken(targetHandle) {
14171
14548
  return targetHandle.startsWith("@") ? targetHandle : `@${targetHandle}`;
14172
14549
  }
14173
14550
  const PENDING_MENTION_ACTION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14174
14551
  /** Per-token mention recovery command line; null when the id is not a UUID (fail closed). */
14175
- function formatAgentMentionNotifyRecoveryCommand(resolutionId) {
14176
- return PENDING_MENTION_ACTION_ID_RE.test(resolutionId) ? `raft mention notify ${resolutionId}` : null;
14552
+ function formatAgentMentionNotifyRecoveryCommand(resolutionId, style = "cli") {
14553
+ return PENDING_MENTION_ACTION_ID_RE.test(resolutionId) ? formatHint(RAFT_HINTS.mentionAction("notify", resolutionId), style) : null;
14177
14554
  }
14178
14555
  function toAgentSenderPendingMentionAction(action) {
14179
14556
  return {
@@ -14230,22 +14607,24 @@ function normalizeAgentMentionActionResults(data) {
14230
14607
  })).filter((item) => item.resolutionId.length > 0);
14231
14608
  }
14232
14609
  const PENDING_DEFAULT_LIMIT = 50;
14233
- function pendingPageVerdict(hasMore, limit, shown) {
14234
- const asked = limit === void 0 ? "server default 50" : `--limit ${limit}`;
14235
- if (hasMore === true) return (limit ?? PENDING_DEFAULT_LIMIT) >= 100 ? `shown ${shown}, ${asked} — truncated=true · more pending actions exist AND this is the server's maximum page (100); this route has no offset or cursor, so the rest cannot be reached from here — resolve what you can and re-run; rows with no available actions are not actionable here and will remain until they expire` : `shown ${shown}, ${asked} — truncated=true · more pending actions exist; raise --limit (server caps at 100) or resolve these first and re-run`;
14610
+ function pendingPageVerdict(hasMore, limit, shown, style) {
14611
+ const limitFlag = formatHintFlag("limit", "limit", style);
14612
+ const asked = limit === void 0 ? "server default 50" : `${limitFlag} ${limit}`;
14613
+ if (hasMore === true) return (limit ?? PENDING_DEFAULT_LIMIT) >= 100 ? `shown ${shown}, ${asked} — truncated=true · more pending actions exist AND this is the server's maximum page (100); this route has no offset or cursor, so the rest cannot be reached from here — resolve what you can and re-run; rows with no available actions are not actionable here and will remain until they expire` : `shown ${shown}, ${asked} — truncated=true · more pending actions exist; raise ${limitFlag} (server caps at 100) or resolve these first and re-run`;
14236
14614
  if (hasMore === false) return `shown ${shown}, ${asked} — truncated=false · this is the complete list`;
14237
14615
  return `shown ${shown}, ${asked} — truncated=unknown · server did not report has_more, so completeness is NOT asserted`;
14238
14616
  }
14239
14617
  /** Undelivered-mentions partial result (send) or the pending list. */
14240
14618
  function formatAgentPendingMentionActions(actions, opts = {}) {
14619
+ const style = opts.hints ?? "cli";
14241
14620
  const unresolvedMentionHandles = opts.source === "send" ? Array.from(new Set(opts.unresolvedMentionHandles ?? [])) : [];
14242
- const pageNote = opts.source === "pending" ? pendingPageVerdict(opts.hasMore, opts.limit, actions.length) : "";
14621
+ const pageNote = opts.source === "pending" ? pendingPageVerdict(opts.hasMore, opts.limit, actions.length, style) : "";
14243
14622
  if (actions.length === 0 && unresolvedMentionHandles.length === 0) return opts.source === "pending" ? `Pending mention actions\n\nNo pending mention actions. (${pageNote})\n` : "";
14244
14623
  if (opts.source === "send") {
14245
14624
  const lines = [
14246
14625
  "Undelivered mentions — partial result",
14247
14626
  "Message effect: status=queued. Queue acceptance is the only message proof.",
14248
- "Do not rerun `raft message send`; the message is already queued and a retry could duplicate it.",
14627
+ `Do not rerun \`${formatHintName(RAFT_HINTS.messageSendName(), style)}\`; the message is already queued and a retry could duplicate it.`,
14249
14628
  "Each row below is bound to the literal @token from your message.",
14250
14629
  "For a literal name rather than a recipient, wrap the @handle in inline or fenced code.",
14251
14630
  ""
@@ -14259,10 +14638,10 @@ function formatAgentPendingMentionActions(actions, opts = {}) {
14259
14638
  if (action.messageId) lines.push(` message: ${action.messageId}`);
14260
14639
  lines.push(` expires: ${action.expiresAt ?? "unknown"}`);
14261
14640
  if (action.recoveryCommand) {
14262
- lines.push(` recovery: ${action.recoveryCommand}`);
14641
+ lines.push(` recovery: ${style === "cli" ? action.recoveryCommand : formatAgentMentionNotifyRecoveryCommand(action.resolutionId, style)}`);
14263
14642
  lines.push(" note: the handle resolved, but the target was not in this conversation at send time. This does not prove the person left the server.");
14264
14643
  lines.push(" note: notify exits nonzero unless the target queue accepts the delivery.");
14265
- } else lines.push(" recovery: unavailable because the pending action id is invalid; inspect `raft mention pending` without resending the message.");
14644
+ } else lines.push(` recovery: unavailable because the pending action id is invalid; inspect \`${formatHint(RAFT_HINTS.mentionPending(), style)}\` without resending the message.`);
14266
14645
  }
14267
14646
  for (const rawHandle of unresolvedMentionHandles) {
14268
14647
  const warning = toAgentSenderUnresolvedMentionWarning(rawHandle);
@@ -14286,11 +14665,11 @@ function formatAgentPendingMentionActions(actions, opts = {}) {
14286
14665
  if (action.messageId) lines.push(` message: ${action.messageId}`);
14287
14666
  lines.push(` reason: ${formatPendingReason(action.reason)}`);
14288
14667
  if (action.expiresAt) lines.push(` expires: ${action.expiresAt}`);
14289
- const commands = formatActionCommands(action);
14668
+ const commands = formatActionCommands(action, style);
14290
14669
  if (commands.length > 0) {
14291
14670
  lines.push(" recovery commands:");
14292
14671
  lines.push(...commands);
14293
- if (commands.some((command) => command.includes(" mention notify "))) lines.push(" note: notify exits nonzero unless the target queue accepts the delivery.");
14672
+ if (actionVerbs(action).includes("notify")) lines.push(" note: notify exits nonzero unless the target queue accepts the delivery.");
14294
14673
  }
14295
14674
  }
14296
14675
  return `${lines.join("\n")}\n`;
@@ -14341,12 +14720,14 @@ function formatAgentSenderMentionDeliveries(messageId, deliveries) {
14341
14720
  //#endregion
14342
14721
  //#region ../shared/src/agentOps/mentions.ts
14343
14722
  const pendingMentionActionsRequestSchema = requestSchema()(object({ limit: number$1().int().positive().optional().describe("Maximum pending mentions to list.") }));
14723
+ const mentionResolutionIdsRequestSchema = requestSchema()(object({ resolutionIds: array(string()).describe("resolutionId values from mentions.pending.") }));
14724
+ /** @deprecated The request of the deprecated `mentions.execute`; use `mentionResolutionIdsRequestSchema` (`mentions.notify` / `mentions.add`). */
14344
14725
  const executeMentionActionRequestSchema = requestSchema()(object({
14345
14726
  action: _enum(["notify", "add"]).describe("notify: tell the mentioned target about the message; add: add them to the conversation."),
14346
14727
  resolutionIds: array(string()).describe("resolutionId values from mentions.pending.")
14347
14728
  }));
14348
14729
  const senderMentionDeliveriesRequestSchema = requestSchema()(object({ messageId: string().describe("Id of a message you sent.") }));
14349
- async function pendingMentionActions(client, request = {}) {
14730
+ async function pendingMentionActions(client, request = {}, options = {}) {
14350
14731
  const invalid = validateOpRequest(pendingMentionActionsRequestSchema, request);
14351
14732
  if (invalid) return invalid;
14352
14733
  const result = await client.mentions.pendingActions(request.limit === void 0 ? {} : { limit: String(request.limit) });
@@ -14354,15 +14735,10 @@ async function pendingMentionActions(client, request = {}) {
14354
14735
  const actions = normalizeAgentPendingMentionActions(result.data);
14355
14736
  const hasMore = typeof result.data.has_more === "boolean" ? result.data.has_more : null;
14356
14737
  const first = actions.find((a) => a.availableActions.length > 0);
14357
- const next = first ? {
14358
- kind: "resolve_mention",
14359
- command: `raft mention notify ${first.resolutionId}`,
14360
- args: {
14361
- action: "notify",
14362
- resolutionIds: [first.resolutionId]
14363
- },
14364
- why: "This @mention reached nobody at send time; notify the target (or add them) so the message is seen."
14365
- } : null;
14738
+ const next = first ? hintStep("resolve_mention", RAFT_HINTS.mentionAction("notify", first.resolutionId), "This @mention reached nobody at send time; notify the target (or add them) so the message is seen.", options.hints, {
14739
+ action: "notify",
14740
+ resolutionIds: [first.resolutionId]
14741
+ }) : null;
14366
14742
  return {
14367
14743
  ok: true,
14368
14744
  state: actions.length > 0 ? "pending" : "empty",
@@ -14375,17 +14751,36 @@ async function pendingMentionActions(client, request = {}) {
14375
14751
  text: formatAgentPendingMentionActions(actions, {
14376
14752
  source: "pending",
14377
14753
  ...hasMore === null ? {} : { hasMore },
14378
- limit: request.limit
14754
+ limit: request.limit,
14755
+ hints: options.hints
14379
14756
  })
14380
14757
  };
14381
14758
  }
14759
+ /** Tell the targets of unreached @mentions about the message (`raft mention notify`). */
14760
+ async function notifyMentions(client, request) {
14761
+ const invalid = validateOpRequest(mentionResolutionIdsRequestSchema, request);
14762
+ if (invalid) return invalid;
14763
+ return runMentionAction(client, "notify", request.resolutionIds);
14764
+ }
14765
+ /** Add the targets of unreached @mentions to the conversation (`raft mention add`). */
14766
+ async function addMentions(client, request) {
14767
+ const invalid = validateOpRequest(mentionResolutionIdsRequestSchema, request);
14768
+ if (invalid) return invalid;
14769
+ return runMentionAction(client, "add", request.resolutionIds);
14770
+ }
14771
+ /** @deprecated Backs the deprecated `mentions.execute`; use `notifyMentions` / `addMentions`. */
14382
14772
  async function executeMentionAction(client, request) {
14383
14773
  const invalid = validateOpRequest(executeMentionActionRequestSchema, request);
14384
14774
  if (invalid) return invalid;
14385
- const ids = (request.resolutionIds ?? []).map((id) => id.trim()).filter(Boolean);
14386
- if (ids.length === 0 || request.action !== "notify" && request.action !== "add") return failureOutcome(opError("INVALID_REQUEST", { message: "Pass an action (notify | add) and at least one resolution id." }));
14775
+ const missing = "Pass an action (notify | add) and at least one resolution id.";
14776
+ if (request.action !== "notify" && request.action !== "add") return failureOutcome(opError("INVALID_REQUEST", { message: missing }));
14777
+ return runMentionAction(client, request.action, request.resolutionIds, missing);
14778
+ }
14779
+ async function runMentionAction(client, action, resolutionIds, missingIdsMessage = "Pass at least one resolution id.") {
14780
+ const ids = (resolutionIds ?? []).map((id) => id.trim()).filter(Boolean);
14781
+ if (ids.length === 0) return failureOutcome(opError("INVALID_REQUEST", { message: missingIdsMessage }));
14387
14782
  const result = await client.mentions.executeAction({
14388
- action: request.action,
14783
+ action,
14389
14784
  resolutionIds: ids
14390
14785
  });
14391
14786
  if (!result.ok) return failureFromClientResult(result);
@@ -14394,11 +14789,11 @@ async function executeMentionAction(client, request) {
14394
14789
  ok: true,
14395
14790
  state: "executed",
14396
14791
  data: {
14397
- action: request.action,
14792
+ action,
14398
14793
  results
14399
14794
  },
14400
14795
  next: null,
14401
- text: formatAgentMentionActionResults(request.action, results)
14796
+ text: formatAgentMentionActionResults(action, results)
14402
14797
  };
14403
14798
  }
14404
14799
  async function senderMentionDeliveries(client, request) {
@@ -14473,7 +14868,7 @@ async function getManualTopic(client, request) {
14473
14868
  text: formatAgentKnowledgeStdout(result.data.content)
14474
14869
  };
14475
14870
  }
14476
- async function searchManual(client, request) {
14871
+ async function searchManual(client, request, options = {}) {
14477
14872
  const malformed = validateOpRequest(searchManualRequestSchema, request);
14478
14873
  if (malformed) return malformed;
14479
14874
  const invalid = requireContext(request);
@@ -14489,12 +14884,7 @@ async function searchManual(client, request) {
14489
14884
  const data = result.data;
14490
14885
  const results = data.results;
14491
14886
  const first = results[0];
14492
- const next = first ? {
14493
- kind: "read_manual_topic",
14494
- command: `raft manual get ${first.slug} --intent "…" --reason "…"`,
14495
- args: { topic: first.slug },
14496
- why: "Open the best-matching topic."
14497
- } : null;
14887
+ const next = first ? hintStep("read_manual_topic", RAFT_HINTS.manualGet(first.slug), "Open the best-matching topic.", options.hints, { topic: first.slug }) : null;
14498
14888
  return {
14499
14889
  ok: true,
14500
14890
  state: results.length > 0 ? "results" : "empty",
@@ -14695,29 +15085,26 @@ function formatAgentActionCardPosted(target, messageId) {
14695
15085
  * the preparer in the card's own thread; a card posted inside a thread has no
14696
15086
  * thread of its own, so the reply lands in that same thread.
14697
15087
  */
14698
- function awaitConfirmationNext(target, messageId) {
15088
+ function awaitConfirmationNext(target, messageId, style) {
14699
15089
  const short = messageId.slice(0, 8);
14700
- if (getParentTargetForThread(target) !== null) return {
14701
- kind: "await_confirmation",
14702
- command: `raft message read --target "${target}" --around ${short}`,
14703
- args: {
14704
- target,
14705
- messageId
14706
- },
14707
- why: "A human must click the card to commit it. When it is executed (or fails), the outcome arrives as a reply in this thread that @mentions you; you do not need to poll."
14708
- };
15090
+ if (getParentTargetForThread(target) !== null) return hintStep("await_confirmation", RAFT_HINTS.messageRead({
15091
+ target,
15092
+ around: {
15093
+ shown: short,
15094
+ id: messageId
15095
+ }
15096
+ }), "A human must click the card to commit it. When it is executed (or fails), the outcome arrives as a reply in this thread that @mentions you; you do not need to poll.", style, {
15097
+ target,
15098
+ around: messageId,
15099
+ messageId
15100
+ });
14709
15101
  const thread = agentTaskThreadTarget(target, messageId);
14710
- return {
14711
- kind: "await_confirmation",
14712
- command: `raft message read --target "${thread}"`,
14713
- args: {
14714
- target: thread,
14715
- messageId
14716
- },
14717
- why: "A human must click the card to commit it. When it is executed (or fails), the outcome arrives as a reply in the card's thread that @mentions you; you do not need to poll."
14718
- };
15102
+ return hintStep("await_confirmation", RAFT_HINTS.messageRead({ target: thread }), "A human must click the card to commit it. When it is executed (or fails), the outcome arrives as a reply in the card's thread that @mentions you; you do not need to poll.", style, {
15103
+ target: thread,
15104
+ messageId
15105
+ });
14719
15106
  }
14720
- async function prepareActionCard(client, request) {
15107
+ async function prepareActionCard(client, request, options = {}) {
14721
15108
  if (typeof request?.target !== "string" || !request.target.trim() || !request.action) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and an action are required to prepare a card." }));
14722
15109
  const invalid = validateOpRequest(prepareActionCardRequestSchema, request);
14723
15110
  if (invalid) return invalid;
@@ -14736,7 +15123,7 @@ async function prepareActionCard(client, request) {
14736
15123
  ok: true,
14737
15124
  state: "prepared",
14738
15125
  data: card,
14739
- next: awaitConfirmationNext(request.target, card.messageId),
15126
+ next: awaitConfirmationNext(request.target, card.messageId, options.hints ?? "cli"),
14740
15127
  text: formatAgentActionCardPosted(request.target, card.messageId)
14741
15128
  };
14742
15129
  }
@@ -15147,6 +15534,14 @@ const OPERATION_DEFS = [
15147
15534
  consumes: nothing,
15148
15535
  output: small
15149
15536
  },
15537
+ {
15538
+ name: "attachments.downloadUrl",
15539
+ description: "Get a short-lived (5 minute) URL for an attachment's bytes, with its filename and MIME type, for runtimes that fetch files themselves. The URL grants access to the file: do not post it in messages.",
15540
+ schema: downloadAttachmentUrlRequestSchema,
15541
+ routes: ["attachmentDownloadUrl"],
15542
+ consumes: nothing,
15543
+ output: small
15544
+ },
15150
15545
  {
15151
15546
  name: "attachments.comments",
15152
15547
  description: "List the comments on an attachment.",
@@ -15170,21 +15565,47 @@ const OPERATION_DEFS = [
15170
15565
  }
15171
15566
  },
15172
15567
  {
15173
- name: "mentions.execute",
15174
- description: "Deliver unreached @mentions: notify the mentioned target, or add them to the conversation.",
15175
- schema: executeMentionActionRequestSchema,
15568
+ name: "mentions.notify",
15569
+ description: "Deliver unreached @mentions by notifying each mentioned target about the message, without adding them to the conversation. Takes resolutionId values from mentions.pending.",
15570
+ schema: mentionResolutionIdsRequestSchema,
15176
15571
  routes: ["mentionActionsExecute"],
15177
15572
  consumes: nothing,
15178
15573
  output: small
15179
15574
  },
15180
15575
  {
15181
- name: "mentions.deliveries",
15576
+ name: "mentions.add",
15577
+ description: "Deliver unreached @mentions by adding each mentioned target to the conversation, so they see the message and what follows. Takes resolutionId values from mentions.pending.",
15578
+ schema: mentionResolutionIdsRequestSchema,
15579
+ routes: ["mentionActionsExecute"],
15580
+ consumes: nothing,
15581
+ output: small
15582
+ },
15583
+ {
15584
+ name: "mentions.delivery",
15182
15585
  description: "Whether each @mention in a message you sent reached its target.",
15183
15586
  schema: senderMentionDeliveriesRequestSchema,
15184
15587
  routes: ["senderMentionDeliveries"],
15185
15588
  consumes: nothing,
15186
15589
  output: small
15187
15590
  },
15591
+ {
15592
+ name: "mentions.execute",
15593
+ description: "Deprecated: use mentions.notify / mentions.add. Removed in the next minor release. Deliver unreached @mentions: notify the mentioned target, or add them to the conversation.",
15594
+ schema: executeMentionActionRequestSchema,
15595
+ routes: ["mentionActionsExecute"],
15596
+ consumes: nothing,
15597
+ output: small,
15598
+ deprecated: true
15599
+ },
15600
+ {
15601
+ name: "mentions.deliveries",
15602
+ description: "Deprecated: use mentions.delivery. Removed in the next minor release. Whether each @mention in a message you sent reached its target.",
15603
+ schema: senderMentionDeliveriesRequestSchema,
15604
+ routes: ["senderMentionDeliveries"],
15605
+ consumes: nothing,
15606
+ output: small,
15607
+ deprecated: true
15608
+ },
15188
15609
  {
15189
15610
  name: "actions.prepare",
15190
15611
  description: "Post an action card (create a channel, add members, create an agent, or an integration step) for a human to confirm; the human who clicks it carries it out as themselves. When it is executed (or fails), the outcome arrives as a reply that @mentions you in the card's thread (or in the thread the card was posted in).",
@@ -15448,10 +15869,6 @@ const OPERATION_DEFS = [
15448
15869
  output: small
15449
15870
  }
15450
15871
  ];
15451
- /** `tasks.updateStatus` → `tasks_update_status`. */
15452
- function raftToolNameFor(name) {
15453
- return name.replace(/\./g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
15454
- }
15455
15872
  const IDEMPOTENCY_STRENGTH = {
15456
15873
  natural: 0,
15457
15874
  key: 1,
@@ -15523,11 +15940,17 @@ function createRaft(options) {
15523
15940
  const serverUrl = requireServerUrl(options.serverUrl);
15524
15941
  const authorization = `Bearer ${requireAgentCredential(options.credential)}`;
15525
15942
  const frontier = SeenFrontier.fromSnapshot(options.frontier);
15943
+ if (options.hints !== void 0 && options.hints !== "cli" && options.hints !== "tool") throw new TypeError("createRaft: hints must be \"cli\" or \"tool\".");
15944
+ const hints = options.hints ?? "cli";
15945
+ /** How every operation renders its hints. */
15946
+ const style = { hints };
15947
+ /** The SDK's default NOT_FOUND next action names a command; render it in the configured style. */
15948
+ const styled = (outcome) => restyleDefaultNextAction(outcome, hints);
15526
15949
  const session = new RaftStateSession(options.state, frontier, options.onStateSaveError);
15527
15950
  /** Load before, run, then save if the operation succeeded and changed the state. */
15528
15951
  const withState = async (run) => {
15529
15952
  await session.ensureLoaded();
15530
- const outcome = await run();
15953
+ const outcome = styled(await run());
15531
15954
  if (outcome.ok) await session.save();
15532
15955
  return outcome;
15533
15956
  };
@@ -15563,7 +15986,7 @@ function createRaft(options) {
15563
15986
  const outcome = await sendMessage(api, pending ? {
15564
15987
  ...request,
15565
15988
  idempotencyKey: pending.idempotencyKey
15566
- } : request, seen);
15989
+ } : request, seen, style);
15567
15990
  if (outcome.ok && outcome.state === "interrupted" && outcome.interrupt.resume.idempotencyKey) session.rememberContinuation({
15568
15991
  target: request.target,
15569
15992
  idempotencyKey: outcome.interrupt.resume.idempotencyKey,
@@ -15578,7 +16001,7 @@ function createRaft(options) {
15578
16001
  const outcome = await checkInbox(inboxApi, {
15579
16002
  ...request,
15580
16003
  ...since === void 0 ? {} : { since }
15581
- }, seen);
16004
+ }, seen, style);
15582
16005
  if (outcome.ok) {
15583
16006
  session.markDirty();
15584
16007
  if (since !== void 0 && (request.ack ?? "cursor") === "cursor") session.commit(since);
@@ -15597,7 +16020,7 @@ function createRaft(options) {
15597
16020
  if (batch.ackMode === "cursor" && batch.messages.length > 0) session.setPending(batch.cursor);
15598
16021
  session.markDirty();
15599
16022
  await session.save();
15600
- });
16023
+ }, style);
15601
16024
  })();
15602
16025
  const commitInbox = async (target) => {
15603
16026
  await session.ensureLoaded();
@@ -15609,7 +16032,7 @@ function createRaft(options) {
15609
16032
  };
15610
16033
  /** `seen` undefined: read without recording anything (a code read). */
15611
16034
  const readWithState = (request, seen) => withState(async () => {
15612
- const outcome = await readHistory(api, request, seen);
16035
+ const outcome = await readHistory(api, request, seen, style);
15613
16036
  if (outcome.ok && seen) session.markDirty();
15614
16037
  return outcome;
15615
16038
  });
@@ -15618,16 +16041,16 @@ function createRaft(options) {
15618
16041
  wake: {
15619
16042
  verifyNotice: (input) => verifyInboxNotice(input),
15620
16043
  webhook: {
15621
- status: () => webhookStatus(api),
15622
- register: (request) => registerWebhook(api, request),
15623
- unregister: () => unregisterWebhook(api)
16044
+ status: () => webhookStatus(api).then(styled),
16045
+ register: (request) => registerWebhook(api, request).then(styled),
16046
+ unregister: () => unregisterWebhook(api).then(styled)
15624
16047
  }
15625
16048
  },
15626
16049
  inbox: {
15627
16050
  check: (request = {}) => checkWithState(request, frontier),
15628
16051
  commit: (target) => commitInbox(target),
15629
16052
  drain: (request = {}) => drainWithState(request, frontier),
15630
- list: (request) => listInbox(api, request)
16053
+ list: (request) => listInbox(api, request, style).then(styled)
15631
16054
  },
15632
16055
  messages: {
15633
16056
  read: (request) => readWithState(request, frontier),
@@ -15636,10 +16059,10 @@ function createRaft(options) {
15636
16059
  ...request,
15637
16060
  target: message.target
15638
16061
  }, frontier),
15639
- search: (request) => searchMessages(api, request),
15640
- resolve: (request) => resolveMessage(api, request),
15641
- react: (request) => reactToMessage(api, request, "add"),
15642
- unreact: (request) => reactToMessage(api, request, "remove")
16062
+ search: (request) => searchMessages(api, request, style).then(styled),
16063
+ resolve: (request) => resolveMessage(api, request, style).then(styled),
16064
+ react: (request) => reactToMessage(api, request, "add").then(styled),
16065
+ unreact: (request) => reactToMessage(api, request, "remove").then(styled)
15643
16066
  },
15644
16067
  attachments: {
15645
16068
  upload: (request) => uploadAttachment(api, {
@@ -15647,51 +16070,55 @@ function createRaft(options) {
15647
16070
  fetch: options.fetch ?? fetch,
15648
16071
  headers: Object.fromEntries(new Headers(options.headers)),
15649
16072
  authorization
15650
- }, request),
15651
- download: (request) => downloadAttachment(api, request),
15652
- comments: (request) => attachmentComments(api, request)
16073
+ }, request, style).then(styled),
16074
+ download: (request) => downloadAttachment(api, request).then(styled),
16075
+ downloadUrl: (request) => downloadAttachmentUrl(api, request, style).then(styled),
16076
+ comments: (request) => attachmentComments(api, request).then(styled)
15653
16077
  },
15654
16078
  mentions: {
15655
- pending: (request) => pendingMentionActions(api, request),
15656
- execute: (request) => executeMentionAction(api, request),
15657
- deliveries: (request) => senderMentionDeliveries(api, request)
15658
- },
15659
- actions: { prepare: (request) => prepareActionCard(api, request) },
16079
+ pending: (request) => pendingMentionActions(api, request, style).then(styled),
16080
+ notify: (request) => notifyMentions(api, request).then(styled),
16081
+ add: (request) => addMentions(api, request).then(styled),
16082
+ delivery: (request) => senderMentionDeliveries(api, request).then(styled),
16083
+ execute: (request) => executeMentionAction(api, request).then(styled),
16084
+ deliveries: (request) => senderMentionDeliveries(api, request).then(styled)
16085
+ },
16086
+ actions: { prepare: (request) => prepareActionCard(api, request, style).then(styled) },
15660
16087
  manual: {
15661
- get: (request) => getManualTopic(api, request),
15662
- search: (request) => searchManual(api, request)
16088
+ get: (request) => getManualTopic(api, request).then(styled),
16089
+ search: (request) => searchManual(api, request, style).then(styled)
15663
16090
  },
15664
16091
  tasks: {
15665
- claim: (request) => claimTasks(api, request),
15666
- list: (request) => listTasks(api, request),
15667
- create: (request) => createTasks(api, request),
15668
- unclaim: (request) => unclaimTask(api, request),
15669
- assign: (request) => assignTask(api, request),
15670
- unassign: (request) => unassignTask(api, request),
15671
- updateStatus: (request) => updateTaskStatus(api, request),
15672
- amend: (request) => amendTask(api, request),
15673
- history: (request) => taskHistory(api, request),
15674
- show: (request) => showTask(api, request),
15675
- convert: (request) => convertMessageToTask(api, request),
15676
- delete: (request) => deleteTask(api, request)
16092
+ claim: (request) => claimTasks(api, request, style).then(styled),
16093
+ list: (request) => listTasks(api, request, style).then(styled),
16094
+ create: (request) => createTasks(api, request, style).then(styled),
16095
+ unclaim: (request) => unclaimTask(api, request).then(styled),
16096
+ assign: (request) => assignTask(api, request).then(styled),
16097
+ unassign: (request) => unassignTask(api, request).then(styled),
16098
+ updateStatus: (request) => updateTaskStatus(api, request, style).then(styled),
16099
+ amend: (request) => amendTask(api, request, style).then(styled),
16100
+ history: (request) => taskHistory(api, request).then(styled),
16101
+ show: (request) => showTask(api, request, style).then(styled),
16102
+ convert: (request) => convertMessageToTask(api, request, style).then(styled),
16103
+ delete: (request) => deleteTask(api, request).then(styled)
15677
16104
  },
15678
16105
  channels: {
15679
- join: (request) => joinChannel(api, request),
15680
- leave: (request) => leaveChannel(api, request),
15681
- mute: (request) => muteChannel(api, request),
15682
- unmute: (request) => unmuteChannel(api, request),
15683
- members: (request) => channelMembers(api, request),
15684
- info: (request) => channelInfo(api, request)
16106
+ join: (request) => joinChannel(api, request, style).then(styled),
16107
+ leave: (request) => leaveChannel(api, request, style).then(styled),
16108
+ mute: (request) => muteChannel(api, request, style).then(styled),
16109
+ unmute: (request) => unmuteChannel(api, request, style).then(styled),
16110
+ members: (request) => channelMembers(api, request).then(styled),
16111
+ info: (request) => channelInfo(api, request, style).then(styled)
15685
16112
  },
15686
16113
  threads: {
15687
- list: () => listThreads(api),
15688
- unfollow: (request) => unfollowThread(api, request)
16114
+ list: () => listThreads(api).then(styled),
16115
+ unfollow: (request) => unfollowThread(api, request).then(styled)
15689
16116
  },
15690
- server: { info: (request) => serverInfo(api, request) },
15691
- users: { info: (request) => userInfo(api, request) },
16117
+ server: { info: (request) => serverInfo(api, request, style).then(styled) },
16118
+ users: { info: (request) => userInfo(api, request, style).then(styled) },
15692
16119
  profile: {
15693
- show: (request) => showProfile(api, request),
15694
- update: (request) => updateProfile(api, request)
16120
+ show: (request) => showProfile(api, request).then(styled),
16121
+ update: (request) => updateProfile(api, request).then(styled)
15695
16122
  },
15696
16123
  frontier,
15697
16124
  state: {
@@ -15712,7 +16139,7 @@ function createRaft(options) {
15712
16139
  const drain = drainWithState(args, call.seen);
15713
16140
  const batches = [];
15714
16141
  for (let step = await drain.next();; step = await drain.next()) {
15715
- if (step.done) return drainedInboxOutcome(batches, step.value);
16142
+ if (step.done) return styled(drainedInboxOutcome(batches, step.value, style));
15716
16143
  batches.push(step.value);
15717
16144
  }
15718
16145
  },
@@ -15740,8 +16167,12 @@ function createRaft(options) {
15740
16167
  "messages.resolve": (args) => raft.messages.resolve(args),
15741
16168
  "messages.react": (args) => raft.messages.react(args),
15742
16169
  "messages.unreact": (args) => raft.messages.unreact(args),
16170
+ "attachments.downloadUrl": (args) => raft.attachments.downloadUrl(args),
15743
16171
  "attachments.comments": (args) => raft.attachments.comments(args),
15744
16172
  "mentions.pending": (args) => raft.mentions.pending(args),
16173
+ "mentions.notify": (args) => raft.mentions.notify(args),
16174
+ "mentions.add": (args) => raft.mentions.add(args),
16175
+ "mentions.delivery": (args) => raft.mentions.delivery(args),
15745
16176
  "mentions.execute": (args) => raft.mentions.execute(args),
15746
16177
  "mentions.deliveries": (args) => raft.mentions.deliveries(args),
15747
16178
  "actions.prepare": (args) => raft.actions.prepare(args),
@@ -15812,4 +16243,4 @@ function whoamiOutcome(result) {
15812
16243
  };
15813
16244
  }
15814
16245
  //#endregion
15815
- export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_JSON_SCHEMA_KEYWORDS, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RAFT_OPERATIONS, RAFT_OPERATIONS_SCHEMA, RAFT_OPERATIONS_VERSION, RAFT_STATE_CONTINUATIONS_PER_TARGET, RAFT_STATE_CONTINUATIONS_TOTAL, RAFT_STATE_SCHEMA, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, amendTaskRequestSchema, assignTaskRequestSchema, attachmentCommentsRequestSchema, bootstrapRaftCredential, channelInfoRequestSchema, channelMembersRequestSchema, channelTargetRequestSchema, checkInboxRequestSchema, claimTasksRequestSchema, commitInboxRequestSchema, convertMessageToTaskRequestSchema, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, createTasksRequestSchema, describeRaftRoute, drainInboxRequestSchema, executeMentionActionRequestSchema, getManualTopicRequestSchema, hasAgentMessageIdentity, hashRaftSendContent, inferAttachmentMimeType, isInterrupted, joinRaftChannelByTarget, listInboxRequestSchema, listRaftRoutes, listTasksRequestSchema, listThreadsRequestSchema, parseRaftRegularChannelTarget, parseRaftState, pendingMentionActionsRequestSchema, prepareActionCardRequestSchema, projectRaftMessage, projectRaftMessages, raftToolNameFor, reactRequestSchema, readHistoryRequestSchema, readLatestReadThread, replyToRequestSchema, resolveMessageRequestSchema, searchManualRequestSchema, searchMessagesRequestSchema, sendMessageRequestSchema, senderMentionDeliveriesRequestSchema, serverInfoRequestSchema, showProfileRequestSchema, taskRefSchema, unfollowThreadRequestSchema, updateProfileRequestSchema, updateTaskStatusRequestSchema, userInfoRequestSchema, verifyInboxNotice, whoamiRequestSchema };
16246
+ export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_JSON_SCHEMA_KEYWORDS, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RAFT_OPERATIONS, RAFT_OPERATIONS_SCHEMA, RAFT_OPERATIONS_VERSION, RAFT_STATE_CONTINUATIONS_PER_TARGET, RAFT_STATE_CONTINUATIONS_TOTAL, RAFT_STATE_SCHEMA, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, amendTaskRequestSchema, assignTaskRequestSchema, attachmentCommentsRequestSchema, bootstrapRaftCredential, channelInfoRequestSchema, channelMembersRequestSchema, channelTargetRequestSchema, checkInboxRequestSchema, claimTasksRequestSchema, commitInboxRequestSchema, convertMessageToTaskRequestSchema, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, createTasksRequestSchema, describeRaftRoute, downloadAttachmentUrlRequestSchema, drainInboxRequestSchema, executeMentionActionRequestSchema, getManualTopicRequestSchema, hasAgentMessageIdentity, hashRaftSendContent, inferAttachmentMimeType, isInterrupted, joinRaftChannelByTarget, listInboxRequestSchema, listRaftRoutes, listTasksRequestSchema, listThreadsRequestSchema, mentionResolutionIdsRequestSchema, parseRaftRegularChannelTarget, parseRaftState, pendingMentionActionsRequestSchema, prepareActionCardRequestSchema, projectRaftMessage, projectRaftMessages, raftToolNameFor, reactRequestSchema, readHistoryRequestSchema, readLatestReadThread, replyToRequestSchema, resolveMessageRequestSchema, searchManualRequestSchema, searchMessagesRequestSchema, sendMessageRequestSchema, senderMentionDeliveriesRequestSchema, serverInfoRequestSchema, showProfileRequestSchema, taskRefSchema, unfollowThreadRequestSchema, updateProfileRequestSchema, updateTaskStatusRequestSchema, userInfoRequestSchema, verifyInboxNotice, whoamiRequestSchema };