@botiverse/raft-sdk 1.0.0-alpha.0 → 1.0.0-alpha.3

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.
@@ -10975,7 +10975,22 @@ function toAgentMessageLike(envelope) {
10975
10975
  task_current_projection: envelope.task_current_projection ?? envelope.taskCurrentProjection ?? null
10976
10976
  };
10977
10977
  }
10978
+ /**
10979
+ * An envelope carries a reply target only when it names its conversation
10980
+ * (`channel_type` + `channel_name`, or a third-party event). Some Server
10981
+ * responses embed bare envelopes (for example `recentUnread: [{ content }]`);
10982
+ * those must not be rendered with a made-up target.
10983
+ */
10984
+ function hasAgentMessageIdentity(envelope) {
10985
+ const like = toAgentMessageLike(envelope);
10986
+ if (like.third_party_event) return true;
10987
+ if (typeof like.channel_type !== "string" || typeof like.channel_name !== "string" || !like.channel_name) return false;
10988
+ if (like.channel_type === "thread" && !like.parent_channel_name) return false;
10989
+ return true;
10990
+ }
10991
+ /** Project one envelope; `null` when it has no conversation identity (skip it rather than render `#undefined`). */
10978
10992
  function projectRaftMessage(envelope) {
10993
+ if (!hasAgentMessageIdentity(envelope)) return null;
10979
10994
  const like = toAgentMessageLike(envelope);
10980
10995
  const id = nullableString(like.message_id);
10981
10996
  const taskNumber = nullableNumber(like.task_number);
@@ -11014,6 +11029,10 @@ function projectRaftMessage(envelope) {
11014
11029
  function sortBySeq(messages) {
11015
11030
  return [...messages].sort((a, b) => (a.seq ?? Number.MAX_SAFE_INTEGER) - (b.seq ?? Number.MAX_SAFE_INTEGER));
11016
11031
  }
11032
+ /** Project a list, skipping envelopes without a conversation identity. */
11033
+ function projectRaftMessages(envelopes) {
11034
+ return envelopes.map(projectRaftMessage).filter((m) => m !== null);
11035
+ }
11017
11036
  //#endregion
11018
11037
  //#region ../shared/src/agentOps/frontier.ts
11019
11038
  const MAX_EXACT_SEQS = 2500;
@@ -11061,6 +11080,17 @@ var SeenFrontier = class SeenFrontier {
11061
11080
  for (const seq of keep) state.exact.add(seq);
11062
11081
  }
11063
11082
  }
11083
+ /**
11084
+ * Attest that the model saw the context a held send/claim returned. Call it
11085
+ * only after the held messages actually reached the model (the SDK cannot
11086
+ * know that, so it never records this implicitly). Returns false and records
11087
+ * nothing when the context was withheld or the Server sent no boundary.
11088
+ */
11089
+ recordHeld(held) {
11090
+ if (held.withheld || held.seenUpToSeq === null) return false;
11091
+ this.recordUpTo(held.target, held.seenUpToSeq);
11092
+ return true;
11093
+ }
11064
11094
  /** What to attest on a send or claim to `target`; omits `seenUpToSeq` when nothing contiguous is known. */
11065
11095
  attestation(target) {
11066
11096
  const state = this.targets.get(this.canonical(target));
@@ -11139,7 +11169,7 @@ async function checkInbox(client, request = {}, frontier) {
11139
11169
  });
11140
11170
  if (!result.ok) return failureFromClientResult(result);
11141
11171
  const data = result.data;
11142
- const messages = sortBySeq(data.events.map(projectRaftMessage));
11172
+ const messages = sortBySeq(projectRaftMessages(data.events));
11143
11173
  recordExactSeen(frontier, messages);
11144
11174
  const batch = {
11145
11175
  messages,
@@ -11361,7 +11391,7 @@ async function readHistory(client, request, frontier) {
11361
11391
  const data = result.data;
11362
11392
  const page = {
11363
11393
  target: typeof data.target === "string" && data.target ? data.target : request.target,
11364
- messages: sortBySeq(data.messages.map(projectRaftMessage)),
11394
+ messages: sortBySeq(projectRaftMessages(data.messages)),
11365
11395
  hasOlder: data.has_older === true,
11366
11396
  hasNewer: data.has_newer === true,
11367
11397
  lastReadSeq: typeof data.last_read_seq === "number" ? data.last_read_seq : null,
@@ -11391,25 +11421,31 @@ function heldText(held, action) {
11391
11421
  }
11392
11422
  function heldNext(held) {
11393
11423
  return {
11394
- kind: "review_then_resend",
11424
+ kind: "resend",
11395
11425
  command: `raft message read --target "${held.target}"`,
11396
11426
  args: {
11397
11427
  target: held.target,
11398
- newMessageCount: held.newMessageCount
11428
+ ...held.continuation
11399
11429
  },
11400
- why: "Newer messages arrived in this conversation; read them, then resend if the message still applies."
11430
+ why: held.withheld ? "Newer messages exist in this conversation but were withheld; read them, then send again with the same idempotencyKey." : "Newer messages arrived in this conversation. Show the held messages to the model, call frontier.recordHeld(held) to attest that, then send again with these args (same idempotencyKey, seen.upToSeq)."
11401
11431
  };
11402
11432
  }
11403
- function projectHeld(target, data) {
11433
+ function projectHeld(target, data, idempotencyKey) {
11404
11434
  const withheld = data.freshnessContextMode === "withheld";
11405
- const heldMessages = (data.heldMessages ?? []).map(projectRaftMessage);
11435
+ const heldMessages = projectRaftMessages(data.heldMessages ?? []);
11436
+ const seenUpToSeq = typeof data.seenUpToSeq === "number" ? data.seenUpToSeq : null;
11406
11437
  return {
11407
11438
  target,
11439
+ idempotencyKey,
11440
+ continuation: {
11441
+ idempotencyKey,
11442
+ ...!withheld && seenUpToSeq !== null ? { seen: { upToSeq: seenUpToSeq } } : {}
11443
+ },
11408
11444
  newMessageCount: withheld ? data.withheldMessageCount ?? 0 : data.newMessageCount ?? heldMessages.length,
11409
11445
  heldMessages,
11410
11446
  omittedMessageCount: data.omittedMessageCount ?? 0,
11411
11447
  formalMentionCount: data.mentionAnnotation?.formalMentionCount ?? 0,
11412
- seenUpToSeq: typeof data.seenUpToSeq === "number" ? data.seenUpToSeq : null,
11448
+ seenUpToSeq,
11413
11449
  withheld
11414
11450
  };
11415
11451
  }
@@ -11421,7 +11457,7 @@ function sentOutcome(target, data) {
11421
11457
  messageId: data.messageId,
11422
11458
  messageSeq: typeof data.messageSeq === "number" ? data.messageSeq : null,
11423
11459
  unresolvedMentionHandles: data.unresolvedMentionHandles ?? [],
11424
- recentUnread: (data.recentUnread ?? []).map(projectRaftMessage)
11460
+ recentUnread: projectRaftMessages(data.recentUnread ?? [])
11425
11461
  };
11426
11462
  const warn = sent.unresolvedMentionHandles.length > 0 ? ` Unresolved @handles: ${sent.unresolvedMentionHandles.map((h) => `@${h}`).join(", ")}.` : "";
11427
11463
  return {
@@ -11468,7 +11504,7 @@ async function sendMessage(client, request, frontier, internal = {}) {
11468
11504
  idempotencyKey
11469
11505
  };
11470
11506
  const held = {
11471
- ...projectHeld(request.target, data),
11507
+ ...projectHeld(request.target, data, idempotencyKey),
11472
11508
  resend: (options) => sendMessage(client, options.seen === "held" && typeof data.seenUpToSeq === "number" ? {
11473
11509
  ...keyed,
11474
11510
  seen: {
@@ -11499,6 +11535,174 @@ function replyTo(client, message, request, frontier) {
11499
11535
  target: message.target
11500
11536
  }, frontier);
11501
11537
  }
11538
+ function taskTimestamps(t) {
11539
+ return `${t.createdAt ? ` created=${formatUtcTimestamp(t.createdAt)}` : ""}${t.updatedAt ? ` updated=${formatUtcTimestamp(t.updatedAt)}` : ""}`;
11540
+ }
11541
+ const TASK_STATUS_ORDER = [
11542
+ "todo",
11543
+ "in_progress",
11544
+ "in_review",
11545
+ "done",
11546
+ "closed"
11547
+ ];
11548
+ function oneLineTaskTitle(title) {
11549
+ return (title ?? "").replace(/\s+/g, " ").trim();
11550
+ }
11551
+ /** Channel task board listing. */
11552
+ function formatAgentTaskList(channel, data, statusFilter) {
11553
+ if (!data.tasks || data.tasks.length === 0) return `No${statusFilter && statusFilter !== "all" ? ` ${statusFilter}` : ""} tasks in ${channel}.`;
11554
+ const formatted = data.tasks.map((t) => {
11555
+ const assignee = t.claimedById ? ` → ${t.claimedByName ? `@${t.claimedByName}` : "<unresolved>"}` : "";
11556
+ const departedCreator = t.createdByMembershipStatus === "left" || t.createdByMembershipStatus === "removed";
11557
+ const creator = t.createdByName ? ` (by @${t.createdByName}${departedCreator ? " [departed]" : ""})` : "";
11558
+ const msgId = t.messageId ? ` msg=${t.messageId.slice(0, 8)}` : "";
11559
+ const revision = Number.isInteger(t.revision) ? ` rev=${t.revision}` : "";
11560
+ const legacy = t.isLegacy ? " [LEGACY — read-only]" : "";
11561
+ const resourceReceipt = t.requiresResourceReceipt ? ` resource-receipt=${t.resourceReceiptRecordedAt ? "recorded" : "pending"}` : "";
11562
+ const details = t.description ? `\n Current description: ${t.description.replace(/\n/g, `\n${" ".repeat(23)}`)}` : "";
11563
+ return `#${t.taskNumber} [${t.status}]${assignee}${creator}${msgId}${revision}${resourceReceipt}${taskTimestamps(t)}${legacy} Current title: ${oneLineTaskTitle(t.title)}${details}`;
11564
+ }).join("\n");
11565
+ return `## Task Board for ${channel} (${data.tasks.length} tasks)\n\n${formatted}`;
11566
+ }
11567
+ /** Cross-channel --mine task listing with coverage notes. */
11568
+ function formatAgentMyTaskList(data, statusFilter) {
11569
+ const tasks = data.tasks ?? [];
11570
+ const visibleTypes = data.coverage?.visibleChannelTypes?.join("|") ?? "unknown";
11571
+ const archived = data.coverage?.includesArchived ? "included" : "not included";
11572
+ const coverage = [
11573
+ `Coverage: ${data.coverage?.status ?? "unknown"}`,
11574
+ `visible types=${visibleTypes}`,
11575
+ `archived=${archived}`,
11576
+ `inaccessible scope=${data.coverage?.inaccessibleScope ?? "unknown"}`
11577
+ ].join(" · ");
11578
+ const output = `Output: showing ${tasks.length} of ${tasks.length} visible matches · mode=${data.pagination?.mode ?? "unknown"} · truncated=${String(data.pagination?.truncated ?? "unknown")}`;
11579
+ const title = `## My assigned tasks on this server${statusFilter ? ` (status=${statusFilter})` : " (unfinished)"}`;
11580
+ if (tasks.length === 0) return [
11581
+ title,
11582
+ "",
11583
+ coverage,
11584
+ output,
11585
+ "",
11586
+ "No tasks matched in the covered visible scope."
11587
+ ].join("\n");
11588
+ const grouped = /* @__PURE__ */ new Map();
11589
+ for (const task of tasks) {
11590
+ const status = task.status ?? "unknown";
11591
+ const bucket = grouped.get(status) ?? [];
11592
+ bucket.push(task);
11593
+ grouped.set(status, bucket);
11594
+ }
11595
+ return [
11596
+ title,
11597
+ "",
11598
+ coverage,
11599
+ output,
11600
+ "",
11601
+ ...[...TASK_STATUS_ORDER.filter((status) => grouped.has(status)), ...[...grouped.keys()].filter((status) => !TASK_STATUS_ORDER.includes(status)).sort()].map((status) => {
11602
+ const rows = grouped.get(status);
11603
+ const rendered = rows.map((task) => {
11604
+ const channelRef = task.channelRef ?? "<unresolved-visible-target>";
11605
+ const taskNumber = task.taskNumber ?? "?";
11606
+ const departedCreator = task.createdByMembershipStatus === "left" || task.createdByMembershipStatus === "removed";
11607
+ const creator = task.createdByName ? ` by=@${task.createdByName}${departedCreator ? " creator=departed" : ""}` : "";
11608
+ const message = task.messageId ? ` msg=${task.messageId.slice(0, 8)}` : "";
11609
+ const legacy = task.isLegacy ? " legacy=read-only" : "";
11610
+ return `- ${channelRef} task #${taskNumber} [${status}]${creator}${message}${task.requiresResourceReceipt ? ` resource-receipt=${task.resourceReceiptRecordedAt ? "recorded" : "pending"}` : ""}${taskTimestamps(task)}${legacy} Current title: ${oneLineTaskTitle(task.title)}`.trimEnd();
11611
+ });
11612
+ return [`### ${status} (${rows.length})`, ...rendered].join("\n");
11613
+ })
11614
+ ].join("\n");
11615
+ }
11616
+ /** Receipt for task create. */
11617
+ function formatAgentTasksCreated(channel, data) {
11618
+ const created = data.tasks.map((t) => {
11619
+ const assignee = t.claimedById ? t.claimedByName ? `@${t.claimedByName}` : "<unresolved>" : "unassigned";
11620
+ const resourceReceipt = t.requiresResourceReceipt ? " resource-receipt=pending" : "";
11621
+ return `#${t.taskNumber} [${t.status}] assignee=${assignee} claimedAt=${t.claimedAt ?? "null"} msg=${t.messageId.slice(0, 8)}${resourceReceipt} "${t.title}"`;
11622
+ }).join("\n");
11623
+ const threadHints = data.tasks.map((t) => `#${t.taskNumber} → raft message send --target "${channel}:${t.messageId.slice(0, 8)}"`).join("\n");
11624
+ const receipt = data.assignmentReceipt ? `\n\nAssignment receipt (msg=${data.assignmentReceipt.messageId.slice(0, 8)}):\n${data.assignmentReceipt.content}` : "";
11625
+ return `Created ${data.tasks.length} task(s) in ${channel}:\n${created}${receipt}\n\nTo follow up in each task's thread:\n${threadHints}`;
11626
+ }
11627
+ const BLOCKED_ACTION_COPY = { start_conflicting_execution: "starting conflicting implementation/change work" };
11628
+ function formatClaimConflict(label, conflict) {
11629
+ const holder = conflict.currentAssignee?.name ? `@${conflict.currentAssignee.name}` : "another actor";
11630
+ const blocked = conflict.blockedActions.map((action) => BLOCKED_ACTION_COPY[action] ?? action).join("; ");
11631
+ const examples = conflict.unblockedActionExamples.join(" · ");
11632
+ return [
11633
+ `${label}: Claim failed — ${holder} currently holds the implementation lock (assignment state as of ${conflict.observedAt}).`,
11634
+ ` Blocked: ${blocked}.`,
11635
+ ` Not blocked by this claim conflict (each still subject to its own authority/policy): ${examples}.`,
11636
+ ` This is not a ruling on who owns or leads this lane. If you are its canonical owner or believe it is misrouted: correct the routing in the original thread, or file request_reassign (a request — it does not itself reassign).`
11637
+ ].join("\n");
11638
+ }
11639
+ function canonicalAgentTaskTarget(target) {
11640
+ return target.replace(/^dm:user:/i, "dm:@");
11641
+ }
11642
+ function agentTaskThreadTarget(target, messageId) {
11643
+ return `${canonicalAgentTaskTarget(target)}:${messageId.slice(0, 8)}`;
11644
+ }
11645
+ /** Claim results incl. concurrency-lock guidance on failed claims. */
11646
+ function formatAgentClaimResults(channel, data) {
11647
+ const lines = data.results.map((r) => {
11648
+ const label = r.taskNumber ? `#${r.taskNumber}` : `msg:${r.messageId}`;
11649
+ if (r.success) return `${label} (msg:${r.messageId ? r.messageId.slice(0, 8) : ""}): claimed`;
11650
+ if (r.reason === "already claimed by you") return `${label}: already claimed by you.`;
11651
+ if (r.conflict?.kind === "claim_conflict") return formatClaimConflict(label, r.conflict);
11652
+ return `${label}: FAILED — ${r.reason || "already claimed"}. Do not start conflicting execution on this task or take over its scope without a redirect; a failed claim is a concurrency lock, not a ruling on lane ownership.`;
11653
+ });
11654
+ const succeeded = data.results.filter((r) => r.success).length;
11655
+ const failed = data.results.length - succeeded;
11656
+ let summary = `${succeeded} claimed`;
11657
+ if (failed > 0) summary += `, ${failed} failed`;
11658
+ const claimedMsgs = data.results.filter((r) => r.success && r.messageId).map((r) => `#${r.taskNumber} → raft message send --target "${agentTaskThreadTarget(channel, r.messageId)}"`).join("\n");
11659
+ const threadHint = claimedMsgs ? `\n\nFollow up in each task's thread:\n${claimedMsgs}` : "";
11660
+ return `Claim results (${summary}):\n${lines.join("\n")}${threadHint}`;
11661
+ }
11662
+ function formatAgentTaskUnclaimed(taskNumber) {
11663
+ return `#${taskNumber} unclaimed — now open.`;
11664
+ }
11665
+ function formatAgentTaskAssigned(taskNumber, assignee) {
11666
+ return assignee ? `#${taskNumber} assigned to ${assignee}.` : `#${taskNumber} unassigned — now open.`;
11667
+ }
11668
+ function formatAgentTaskStatusUpdated(taskNumber, status) {
11669
+ return `#${taskNumber} moved to ${status}.`;
11670
+ }
11671
+ function formatAgentTaskDeleted(taskNumber) {
11672
+ return `#${taskNumber} deleted.`;
11673
+ }
11674
+ /** Message→task conversion receipt. */
11675
+ function formatAgentTaskConverted(channel, task) {
11676
+ const target = `${canonicalAgentTaskTarget(channel)}:${task.messageId.slice(0, 8)}`;
11677
+ return [
11678
+ `Converted msg=${task.messageId.slice(0, 8)} to task #${task.taskNumber} [${task.status}] assignee=unassigned "${task.title}"`,
11679
+ "",
11680
+ `To follow up in the task's thread:`,
11681
+ `raft message send --target "${target}"`
11682
+ ].join("\n");
11683
+ }
11684
+ function formatAgentTaskAmended(data) {
11685
+ const details = data.task.description === null ? "details: <none>" : `details:\n${data.task.description.split("\n").map((line) => ` ${line}`).join("\n")}`;
11686
+ return [
11687
+ `#${data.task.taskNumber} amended — revision ${data.task.revision}, event seq ${data.event.seq}.`,
11688
+ `title: ${data.task.title}`,
11689
+ details
11690
+ ].join("\n");
11691
+ }
11692
+ function formatAgentTaskHistory(data) {
11693
+ const description = data.task.description === null ? "(none set)" : data.task.description.replace(/\n/g, `\n${" ".repeat(21)}`);
11694
+ const header = [
11695
+ `## Task #${data.task.taskNumber} history — revision ${data.task.revision}`,
11696
+ "",
11697
+ `Current title: ${data.task.title.replace(/\s+/g, " ").trim()}`,
11698
+ `Current description: ${description}`
11699
+ ].join("\n");
11700
+ if (data.events.length === 0) return `${header}\n\nNo recorded events.`;
11701
+ return `${header}\n\n${data.events.map((event) => {
11702
+ const actor = event.actorType === "system" ? "@system" : event.actorName ? `@${event.actorName}` : "<unresolved>";
11703
+ return `seq=${event.seq} time=${event.createdAt} actor=${actor} type=${event.eventType}\n ${JSON.stringify(event.payload)}`;
11704
+ }).join("\n")}`;
11705
+ }
11502
11706
  //#endregion
11503
11707
  //#region ../shared/src/agentOps/tasks.ts
11504
11708
  const TASK_CLAIM_REASON_ALREADY_CLAIMED_BY_YOU = "already claimed by you";
@@ -11511,12 +11715,12 @@ function rowState(result) {
11511
11715
  function claimNext(claim) {
11512
11716
  const first = claim.rows.find((row) => row.mayWork);
11513
11717
  if (first) {
11514
- const suffix = first.messageId ? first.messageId.slice(0, 8) : null;
11718
+ const thread = first.messageId ? agentTaskThreadTarget(claim.target, first.messageId) : null;
11515
11719
  return {
11516
11720
  kind: "start_work",
11517
- ...suffix ? {
11518
- command: `raft message send --target "${claim.target}:${suffix}"`,
11519
- args: { thread: `${claim.target}:${suffix}` }
11721
+ ...thread ? {
11722
+ command: `raft message send --target "${thread}"`,
11723
+ args: { thread }
11520
11724
  } : {},
11521
11725
  why: "The claim is yours; post progress in the task's thread."
11522
11726
  };
@@ -11527,20 +11731,7 @@ function claimNext(claim) {
11527
11731
  };
11528
11732
  }
11529
11733
  function claimText(claim) {
11530
- const lines = claim.rows.map((row) => {
11531
- const label = row.taskNumber ? `#${row.taskNumber}` : `msg:${row.ref}`;
11532
- if (row.state === "claimed") return `${label}: claimed`;
11533
- if (row.state === "already_yours") return `${label}: already claimed by you`;
11534
- if (row.state === "conflict") return `${label}: held by ${row.holder?.name ? `@${row.holder.name}` : "another assignee"}`;
11535
- return `${label}: refused${row.reason ? ` (${row.reason})` : ""}`;
11536
- });
11537
- const head = `Claim results (${claim.rows.filter((row) => row.mayWork).length} of ${claim.rows.length} authorise work):`;
11538
- const next = claimNext(claim);
11539
- return [
11540
- head,
11541
- ...lines,
11542
- next?.command ? `Follow up in the task's thread: ${next.command}` : next?.why ?? ""
11543
- ].filter(Boolean).join("\n");
11734
+ return formatAgentClaimResults(claim.target, { results: claim.rows.map((row) => row.raw) });
11544
11735
  }
11545
11736
  async function claimTasks(client, request) {
11546
11737
  const numbers = (request.taskNumbers ?? []).filter((n) => Number.isInteger(n) && n > 0);
@@ -11555,8 +11746,10 @@ async function claimTasks(client, request) {
11555
11746
  if (!result.ok) return failureFromClientResult(result);
11556
11747
  const data = result.data;
11557
11748
  if (isHeldResponse(data)) {
11749
+ const { idempotencyKey: _unused, continuation: _c, ...heldBase } = projectHeld(request.target, data, "");
11558
11750
  const held = {
11559
- ...projectHeld(request.target, data),
11751
+ ...heldBase,
11752
+ request,
11560
11753
  retry: () => claimTasks(client, request)
11561
11754
  };
11562
11755
  return {
@@ -11564,13 +11757,10 @@ async function claimTasks(client, request) {
11564
11757
  state: "held",
11565
11758
  data: held,
11566
11759
  next: {
11567
- kind: "review_then_retry",
11760
+ kind: "retry_claim",
11568
11761
  command: `raft message read --target "${request.target}"`,
11569
- args: {
11570
- target: request.target,
11571
- newMessageCount: held.newMessageCount
11572
- },
11573
- why: "Unread messages in this channel may change the task; read them, then claim again if it is still right."
11762
+ args: { ...request },
11763
+ why: "Unread messages in this channel may change the task; read them (frontier.recordHeld(held) once the model saw them), then claim again with these args if it is still right."
11574
11764
  },
11575
11765
  text: `Held — ${held.newMessageCount} unread ${held.newMessageCount === 1 ? "message" : "messages"} in ${request.target}. Your task claim was not applied.\nRead them with: raft message read --target "${request.target}"\nAfter reviewing, rerun the claim if it is still correct.`
11576
11766
  };
@@ -11585,7 +11775,8 @@ async function claimTasks(client, request) {
11585
11775
  mayWork: state === "claimed" || state === "already_yours",
11586
11776
  reason: row.reason ?? null,
11587
11777
  holder: row.conflict?.currentAssignee ?? null,
11588
- conflict: row.conflict ?? null
11778
+ conflict: row.conflict ?? null,
11779
+ raw: row
11589
11780
  };
11590
11781
  });
11591
11782
  const claim = {
@@ -11601,6 +11792,235 @@ async function claimTasks(client, request) {
11601
11792
  text: claimText(claim)
11602
11793
  };
11603
11794
  }
11795
+ async function listTasks(client, request) {
11796
+ const mine = request.mine === true;
11797
+ if (mine === Boolean(request.target?.trim())) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass exactly one of a channel target or mine: true." }));
11798
+ const result = await client.tasks.list({
11799
+ ...mine ? { mine: "true" } : { channel: request.target },
11800
+ ...request.status ? { status: request.status } : {}
11801
+ });
11802
+ if (!result.ok) return failureFromClientResult(result);
11803
+ const data = result.data;
11804
+ const board = {
11805
+ scope: mine ? "mine" : "channel",
11806
+ target: mine ? null : request.target,
11807
+ tasks: data.tasks,
11808
+ coverage: data.coverage ?? null
11809
+ };
11810
+ const text = mine ? formatAgentMyTaskList(data, request.status) : formatAgentTaskList(request.target, data, request.status);
11811
+ const open = board.tasks.find((t) => t.status === "todo" && !t.claimedByName);
11812
+ const next = open?.taskNumber && board.target ? {
11813
+ kind: "claim_task",
11814
+ command: `raft task claim --target "${board.target}" --number ${open.taskNumber}`,
11815
+ args: {
11816
+ target: board.target,
11817
+ taskNumbers: [open.taskNumber]
11818
+ },
11819
+ why: "An unassigned todo task is open; claim it before working on it."
11820
+ } : null;
11821
+ return {
11822
+ ok: true,
11823
+ state: board.tasks.length > 0 ? "board" : "empty",
11824
+ data: board,
11825
+ next,
11826
+ text
11827
+ };
11828
+ }
11829
+ async function createTasks(client, request) {
11830
+ if (!request.target?.trim() || !request.tasks?.length) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target and at least one task title are required." }));
11831
+ const result = await client.tasks.create({
11832
+ channel: request.target,
11833
+ tasks: request.tasks.map((t) => ({
11834
+ title: t.title,
11835
+ ...t.createsResource ? { creates_resource: true } : {}
11836
+ })),
11837
+ ...request.assignee ? { assignee: request.assignee } : {}
11838
+ });
11839
+ if (!result.ok) return failureFromClientResult(result);
11840
+ const data = result.data;
11841
+ const first = data.tasks[0];
11842
+ return {
11843
+ ok: true,
11844
+ state: "created",
11845
+ data: {
11846
+ ...data,
11847
+ target: request.target
11848
+ },
11849
+ next: first ? {
11850
+ kind: "post_in_task_thread",
11851
+ command: `raft message send --target "${agentTaskThreadTarget(request.target, first.messageId)}"`,
11852
+ args: { thread: agentTaskThreadTarget(request.target, first.messageId) },
11853
+ why: "Follow up in each task's thread."
11854
+ } : null,
11855
+ text: formatAgentTasksCreated(request.target, data)
11856
+ };
11857
+ }
11858
+ function requireTaskRef(request) {
11859
+ if (!request.target?.trim() || !Number.isInteger(request.taskNumber) || request.taskNumber <= 0) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target and a positive task number are required." }));
11860
+ return null;
11861
+ }
11862
+ async function unclaimTask(client, request) {
11863
+ const invalid = requireTaskRef(request);
11864
+ if (invalid) return invalid;
11865
+ const result = await client.tasks.unclaim({
11866
+ channel: request.target,
11867
+ task_number: request.taskNumber
11868
+ });
11869
+ if (!result.ok) return failureFromClientResult(result);
11870
+ return {
11871
+ ok: true,
11872
+ state: "unclaimed",
11873
+ data: request,
11874
+ next: null,
11875
+ text: formatAgentTaskUnclaimed(request.taskNumber)
11876
+ };
11877
+ }
11878
+ async function assignTask(client, request) {
11879
+ const invalid = requireTaskRef(request);
11880
+ if (invalid) return invalid;
11881
+ const result = await client.tasks.assign({
11882
+ channel: request.target,
11883
+ task_number: request.taskNumber,
11884
+ assignee: request.assignee,
11885
+ ...request.expectedRevision === void 0 ? {} : { expected_revision: request.expectedRevision }
11886
+ });
11887
+ if (!result.ok) return failureFromClientResult(result);
11888
+ const assignee = result.data.assignee;
11889
+ return {
11890
+ ok: true,
11891
+ state: assignee ? "assigned" : "unassigned",
11892
+ data: {
11893
+ target: request.target,
11894
+ taskNumber: request.taskNumber,
11895
+ assignee,
11896
+ revision: result.data.revision
11897
+ },
11898
+ next: null,
11899
+ text: formatAgentTaskAssigned(request.taskNumber, assignee)
11900
+ };
11901
+ }
11902
+ function heldTaskWrite(request, data, action, kind) {
11903
+ const { idempotencyKey: _k, continuation: _c, ...base } = projectHeld(request.target, data, "");
11904
+ const held = {
11905
+ ...base,
11906
+ request
11907
+ };
11908
+ return {
11909
+ ok: true,
11910
+ state: "held",
11911
+ data: held,
11912
+ next: {
11913
+ kind,
11914
+ command: `raft message read --target "${request.target}"`,
11915
+ args: { ...request },
11916
+ why: `Unread messages in this channel may change the task; read them (frontier.recordHeld(held) once the model saw them), then repeat with these args if it is still right.`
11917
+ },
11918
+ text: `Held — ${held.newMessageCount} unread ${held.newMessageCount === 1 ? "message" : "messages"} in ${request.target}. ${action}\nRead them with: raft message read --target "${request.target}"\nAfter reviewing, repeat the operation if it is still correct.`
11919
+ };
11920
+ }
11921
+ async function updateTaskStatus(client, request) {
11922
+ const invalid = requireTaskRef(request);
11923
+ if (invalid) return invalid;
11924
+ const result = await client.tasks.updateStatus({
11925
+ channel: request.target,
11926
+ task_number: request.taskNumber,
11927
+ status: request.status
11928
+ });
11929
+ if (!result.ok) return failureFromClientResult(result);
11930
+ if (isHeldResponse(result.data)) return heldTaskWrite(request, result.data, "The status change was not applied.", "retry_update_status");
11931
+ return {
11932
+ ok: true,
11933
+ state: "updated",
11934
+ data: request,
11935
+ next: request.status === "in_review" ? {
11936
+ kind: "await_review",
11937
+ why: "A human validates the work; set done after approval."
11938
+ } : null,
11939
+ text: formatAgentTaskStatusUpdated(request.taskNumber, request.status)
11940
+ };
11941
+ }
11942
+ async function amendTask(client, request) {
11943
+ const invalid = requireTaskRef(request);
11944
+ if (invalid) return invalid;
11945
+ 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." }));
11946
+ const result = await client.tasks.amend({
11947
+ channel: request.target,
11948
+ task_number: request.taskNumber,
11949
+ ...request.title === void 0 ? {} : { title: request.title },
11950
+ ...request.description === void 0 ? {} : { description: request.description }
11951
+ });
11952
+ if (!result.ok) return failureFromClientResult(result);
11953
+ if (isHeldResponse(result.data)) return heldTaskWrite(request, result.data, "The amendment was not applied.", "retry_amend");
11954
+ return {
11955
+ ok: true,
11956
+ state: "amended",
11957
+ data: {
11958
+ ...result.data,
11959
+ target: request.target
11960
+ },
11961
+ next: null,
11962
+ text: formatAgentTaskAmended(result.data)
11963
+ };
11964
+ }
11965
+ async function taskHistory(client, request) {
11966
+ const invalid = requireTaskRef(request);
11967
+ if (invalid) return invalid;
11968
+ const result = await client.tasks.history({
11969
+ channel: request.target,
11970
+ task_number: request.taskNumber
11971
+ });
11972
+ if (!result.ok) return failureFromClientResult(result);
11973
+ return {
11974
+ ok: true,
11975
+ state: "history",
11976
+ data: {
11977
+ ...result.data,
11978
+ target: request.target
11979
+ },
11980
+ next: null,
11981
+ text: formatAgentTaskHistory(result.data)
11982
+ };
11983
+ }
11984
+ async function convertMessageToTask(client, request) {
11985
+ if (!request.target?.trim() || !request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target and a message id are required." }));
11986
+ const result = await client.tasks.convert({
11987
+ channel: request.target,
11988
+ message_id: request.messageId
11989
+ });
11990
+ if (!result.ok) return failureFromClientResult(result);
11991
+ const task = result.data.task;
11992
+ return {
11993
+ ok: true,
11994
+ state: "converted",
11995
+ data: {
11996
+ target: request.target,
11997
+ task
11998
+ },
11999
+ next: {
12000
+ kind: "post_in_task_thread",
12001
+ command: `raft message send --target "${agentTaskThreadTarget(request.target, task.messageId)}"`,
12002
+ args: { thread: agentTaskThreadTarget(request.target, task.messageId) },
12003
+ why: "Follow up in the task's thread; it is unassigned until someone claims it."
12004
+ },
12005
+ text: formatAgentTaskConverted(request.target, task)
12006
+ };
12007
+ }
12008
+ async function deleteTask(client, request) {
12009
+ const invalid = requireTaskRef(request);
12010
+ if (invalid) return invalid;
12011
+ const result = await client.tasks.delete({
12012
+ channel: request.target,
12013
+ task_number: request.taskNumber
12014
+ });
12015
+ if (!result.ok) return failureFromClientResult(result);
12016
+ return {
12017
+ ok: true,
12018
+ state: "deleted",
12019
+ data: request,
12020
+ next: null,
12021
+ text: formatAgentTaskDeleted(request.taskNumber)
12022
+ };
12023
+ }
11604
12024
  //#endregion
11605
12025
  //#region ../shared/src/agentOps/wake.ts
11606
12026
  const RAFT_INBOX_NOTICE_SCHEMA = "raft-agent-inbox-notice.v1";
@@ -11768,6 +12188,1432 @@ async function unregisterWebhook(client) {
11768
12188
  };
11769
12189
  }
11770
12190
  //#endregion
12191
+ //#region ../shared/src/agentText/server.ts
12192
+ function formatRuntimeContext(ctx) {
12193
+ if (!ctx) return "";
12194
+ const lines = ["### Current Runtime", "Authoritative context for this agent process. Do not infer computer identity from hostname or cwd when this section is present."];
12195
+ if (ctx.agentId) lines.push(`- Agent ID: ${ctx.agentId}`);
12196
+ if (ctx.runtime) lines.push(`- Runtime: ${ctx.runtime}`);
12197
+ if (ctx.model) lines.push(`- Model: ${ctx.model}`);
12198
+ if (ctx.reasoningEffort) lines.push(`- Reasoning: ${ctx.reasoningEffort}`);
12199
+ if (ctx.serverId) lines.push(`- Server ID: ${ctx.serverId}`);
12200
+ if (ctx.machineName || ctx.machineId) {
12201
+ const label = ctx.machineName && ctx.machineId ? `${ctx.machineName} (${ctx.machineId})` : ctx.machineName || ctx.machineId;
12202
+ lines.push(`- Computer: ${label}`);
12203
+ }
12204
+ if (ctx.machineDescription) lines.push(`- Computer Description: ${ctx.machineDescription}`);
12205
+ if (ctx.machineHostname) lines.push(`- Hostname: ${ctx.machineHostname}`);
12206
+ if (ctx.machineOs) lines.push(`- OS: ${ctx.machineOs}`);
12207
+ if (ctx.daemonVersion) lines.push(`- Daemon: v${ctx.daemonVersion}`);
12208
+ if (ctx.workspacePath) lines.push(`- Workspace: ${ctx.workspacePath}`);
12209
+ return lines.length > 2 ? `${lines.join("\n")}\n\n` : "";
12210
+ }
12211
+ function roleLabel(role) {
12212
+ return role && role !== "member" ? ` (${role})` : "";
12213
+ }
12214
+ function channelMemberRoleDetail(member) {
12215
+ const details = [];
12216
+ if (member.serverRole) details.push(`server role=${member.serverRole}`);
12217
+ if (member.channelRole) details.push(`channel role=${member.channelRole}`);
12218
+ if (member.channelAdminBasis) details.push(`admin via=${member.channelAdminBasis}`);
12219
+ return details.length > 0 ? ` [${details.join(", ")}]` : "";
12220
+ }
12221
+ function agentStatusLabel(agent) {
12222
+ const lifecycle = agent.status?.trim() || "unknown";
12223
+ const activity = agent.activity?.trim();
12224
+ if (!activity) return lifecycle;
12225
+ const activityWithDetail = agent.activityDetail?.trim() ? `${activity}: ${agent.activityDetail.trim()}` : activity;
12226
+ return activity === lifecycle ? lifecycle : `${lifecycle}; ${activityWithDetail}`;
12227
+ }
12228
+ function formatCurrentAgent(data) {
12229
+ if (!data.serverRole) return "";
12230
+ const lines = ["### Current Agent"];
12231
+ lines.push(`- Role: ${data.serverRole}`);
12232
+ return `${lines.join("\n")}\n\n`;
12233
+ }
12234
+ /** Server overview: runtime context, channels, agents, humans. */
12235
+ function formatAgentServerInfo(data) {
12236
+ let text = "## Server\n\n";
12237
+ const channels = data.channels ?? [];
12238
+ const agents = data.agents ?? [];
12239
+ const humans = data.humans ?? [];
12240
+ text += formatRuntimeContext(data.runtimeContext);
12241
+ text += formatCurrentAgent(data);
12242
+ text += "### Channels\n";
12243
+ 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";
12244
+ text += "Server-profile changes still use raft server update and remain server-role gated.\n";
12245
+ text += "Mute state is shown when the server provides it; otherwise it is omitted.\n";
12246
+ if (channels.length > 0) for (const t of channels) {
12247
+ const statusParts = [t.type === "private" || t.type === "dm" ? "private" : "public", t.joined ? "joined" : "not joined"];
12248
+ if (t.channelRole) statusParts.push(`channel role=${t.channelRole}`);
12249
+ if (t.channelAdminBasis) statusParts.push(`admin via=${t.channelAdminBasis}`);
12250
+ const muted = channelMuted(t);
12251
+ if (muted !== void 0) statusParts.push(muted ? "muted" : "not muted");
12252
+ const status = statusParts.join(", ");
12253
+ const ref = agentChannelRef(t.name, t.type);
12254
+ text += t.description ? ` - ${ref} [${status}] — ${t.description}\n` : ` - ${ref} [${status}]\n`;
12255
+ }
12256
+ else text += " (none)\n";
12257
+ text += "\n### Agents\n";
12258
+ text += "Other AI agents in this server.\n";
12259
+ text += "Role labels show server-level owner/admin authority; no role label means ordinary member.\n";
12260
+ if (agents.length > 0) for (const a of agents) {
12261
+ const role = roleLabel(a.role);
12262
+ const status = agentStatusLabel(a);
12263
+ text += a.description ? ` - @${a.name} (${status})${role} — ${a.description}\n` : ` - @${a.name} (${status})${role}\n`;
12264
+ }
12265
+ else text += " (none)\n";
12266
+ text += "\n### Humans\n";
12267
+ 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";
12268
+ text += "Role labels show server-level owner/admin authority; no role label means ordinary member.\n";
12269
+ if (humans.length > 0) for (const u of humans) {
12270
+ const role = roleLabel(u.role);
12271
+ text += u.description ? ` - @${u.name}${role} — ${u.description}\n` : ` - @${u.name}${role}\n`;
12272
+ }
12273
+ else text += " (none)\n";
12274
+ return text;
12275
+ }
12276
+ function agentChannelRef(name, type) {
12277
+ if (type === "dm") return `dm:@${name}`;
12278
+ return name.startsWith("#") ? name : `#${name}`;
12279
+ }
12280
+ function channelVisibility(channel) {
12281
+ const type = channel.type?.trim();
12282
+ return type === "private" || type === "dm" ? "private" : "public";
12283
+ }
12284
+ function channelMuted(channel) {
12285
+ if (typeof channel.muted === "boolean") return channel.muted;
12286
+ if (typeof channel.activityMuted === "boolean") return channel.activityMuted;
12287
+ }
12288
+ function channelStatus(channel) {
12289
+ const parts = [channelVisibility(channel), channel.joined ? "joined" : "not joined"];
12290
+ const muted = channelMuted(channel);
12291
+ if (channel.channelRole) parts.push(`channel role=${channel.channelRole}`);
12292
+ if (channel.channelAdminBasis) parts.push(`admin via=${channel.channelAdminBasis}`);
12293
+ if (muted !== void 0) parts.push(muted ? "muted" : "not muted");
12294
+ if (typeof channel.archived === "boolean") parts.push(channel.archived ? "archived" : "not archived");
12295
+ return parts.join(", ");
12296
+ }
12297
+ function formatPageFooter(page) {
12298
+ if (!page) return "";
12299
+ const start = page.total === 0 ? 0 : Math.min(page.offset + 1, page.total);
12300
+ const end = Math.min(page.offset + page.limit, page.total);
12301
+ const lines = [`\nShowing ${start}-${end} of ${page.total}.`];
12302
+ if (page.nextCommand && end < page.total) lines.push(`More: ${page.nextCommand}`);
12303
+ return `${lines.join("\n")}\n`;
12304
+ }
12305
+ /** Compact server summary. */
12306
+ function formatAgentServerSummary(data) {
12307
+ const channels = data.channels ?? [];
12308
+ const agents = data.agents ?? [];
12309
+ const humans = data.humans ?? [];
12310
+ const joined = channels.filter((channel) => channel.joined).length;
12311
+ return `${[
12312
+ "## Server",
12313
+ "",
12314
+ `Channels: ${channels.length} visible (${joined} joined)`,
12315
+ `Agents: ${agents.length}`,
12316
+ `Humans: ${humans.length}`,
12317
+ "",
12318
+ "Narrow queries:",
12319
+ "- raft server info --channels",
12320
+ "- raft server info --agents",
12321
+ "- raft server info --humans",
12322
+ "- raft channel info <name>",
12323
+ "- raft user info <name>",
12324
+ "",
12325
+ "Full dump: raft server info --full"
12326
+ ].join("\n")}\n`;
12327
+ }
12328
+ /** Channel listing page. */
12329
+ function formatAgentServerChannels(channels, page) {
12330
+ const lines = [
12331
+ "## Server Channels",
12332
+ "",
12333
+ "Private channels are shown only when this agent is a member. Do not disclose private-channel names or metadata outside that channel."
12334
+ ];
12335
+ if (channels.length === 0) lines.push("(none)");
12336
+ else for (const channel of channels) {
12337
+ const description = channel.description?.trim();
12338
+ lines.push(description ? `${agentChannelRef(channel.name, channel.type)} [${channelStatus(channel)}] — ${description}` : `${agentChannelRef(channel.name, channel.type)} [${channelStatus(channel)}]`);
12339
+ }
12340
+ return `${lines.join("\n")}${formatPageFooter(page)}`;
12341
+ }
12342
+ /** Agent listing page. */
12343
+ function formatAgentServerAgents(agents, page) {
12344
+ const lines = [
12345
+ "## Server Agents",
12346
+ "",
12347
+ "Role labels show server-level owner/admin authority; no role label means ordinary member."
12348
+ ];
12349
+ if (agents.length === 0) lines.push("(none)");
12350
+ else for (const agent of agents) {
12351
+ const role = roleLabel(agent.role);
12352
+ const status = agentStatusLabel(agent);
12353
+ lines.push(agent.description ? `@${agent.name} (${status})${role} — ${agent.description}` : `@${agent.name} (${status})${role}`);
12354
+ }
12355
+ return `${lines.join("\n")}${formatPageFooter(page)}`;
12356
+ }
12357
+ /** Human listing page. */
12358
+ function formatAgentServerHumans(humans, page) {
12359
+ const lines = [
12360
+ "## Server Humans",
12361
+ "",
12362
+ "Role labels show server-level owner/admin authority; no role label means ordinary member."
12363
+ ];
12364
+ if (humans.length === 0) lines.push("(none)");
12365
+ else for (const human of humans) {
12366
+ const role = roleLabel(human.role);
12367
+ lines.push(human.description ? `@${human.name}${role} — ${human.description}` : `@${human.name}${role}`);
12368
+ }
12369
+ return `${lines.join("\n")}${formatPageFooter(page)}`;
12370
+ }
12371
+ /** Channel membership with server-role labels. */
12372
+ function formatAgentChannelMembers(data) {
12373
+ let text = "## Channel Members\n\n";
12374
+ const ref = data.channel?.ref ?? "(unknown)";
12375
+ const type = data.channel?.type ? ` (${data.channel.type})` : "";
12376
+ const agents = data.agents ?? [];
12377
+ const humans = data.humans ?? [];
12378
+ text += `Channel: ${ref}${type}\n`;
12379
+ text += "Members means join/post authority for this surface.\n\n";
12380
+ text += "### Agents\n";
12381
+ text += "Server and stored channel roles are shown separately when available.\n";
12382
+ if (agents.length > 0) for (const a of agents) {
12383
+ const role = roleLabel(a.role);
12384
+ const channelRole = channelMemberRoleDetail(a);
12385
+ const status = agentStatusLabel(a);
12386
+ text += a.description ? ` - @${a.name} (${status})${role}${channelRole} — ${a.description}\n` : ` - @${a.name} (${status})${role}${channelRole}\n`;
12387
+ }
12388
+ else text += " (none)\n";
12389
+ text += "\n### Humans\n";
12390
+ text += "Server and stored channel roles are shown separately when available.\n";
12391
+ if (humans.length > 0) for (const u of humans) {
12392
+ const role = roleLabel(u.role);
12393
+ const channelRole = channelMemberRoleDetail(u);
12394
+ text += u.description ? ` - @${u.name}${role}${channelRole} — ${u.description}\n` : ` - @${u.name}${role}${channelRole}\n`;
12395
+ }
12396
+ else text += " (none)\n";
12397
+ return text;
12398
+ }
12399
+ //#endregion
12400
+ //#region ../shared/src/runtimeCatalog.ts
12401
+ const EXTERNAL_AGENT_RUNTIME_ID = "external";
12402
+ const EXTERNAL_AGENT_RUNTIME_DISPLAY_NAME = "External agent";
12403
+ function isExternalAgentRuntime(runtime) {
12404
+ return runtime === EXTERNAL_AGENT_RUNTIME_ID;
12405
+ }
12406
+ const RUNTIMES = [
12407
+ {
12408
+ id: "claude",
12409
+ displayName: "Claude Code",
12410
+ abbreviation: "CC",
12411
+ binary: "claude",
12412
+ supported: true
12413
+ },
12414
+ {
12415
+ id: "codex",
12416
+ displayName: "Codex CLI",
12417
+ abbreviation: "CX",
12418
+ binary: "codex",
12419
+ supported: true
12420
+ },
12421
+ {
12422
+ id: "grok",
12423
+ displayName: "Grok Build",
12424
+ abbreviation: "GK",
12425
+ binary: "grok",
12426
+ supported: true
12427
+ },
12428
+ {
12429
+ id: "builtin",
12430
+ displayName: "Built-in Pi",
12431
+ abbreviation: "BP",
12432
+ binary: "",
12433
+ supported: true
12434
+ },
12435
+ {
12436
+ id: "antigravity",
12437
+ displayName: "Antigravity CLI",
12438
+ abbreviation: "AG",
12439
+ binary: "agy",
12440
+ supported: true,
12441
+ deprecated: true
12442
+ },
12443
+ {
12444
+ id: "kimi-sdk",
12445
+ displayName: "Kimi Code",
12446
+ abbreviation: "KC",
12447
+ binary: "",
12448
+ supported: true
12449
+ },
12450
+ {
12451
+ id: "kimi",
12452
+ displayName: "Kimi CLI",
12453
+ abbreviation: "KL",
12454
+ binary: "kimi",
12455
+ supported: true,
12456
+ deprecated: true
12457
+ },
12458
+ {
12459
+ id: "copilot",
12460
+ displayName: "Copilot CLI",
12461
+ abbreviation: "CP",
12462
+ binary: "copilot",
12463
+ supported: true
12464
+ },
12465
+ {
12466
+ id: "cursor",
12467
+ displayName: "Cursor CLI",
12468
+ abbreviation: "CU",
12469
+ binary: "cursor-agent",
12470
+ supported: true
12471
+ },
12472
+ {
12473
+ id: "gemini",
12474
+ displayName: "Gemini CLI",
12475
+ abbreviation: "GM",
12476
+ binary: "gemini",
12477
+ supported: true,
12478
+ deprecated: true
12479
+ },
12480
+ {
12481
+ id: "opencode",
12482
+ displayName: "OpenCode",
12483
+ abbreviation: "OC",
12484
+ binary: "opencode",
12485
+ supported: true
12486
+ },
12487
+ {
12488
+ id: "pi",
12489
+ displayName: "Pi",
12490
+ abbreviation: "PI",
12491
+ binary: "pi",
12492
+ supported: true
12493
+ }
12494
+ ];
12495
+ function isRuntimeDeprecated(runtimeId) {
12496
+ return RUNTIMES.some((runtime) => runtime.id === runtimeId && Boolean(runtime.deprecated));
12497
+ }
12498
+ /** Map runtime ID → display name. Falls back to the ID itself. */
12499
+ function getRuntimeDisplayName(id) {
12500
+ if (isExternalAgentRuntime(id)) return EXTERNAL_AGENT_RUNTIME_DISPLAY_NAME;
12501
+ return RUNTIMES.find((r) => r.id === id)?.displayName ?? id;
12502
+ }
12503
+ //#endregion
12504
+ //#region ../shared/src/agentText/threadsProfile.ts
12505
+ /**
12506
+ * Runtime label for identity display in agent-facing output: product name plus
12507
+ * lifecycle status. Agent-facing text is English-only, so the suffix is literal
12508
+ * here; the web surface renders the same status from the i18n catalog.
12509
+ */
12510
+ function agentRuntimeLabelWithStatus(runtimeId) {
12511
+ const name = getRuntimeDisplayName(runtimeId);
12512
+ return isRuntimeDeprecated(runtimeId) ? `${name} (deprecated)` : name;
12513
+ }
12514
+ function formatDoneSuffix(thread) {
12515
+ return thread.doneAt ? `, doneAt=${thread.doneAt}` : "";
12516
+ }
12517
+ /** Followed thread list for the bound agent, including exact thread targets. */
12518
+ function formatAgentThreadList(threads) {
12519
+ if (threads.length === 0) return "No followed threads.";
12520
+ return [`Followed threads (${threads.length}):`, ...threads.map((thread) => `- ${thread.target} (${thread.threadChannelId}, parent=${thread.parentChannelRef}, followedAt=${thread.followedAt}, reason=${thread.reason}${formatDoneSuffix(thread)})`)].join("\n");
12521
+ }
12522
+ function formatCreatedAgents(createdAgents, runtimeLabel) {
12523
+ if (createdAgents.length === 0) return ["- Created Agents: none"];
12524
+ return [`- Created Agents (${createdAgents.length}):`, ...createdAgents.map((createdAgent) => ` - @${createdAgent.name} (${runtimeLabel(createdAgent.runtime)}, ${createdAgent.status})`)];
12525
+ }
12526
+ function formatHumanProfile(profile, runtimeLabel) {
12527
+ const lines = [
12528
+ "## Profile",
12529
+ "",
12530
+ "- Type: human",
12531
+ `- Handle: @${profile.name}`,
12532
+ `- Display Name: ${profile.displayName ?? "(none)"}`,
12533
+ `- Description: ${profile.description ?? "(none)"}`,
12534
+ `- Membership: ${profile.membershipStatus}`
12535
+ ];
12536
+ if (profile.role) lines.push(`- Role: ${profile.role}`);
12537
+ if (profile.joinedAt) lines.push(`- Joined: ${profile.joinedAt}`);
12538
+ if (profile.email) lines.push(`- Email: ${profile.email}`);
12539
+ return [...lines, ...formatCreatedAgents(profile.createdAgents, runtimeLabel)].join("\n");
12540
+ }
12541
+ function formatCreator(profile) {
12542
+ if (!profile.creator) return null;
12543
+ return profile.creator.displayName ? `${profile.creator.displayName} (@${profile.creator.name})` : `@${profile.creator.name}`;
12544
+ }
12545
+ function formatAgentProfileCard(profile, runtimeLabel) {
12546
+ const lines = [
12547
+ "## Profile",
12548
+ "",
12549
+ "- Type: agent",
12550
+ `- Handle: @${profile.name}`,
12551
+ `- Display Name: ${profile.displayName ?? "(none)"}`,
12552
+ `- Description: ${profile.description ?? "(none)"}`,
12553
+ `- Status: ${profile.status}`,
12554
+ `- Role: ${profile.serverRole}`,
12555
+ `- Runtime: ${runtimeLabel(profile.runtime)}`,
12556
+ `- Model: ${profile.model}`,
12557
+ `- Reasoning: ${profile.reasoningEffort ?? "medium"}`
12558
+ ];
12559
+ if (profile.executionMode) lines.push(`- Execution: ${profile.executionMode}`);
12560
+ if (profile.computerName || profile.computerId) {
12561
+ const label = profile.computerName && profile.computerId ? `${profile.computerName} (${profile.computerId})` : profile.computerName ?? profile.computerId;
12562
+ lines.push(`- Computer: ${label}`);
12563
+ }
12564
+ if (profile.computerHostname) lines.push(`- Hostname: ${profile.computerHostname}`);
12565
+ if (profile.daemonVersion) lines.push(`- Daemon: v${profile.daemonVersion}`);
12566
+ lines.push(`- Created: ${profile.createdAt}`);
12567
+ if (profile.deletedAt) lines.push(`- Deleted At: ${profile.deletedAt}`);
12568
+ const creator = formatCreator(profile);
12569
+ if (creator) lines.push(`- Creator: ${creator}`);
12570
+ return [...lines, ...formatCreatedAgents(profile.createdAgents, runtimeLabel)].join("\n");
12571
+ }
12572
+ /** Agent/human profile card. */
12573
+ function formatAgentProfile(profile, options = {}) {
12574
+ const runtimeLabel = options.runtimeLabel ?? agentRuntimeLabelWithStatus;
12575
+ return profile.kind === "human" ? formatHumanProfile(profile, runtimeLabel) : formatAgentProfileCard(profile, runtimeLabel);
12576
+ }
12577
+ //#endregion
12578
+ //#region ../shared/src/agentOps/channels.ts
12579
+ const INVALID_CHANNEL_TARGET = "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported.";
12580
+ async function resolveRegularChannel(client, target) {
12581
+ const name = parseRaftRegularChannelTarget$1(target ?? "");
12582
+ if (!name) return failureOutcome(opError("INVALID_REQUEST", { message: INVALID_CHANNEL_TARGET }));
12583
+ const info = await client.server.info();
12584
+ if (!info.ok) return failureFromClientResult(info);
12585
+ const channel = info.data.channels.find((candidate) => candidate.name === name);
12586
+ if (!channel) return failureOutcome(opError("NOT_FOUND", {
12587
+ message: `Channel not found: ${target}`,
12588
+ nextAction: "List visible channels with `raft server info --channels`; private channels need a human to add you."
12589
+ }));
12590
+ return {
12591
+ id: channel.id,
12592
+ name: channel.name,
12593
+ joined: channel.joined
12594
+ };
12595
+ }
12596
+ async function joinChannel(client, request) {
12597
+ const result = await joinRaftChannelByTarget$1(client, { target: request.target });
12598
+ if (!result.ok) {
12599
+ const error = result.error;
12600
+ if (error.kind === "validation") return error.reason === "target_not_found" ? failureOutcome(opError("NOT_FOUND", {
12601
+ message: error.message,
12602
+ nextAction: "Only visible public channels can be joined; private and joint channels need an invitation."
12603
+ })) : failureOutcome(opError("INVALID_REQUEST", { message: error.message }));
12604
+ if (error.kind === "http") return failureOutcome(opError("HTTP_ERROR", {
12605
+ message: error.message,
12606
+ status: error.status,
12607
+ ...error.errorCode ? { serverCode: error.errorCode } : {},
12608
+ ...error.suggestedNextAction ? { nextAction: error.suggestedNextAction } : {}
12609
+ }));
12610
+ return failureOutcome(opError("TRANSPORT_ERROR", { message: error.message }));
12611
+ }
12612
+ const state = result.data.state;
12613
+ return {
12614
+ ok: true,
12615
+ state,
12616
+ data: {
12617
+ target: result.data.target,
12618
+ channelId: result.data.channelId
12619
+ },
12620
+ next: {
12621
+ kind: "read_target",
12622
+ command: `raft message read --target "${result.data.target}"`,
12623
+ args: { target: result.data.target },
12624
+ why: "Read the channel before posting."
12625
+ },
12626
+ text: state === "joined" ? `Joined ${result.data.target}.` : `Already a member of ${result.data.target}.`
12627
+ };
12628
+ }
12629
+ async function leaveChannel(client, request) {
12630
+ const channel = await resolveRegularChannel(client, request.target);
12631
+ if ("ok" in channel) return channel;
12632
+ if (!channel.joined) return {
12633
+ ok: true,
12634
+ state: "not_joined",
12635
+ data: {
12636
+ target: request.target,
12637
+ channelId: channel.id
12638
+ },
12639
+ next: null,
12640
+ text: `You are not a member of ${request.target}; nothing to leave.`
12641
+ };
12642
+ const result = await client.channels.leave({ channelId: channel.id });
12643
+ if (!result.ok) return failureFromClientResult(result);
12644
+ const lines = [`Left ${request.target}. You can still inspect visible public channel history there, but you can no longer send or receive ordinary channel delivery until you join the public channel again or a human re-adds you to a private channel.`];
12645
+ const attention = result.data.attention;
12646
+ if (attention?.ordinaryActivity) lines.push(attention.ordinaryActivity);
12647
+ return {
12648
+ ok: true,
12649
+ state: "left",
12650
+ data: {
12651
+ target: request.target,
12652
+ channelId: channel.id
12653
+ },
12654
+ next: null,
12655
+ text: lines.join("\n")
12656
+ };
12657
+ }
12658
+ function formatSeq(value) {
12659
+ return value == null ? "none" : String(value);
12660
+ }
12661
+ async function setChannelMute(client, request, action) {
12662
+ const channel = await resolveRegularChannel(client, request.target);
12663
+ if ("ok" in channel) return channel;
12664
+ const result = action === "mute" ? await client.channels.mute({ channelId: channel.id }, {}) : await client.channels.unmute({ channelId: channel.id });
12665
+ if (!result.ok) return failureFromClientResult(result);
12666
+ const data = result.data;
12667
+ const lines = [
12668
+ `${data.activityMuted ? "Muted" : "Unmuted"} ${request.target}.`,
12669
+ `Activity muted: ${data.activityMuted ? "yes" : "no"}`,
12670
+ `Mute from seq: ${formatSeq(data.muteFromSeq)}`
12671
+ ];
12672
+ if (data.attention?.ordinaryActivity) lines.push(data.attention.ordinaryActivity);
12673
+ if (data.attention?.stillArrives?.length) {
12674
+ lines.push("Still arrives:");
12675
+ for (const item of data.attention.stillArrives) lines.push(`- ${item}`);
12676
+ }
12677
+ if (data.attention?.threadBoundary) lines.push(data.attention.threadBoundary);
12678
+ if (data.attention?.catchUp) lines.push(data.attention.catchUp);
12679
+ if (data.attention?.unmuteCommand) lines.push(`To unmute: ${data.attention.unmuteCommand}`);
12680
+ if (data.attention?.unmuteApi) lines.push(`Agent API: ${data.attention.unmuteApi}`);
12681
+ if (data.attention?.muteCommand) lines.push(`To mute: ${data.attention.muteCommand}`);
12682
+ if (data.attention?.muteApi) lines.push(`Agent API: ${data.attention.muteApi}`);
12683
+ return {
12684
+ ok: true,
12685
+ state: data.activityMuted ? "muted" : "unmuted",
12686
+ data: {
12687
+ target: request.target,
12688
+ channelId: channel.id,
12689
+ activityMuted: data.activityMuted === true,
12690
+ muteFromSeq: data.muteFromSeq ?? null,
12691
+ stillArrives: data.attention?.stillArrives ?? []
12692
+ },
12693
+ next: null,
12694
+ text: lines.join("\n")
12695
+ };
12696
+ }
12697
+ function muteChannel(client, request) {
12698
+ return setChannelMute(client, request, "mute");
12699
+ }
12700
+ function unmuteChannel(client, request) {
12701
+ return setChannelMute(client, request, "unmute");
12702
+ }
12703
+ async function channelMembers(client, request) {
12704
+ if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel, DM, or thread target is required." }));
12705
+ const result = await client.channels.members({ channel: request.target });
12706
+ if (!result.ok) return failureFromClientResult(result);
12707
+ return {
12708
+ ok: true,
12709
+ state: "members",
12710
+ data: result.data,
12711
+ next: null,
12712
+ text: formatAgentChannelMembers(result.data)
12713
+ };
12714
+ }
12715
+ async function listThreads(client) {
12716
+ const result = await client.threads.list();
12717
+ if (!result.ok) return failureFromClientResult(result);
12718
+ const threads = result.data.threads;
12719
+ return {
12720
+ ok: true,
12721
+ state: threads.length > 0 ? "threads" : "empty",
12722
+ data: threads,
12723
+ next: null,
12724
+ text: formatAgentThreadList(threads)
12725
+ };
12726
+ }
12727
+ async function unfollowThread(client, request) {
12728
+ if (!request.target?.includes(":")) return failureOutcome(opError("INVALID_REQUEST", { message: "A thread target is required, for example '#channel:shortid' or 'dm:@peer:shortid'." }));
12729
+ const result = await client.threads.unfollow({
12730
+ thread: request.target,
12731
+ ...request.reason ? { reason: request.reason } : {}
12732
+ });
12733
+ if (!result.ok) return failureFromClientResult(result);
12734
+ return {
12735
+ ok: true,
12736
+ state: "unfollowed",
12737
+ data: { target: request.target },
12738
+ next: null,
12739
+ text: `Unfollowed ${request.target}. Ordinary delivery from this thread stops; a direct @mention reactivates the follow.`
12740
+ };
12741
+ }
12742
+ //#endregion
12743
+ //#region ../shared/src/agentOps/server.ts
12744
+ function nextCommand(section, request, offset, limit, total) {
12745
+ const nextOffset = offset + limit;
12746
+ if (nextOffset >= total) return void 0;
12747
+ const parts = [
12748
+ "raft server info",
12749
+ `--${section}`,
12750
+ `--offset ${nextOffset}`,
12751
+ `--limit ${limit}`
12752
+ ];
12753
+ if (section === "channels" && request.joined) parts.push("--joined");
12754
+ return parts.join(" ");
12755
+ }
12756
+ async function serverInfo(client, request = {}) {
12757
+ const result = await client.server.info();
12758
+ if (!result.ok) return failureFromClientResult(result);
12759
+ const server = result.data;
12760
+ const view = request.view ?? "summary";
12761
+ if (view === "summary") return {
12762
+ ok: true,
12763
+ state: "info",
12764
+ data: {
12765
+ view,
12766
+ server,
12767
+ page: null
12768
+ },
12769
+ next: {
12770
+ kind: "list_channels",
12771
+ command: "raft server info --channels",
12772
+ why: "The summary only counts; list a section to see names."
12773
+ },
12774
+ text: formatAgentServerSummary(server)
12775
+ };
12776
+ if (view === "full") return {
12777
+ ok: true,
12778
+ state: "info",
12779
+ data: {
12780
+ view,
12781
+ server,
12782
+ page: null
12783
+ },
12784
+ next: null,
12785
+ text: formatAgentServerInfo(server)
12786
+ };
12787
+ const limit = Math.max(1, Math.trunc(request.limit ?? 50));
12788
+ const offset = Math.max(0, Math.trunc(request.offset ?? 0));
12789
+ const rows = view === "channels" ? server.channels.filter((c) => !request.joined || c.joined) : view === "agents" ? server.agents : server.humans;
12790
+ const pageRows = rows.slice(offset, offset + limit);
12791
+ const page = {
12792
+ section: view,
12793
+ total: rows.length,
12794
+ offset,
12795
+ limit,
12796
+ nextCommand: nextCommand(view, request, offset, limit, rows.length)
12797
+ };
12798
+ const text = view === "channels" ? formatAgentServerChannels(pageRows, page) : view === "agents" ? formatAgentServerAgents(pageRows, page) : formatAgentServerHumans(pageRows, page);
12799
+ const next = page.nextCommand ? {
12800
+ kind: "next_page",
12801
+ command: page.nextCommand,
12802
+ args: {
12803
+ view,
12804
+ offset: offset + limit,
12805
+ limit
12806
+ },
12807
+ why: "More rows exist; one page is one page."
12808
+ } : null;
12809
+ return {
12810
+ ok: true,
12811
+ state: "info",
12812
+ data: {
12813
+ view,
12814
+ server,
12815
+ page
12816
+ },
12817
+ next,
12818
+ text
12819
+ };
12820
+ }
12821
+ async function showProfile(client, request = {}, options = {}) {
12822
+ const result = await client.profile.show(request.target ? { target: request.target } : {});
12823
+ if (!result.ok) return failureFromClientResult(result);
12824
+ return {
12825
+ ok: true,
12826
+ state: "profile",
12827
+ data: result.data,
12828
+ next: null,
12829
+ text: formatAgentProfile(result.data, options)
12830
+ };
12831
+ }
12832
+ async function updateProfile(client, request, options = {}) {
12833
+ if (request.displayName === void 0 && request.description === void 0 && request.avatarUrl === void 0) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass a displayName, description, or avatarUrl to update." }));
12834
+ const result = await client.profile.update(request);
12835
+ if (!result.ok) return failureFromClientResult(result);
12836
+ return {
12837
+ ok: true,
12838
+ state: "updated",
12839
+ data: result.data,
12840
+ next: null,
12841
+ text: formatAgentProfile(result.data, options)
12842
+ };
12843
+ }
12844
+ //#endregion
12845
+ //#region ../shared/src/agentText/attachments.ts
12846
+ /** Upload receipt with attachment id and send-usage hint. */
12847
+ function formatAgentAttachmentUploaded(attachment) {
12848
+ 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`;
12849
+ }
12850
+ function anchorSummary(anchor) {
12851
+ if (anchor.type === "md-section") {
12852
+ const title = anchor.data.headingTitle ?? anchor.data.headingId;
12853
+ return typeof title === "string" && title ? `§ ${title}` : "§ section";
12854
+ }
12855
+ if (anchor.type === "lines" || anchor.type === "csv-rows") {
12856
+ const start = Number(anchor.data.start);
12857
+ const end = Number(anchor.data.end ?? start);
12858
+ if (!Number.isFinite(start)) return anchor.type;
12859
+ const prefix = anchor.type === "lines" ? "L" : "rows ";
12860
+ return start === (Number.isFinite(end) ? end : start) ? `${prefix}${start}` : `${prefix}${start}–${end}`;
12861
+ }
12862
+ if (anchor.type === "html-region") {
12863
+ const quote = anchor.data.quote;
12864
+ if (typeof quote === "string" && quote.trim().length > 0) {
12865
+ const trimmed = quote.trim();
12866
+ return trimmed.length > 60 ? `${trimmed.slice(0, 60)}…` : trimmed;
12867
+ }
12868
+ return "HTML region";
12869
+ }
12870
+ return anchor.type;
12871
+ }
12872
+ /** Attachment-scoped comment list incl. anchors/reactions, or its empty state. */
12873
+ function formatAgentAttachmentComments(attachmentId, comments, threadChannelId) {
12874
+ if (comments.length === 0) return `No comments on attachment ${attachmentId.slice(0, 8)}.\n`;
12875
+ const lines = [`## Comments on attachment ${attachmentId.slice(0, 8)} (${comments.length})`];
12876
+ for (const c of comments) {
12877
+ const check = c.reactions.some((r) => r.emoji === "✅") ? " ✅" : "";
12878
+ const anchor = c.anchor ? ` [anchor: ${anchorSummary(c.anchor)}]` : "";
12879
+ lines.push(`[msg=${c.id.slice(0, 8)} time=${c.createdAt} type=${c.senderType}]${check}${anchor} @${c.senderName}: ${c.content}`);
12880
+ }
12881
+ if (threadChannelId) lines.push(`(full conversation lives in thread channel ${threadChannelId})`);
12882
+ return lines.join("\n") + "\n";
12883
+ }
12884
+ //#endregion
12885
+ //#region ../shared/src/agentOps/attachments.ts
12886
+ /** Compatibility fallback for Servers that predate the capability endpoint (the CLI's constant). */
12887
+ const AGENT_ATTACHMENT_UPLOAD_FALLBACK_MAX_BYTES = 52428800;
12888
+ const FILENAME_MIME_MAP = {
12889
+ ".jpg": "image/jpeg",
12890
+ ".jpeg": "image/jpeg",
12891
+ ".png": "image/png",
12892
+ ".gif": "image/gif",
12893
+ ".webp": "image/webp",
12894
+ ".pdf": "application/pdf",
12895
+ ".txt": "text/plain",
12896
+ ".md": "text/markdown",
12897
+ ".json": "application/json",
12898
+ ".csv": "text/csv"
12899
+ };
12900
+ const MIME_TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i;
12901
+ function sniffMimeType(bytes) {
12902
+ const starts = (sig) => sig.every((b, i) => bytes[i] === b);
12903
+ if (bytes.length >= 8 && starts([
12904
+ 137,
12905
+ 80,
12906
+ 78,
12907
+ 71,
12908
+ 13,
12909
+ 10,
12910
+ 26,
12911
+ 10
12912
+ ])) return "image/png";
12913
+ if (bytes.length >= 3 && starts([
12914
+ 255,
12915
+ 216,
12916
+ 255
12917
+ ])) return "image/jpeg";
12918
+ if (bytes.length >= 6) {
12919
+ const header = String.fromCharCode(...bytes.subarray(0, 6));
12920
+ if (header === "GIF87a" || header === "GIF89a") return "image/gif";
12921
+ }
12922
+ if (bytes.length >= 12 && String.fromCharCode(...bytes.subarray(0, 4)) === "RIFF" && String.fromCharCode(...bytes.subarray(8, 12)) === "WEBP") return "image/webp";
12923
+ return null;
12924
+ }
12925
+ /** Explicit type wins; otherwise sniff the bytes, then the filename extension, then octet-stream. */
12926
+ function inferAttachmentMimeType(filename, bytes, explicit) {
12927
+ const normalized = explicit?.trim().toLowerCase();
12928
+ if (normalized && MIME_TYPE_RE.test(normalized)) return normalized;
12929
+ const dot = filename.lastIndexOf(".");
12930
+ const ext = dot >= 0 ? filename.slice(dot).toLowerCase() : "";
12931
+ return sniffMimeType(bytes) ?? FILENAME_MIME_MAP[ext] ?? "application/octet-stream";
12932
+ }
12933
+ /**
12934
+ * Small-file multipart upload (`POST /upload`). Files at or above the Server's
12935
+ * direct-upload threshold need the upload-session routes (`routes.attachments.
12936
+ * createUploadSession` …), which this operation reports as `UNAVAILABLE` with a
12937
+ * next action rather than attempting silently.
12938
+ */
12939
+ async function uploadAttachment(client, transport, request) {
12940
+ if (!request.target?.trim() || !request.filename?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and a filename are required to upload." }));
12941
+ if (!(request.bytes instanceof Uint8Array) || request.bytes.byteLength === 0) return failureOutcome(opError("INVALID_REQUEST", { message: "Refusing to upload a 0-byte attachment." }));
12942
+ 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}` }));
12943
+ const capability = await client.attachments.uploadCapabilities();
12944
+ const maxBytes = capability.ok ? capability.data.maxBytes : AGENT_ATTACHMENT_UPLOAD_FALLBACK_MAX_BYTES;
12945
+ const threshold = capability.ok && capability.data.directUploadEnabled ? capability.data.directUploadThresholdBytes : null;
12946
+ if (request.bytes.byteLength > maxBytes) return failureOutcome(opError("INVALID_REQUEST", { message: `Attachment is ${request.bytes.byteLength} bytes; the Server's maximum is ${maxBytes}.` }));
12947
+ if (threshold !== null && request.bytes.byteLength >= threshold) return failureOutcome(opError("UNAVAILABLE", {
12948
+ message: `Files of ${threshold} bytes or more use the direct-upload session routes, which this operation does not drive yet.`,
12949
+ nextAction: "Use routes.attachments.createUploadSession / completeUploadSession, or upload a smaller file.",
12950
+ retryable: false
12951
+ }));
12952
+ const resolved = await client.channels.resolve({ target: request.target });
12953
+ if (!resolved.ok) return failureFromClientResult(resolved);
12954
+ const channelId = resolved.data.channelId;
12955
+ const mimeType = inferAttachmentMimeType(request.filename, request.bytes, request.mimeType);
12956
+ const copy = new Uint8Array(request.bytes.byteLength);
12957
+ copy.set(request.bytes);
12958
+ const form = new FormData();
12959
+ form.append("file", new Blob([copy.buffer], { type: mimeType }), request.filename);
12960
+ form.append("channelId", channelId);
12961
+ if (request.mimeType) form.append("mimeType", mimeType);
12962
+ const headers = new Headers(transport.headers);
12963
+ headers.delete("content-type");
12964
+ headers.set("accept", "application/json");
12965
+ headers.set("authorization", transport.authorization);
12966
+ let response;
12967
+ try {
12968
+ response = await transport.fetch(`${transport.serverUrl}${agentApiContract.attachmentUpload.fullPath}`, {
12969
+ method: "POST",
12970
+ headers,
12971
+ body: form
12972
+ });
12973
+ } catch {
12974
+ return failureOutcome(opError("TRANSPORT_ERROR"));
12975
+ }
12976
+ let body = null;
12977
+ try {
12978
+ body = await response.json();
12979
+ } catch {
12980
+ body = null;
12981
+ }
12982
+ if (!response.ok) {
12983
+ const b = body && typeof body === "object" ? body : {};
12984
+ return failureOutcome(opErrorFromClientError({
12985
+ kind: "http",
12986
+ reason: "http_error",
12987
+ message: typeof b.error === "string" ? b.error : `HTTP ${response.status}`,
12988
+ status: response.status,
12989
+ errorCode: typeof b.errorCode === "string" ? b.errorCode : typeof b.code === "string" ? b.code : null,
12990
+ suggestedNextAction: typeof b.suggestedNextAction === "string" ? b.suggestedNextAction : null
12991
+ }));
12992
+ }
12993
+ const parsed = agentApiContract.attachmentUpload.response.body.safeParse(body);
12994
+ if (!parsed.success) return failureOutcome(opError("INVALID_RESPONSE"));
12995
+ const data = parsed.data;
12996
+ return {
12997
+ ok: true,
12998
+ state: "uploaded",
12999
+ data: {
13000
+ ...data,
13001
+ target: request.target,
13002
+ mimeType: data.mimeType ?? mimeType
13003
+ },
13004
+ next: {
13005
+ kind: "send_with_attachment",
13006
+ command: `raft message send --target "${request.target}" --attachment-id ${data.id}`,
13007
+ args: {
13008
+ target: request.target,
13009
+ attachmentIds: [data.id]
13010
+ },
13011
+ why: "The upload alone posts nothing; send a message that links the attachment id."
13012
+ },
13013
+ text: formatAgentAttachmentUploaded(data)
13014
+ };
13015
+ }
13016
+ async function downloadAttachment(client, request) {
13017
+ if (!request.attachmentId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "An attachment id is required." }));
13018
+ const result = await client.attachments.download({ attachmentId: request.attachmentId });
13019
+ if (!result.ok) return failureFromClientResult(result);
13020
+ const bytes = result.data;
13021
+ return {
13022
+ ok: true,
13023
+ state: "downloaded",
13024
+ data: {
13025
+ attachmentId: request.attachmentId,
13026
+ bytes
13027
+ },
13028
+ next: null,
13029
+ text: `Downloaded attachment ${request.attachmentId.slice(0, 8)} (${bytes.byteLength} bytes).`
13030
+ };
13031
+ }
13032
+ async function attachmentComments(client, request) {
13033
+ if (!request.attachmentId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "An attachment id is required." }));
13034
+ const result = await client.attachments.comments({ attachmentId: request.attachmentId }, request.limit === void 0 ? {} : { limit: String(request.limit) });
13035
+ if (!result.ok) return failureFromClientResult(result);
13036
+ const data = result.data;
13037
+ return {
13038
+ ok: true,
13039
+ state: data.comments.length > 0 ? "comments" : "empty",
13040
+ data: {
13041
+ ...data,
13042
+ attachmentId: request.attachmentId
13043
+ },
13044
+ next: null,
13045
+ text: formatAgentAttachmentComments(request.attachmentId, data.comments, data.threadChannelId)
13046
+ };
13047
+ }
13048
+ //#endregion
13049
+ //#region ../shared/src/agentText/search.ts
13050
+ /** Local wall-clock rendering with offset, as the CLI prints search hit times. */
13051
+ function formatLocalTimeWithOffset(iso) {
13052
+ const d = new Date(iso);
13053
+ if (isNaN(d.getTime())) return iso;
13054
+ const pad = (n) => String(n).padStart(2, "0");
13055
+ const offsetMinutes = -d.getTimezoneOffset();
13056
+ const sign = offsetMinutes >= 0 ? "+" : "-";
13057
+ const absOffset = Math.abs(offsetMinutes);
13058
+ const offset = `${sign}${pad(Math.floor(absOffset / 60))}:${pad(absOffset % 60)}`;
13059
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())} ${offset}`;
13060
+ }
13061
+ function renderSearchSource(result) {
13062
+ if (result.channelType === "thread") {
13063
+ const shortId = typeof result.channelName === "string" && result.channelName.startsWith("thread-") ? result.channelName.slice(7) : typeof result.threadId === "string" && result.threadId ? result.threadId.slice(0, 8) : result.channelName;
13064
+ if (result.parentChannelType === "dm") return `dm:${neutralizeAgentRaftRefLiterals(result.parentChannelName ?? "unknown")}:${shortId}`;
13065
+ return `thread:${neutralizeAgentRaftRefLiterals(result.parentChannelName ?? "unknown")}:${shortId}`;
13066
+ }
13067
+ if (result.channelType === "dm") return `dm:${neutralizeAgentRaftRefLiterals(result.channelName ?? "unknown")}`;
13068
+ return `channel:${neutralizeAgentRaftRefLiterals(result.channelName ?? "unknown")}`;
13069
+ }
13070
+ const PREVIEW_BEFORE_CHARS = 80;
13071
+ const PREVIEW_AFTER_CHARS = 120;
13072
+ const PREVIEW_FALLBACK_CHARS = 200;
13073
+ function escapeRegExp(value) {
13074
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13075
+ }
13076
+ function findSearchMatch(content, query) {
13077
+ const normalizedQuery = query.trim();
13078
+ if (!normalizedQuery) return null;
13079
+ const exactIndex = content.toLowerCase().indexOf(normalizedQuery.toLowerCase());
13080
+ if (exactIndex >= 0) return {
13081
+ start: exactIndex,
13082
+ end: exactIndex + normalizedQuery.length
13083
+ };
13084
+ const terms = normalizedQuery.match(/"([^"]+)"|\S+/g) ?? [];
13085
+ for (const rawTerm of terms) {
13086
+ const term = rawTerm.replace(/^"|"$/g, "").trim();
13087
+ if (!term) continue;
13088
+ const match = new RegExp(escapeRegExp(term), "i").exec(content);
13089
+ if (match?.index !== void 0) return {
13090
+ start: match.index,
13091
+ end: match.index + match[0].length
13092
+ };
13093
+ }
13094
+ return null;
13095
+ }
13096
+ function* findRaftRefLiteralRanges(content) {
13097
+ for (const match of content.matchAll(/\bdm:@[A-Za-z0-9][A-Za-z0-9_-]*(?:~(?:agent|human))?/g)) if (match.index !== void 0) yield {
13098
+ start: match.index,
13099
+ end: match.index + match[0].length
13100
+ };
13101
+ for (const match of content.matchAll(/\btask #[0-9]+\b/g)) if (match.index !== void 0) yield {
13102
+ start: match.index,
13103
+ end: match.index + match[0].length
13104
+ };
13105
+ for (const match of content.matchAll(/(^|[\n\s([{"'`;])(@[A-Za-z0-9][A-Za-z0-9_-]*)/g)) {
13106
+ if (match.index === void 0) continue;
13107
+ const prefix = match[1] ?? "";
13108
+ const ref = match[2] ?? "";
13109
+ yield {
13110
+ start: match.index + prefix.length,
13111
+ end: match.index + prefix.length + ref.length
13112
+ };
13113
+ }
13114
+ for (const match of content.matchAll(/(^|[\n\s([{"'`;])(#[A-Za-z][A-Za-z0-9_-]*)/g)) {
13115
+ if (match.index === void 0) continue;
13116
+ const prefix = match[1] ?? "";
13117
+ const ref = match[2] ?? "";
13118
+ yield {
13119
+ start: match.index + prefix.length,
13120
+ end: match.index + prefix.length + ref.length
13121
+ };
13122
+ }
13123
+ }
13124
+ function expandSearchMatchToRefLiteral(content, match) {
13125
+ for (const refRange of findRaftRefLiteralRanges(content)) if (match.start < refRange.end && match.end > refRange.start) return {
13126
+ start: Math.min(match.start, refRange.start),
13127
+ end: Math.max(match.end, refRange.end)
13128
+ };
13129
+ return match;
13130
+ }
13131
+ function trimPreviewWindow(content, start, end) {
13132
+ let trimmedStart = start;
13133
+ let trimmedEnd = end;
13134
+ while (trimmedStart > 0 && /\s/.test(content[trimmedStart] ?? "")) trimmedStart += 1;
13135
+ while (trimmedEnd < content.length && /\s/.test(content[trimmedEnd - 1] ?? "")) trimmedEnd -= 1;
13136
+ return {
13137
+ start: Math.max(0, Math.min(trimmedStart, content.length)),
13138
+ end: Math.max(0, Math.min(trimmedEnd, content.length))
13139
+ };
13140
+ }
13141
+ function escapeSearchComponentLiterals(text) {
13142
+ return text.replace(/<\/?(?:result|preview|match)\b[^>]*>|<omit\s*\/>/gi, (tag) => tag.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
13143
+ }
13144
+ /** Neutralise `@handle`, `#channel`, `dm:@peer`, and `task #N` literals so quoted text cannot route attention. */
13145
+ function neutralizeAgentRaftRefLiterals(text) {
13146
+ return text.replace(/\bdm:@([A-Za-z0-9][A-Za-z0-9_-]*(?:~(?:agent|human))?)/g, "dm:user:$1").replace(/\btask #([0-9]+)\b/g, "task:$1").replace(/(^|[\n\s([{"'`;])@([A-Za-z0-9][A-Za-z0-9_-]*)/g, "$1user:$2").replace(/(^|[\n\s([{"'`;])#([A-Za-z][A-Za-z0-9_-]*)/g, "$1channel:$2");
13147
+ }
13148
+ function renderAgentSearchPreviewText(text) {
13149
+ return neutralizeAgentRaftRefLiterals(escapeSearchComponentLiterals(text));
13150
+ }
13151
+ function renderSearchPreview(content, query) {
13152
+ const foundMatch = findSearchMatch(content, query);
13153
+ const match = foundMatch ? expandSearchMatchToRefLiteral(content, foundMatch) : null;
13154
+ let start = 0;
13155
+ let end = Math.min(content.length, PREVIEW_FALLBACK_CHARS);
13156
+ if (match) {
13157
+ start = Math.max(0, match.start - PREVIEW_BEFORE_CHARS);
13158
+ end = Math.min(content.length, match.end + PREVIEW_AFTER_CHARS);
13159
+ }
13160
+ ({start, end} = trimPreviewWindow(content, start, end));
13161
+ const leadingOmit = start > 0 ? "<omit />" : "";
13162
+ const trailingOmit = end < content.length ? "<omit />" : "";
13163
+ if (!match || match.end <= start || match.start >= end) return `${leadingOmit}${renderAgentSearchPreviewText(content.slice(start, end))}${trailingOmit}`;
13164
+ const before = content.slice(start, match.start);
13165
+ const matched = content.slice(match.start, match.end);
13166
+ const after = content.slice(match.end, end);
13167
+ return [
13168
+ leadingOmit,
13169
+ renderAgentSearchPreviewText(before),
13170
+ "<match>",
13171
+ renderAgentSearchPreviewText(matched),
13172
+ "</match>",
13173
+ renderAgentSearchPreviewText(after),
13174
+ trailingOmit
13175
+ ].join("");
13176
+ }
13177
+ /** Search results with <match>/<omit /> preview markup. */
13178
+ function formatAgentSearchResults(query, data, offset, sort, limit) {
13179
+ const trimmedQuery = query.trim();
13180
+ const oldestShown = data.results?.length ? data.results[data.results.length - 1]?.createdAt : void 0;
13181
+ 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)}`;
13182
+ const effectiveLimit = Math.min(limit ?? 20, 50);
13183
+ const countEqualsLimit = (data.results?.length ?? 0) === effectiveLimit;
13184
+ 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`;
13185
+ 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`;
13186
+ 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";
13187
+ if (!data.results || data.results.length === 0) return `No search results. (${truncation})`;
13188
+ const formatted = data.results.map((result) => {
13189
+ const ref = `msg:${result.id}`;
13190
+ const content = result.content ?? result.snippet ?? "";
13191
+ const sender = neutralizeAgentRaftRefLiterals(result.senderName ?? "unknown");
13192
+ const senderType = result.senderType ? ` (${result.senderType})` : "";
13193
+ const taskProjection = formatAgentTaskCurrentProjection(result.taskCurrentProjection, result.taskNumber, renderAgentSearchPreviewText).trimStart();
13194
+ return [
13195
+ `<result ref="${ref}">`,
13196
+ `Source: ${renderSearchSource(result)}`,
13197
+ `Sender: ${sender}${senderType}`,
13198
+ `Time: ${result.createdAt ? formatLocalTimeWithOffset(result.createdAt) : "-"}`,
13199
+ ...taskProjection ? [taskProjection] : [],
13200
+ "",
13201
+ "<preview>",
13202
+ renderSearchPreview(content, trimmedQuery),
13203
+ "</preview>",
13204
+ "</result>"
13205
+ ].join("\n");
13206
+ }).join("\n\n");
13207
+ const resultLabel = data.results.length === 1 ? "result" : "results";
13208
+ const count = `${data.results.length} ${resultLabel} · ${truncation}`;
13209
+ return [
13210
+ trimmedQuery ? `Search results for: "${trimmedQuery}" (${count})` : `Filtered message results (${count})`,
13211
+ "",
13212
+ formatted,
13213
+ "",
13214
+ "If a result may be relevant but its preview is not enough, read the surrounding context for that result before answering."
13215
+ ].join("\n");
13216
+ }
13217
+ //#endregion
13218
+ //#region ../shared/src/agentOps/search.ts
13219
+ async function searchMessages(client, request) {
13220
+ if (!request.query?.trim() && !request.target && !request.sender) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass a query, or filter by target or sender." }));
13221
+ const result = await client.messages.search({
13222
+ ...request.query?.trim() ? { q: request.query.trim() } : {},
13223
+ ...request.target ? { channel: request.target } : {},
13224
+ ...request.sender ? { sender: request.sender } : {},
13225
+ ...request.sort ? { sort: request.sort } : {},
13226
+ ...request.before ? { before: request.before } : {},
13227
+ ...request.after ? { after: request.after } : {},
13228
+ ...request.limit === void 0 ? {} : { limit: String(request.limit) },
13229
+ ...request.offset === void 0 ? {} : { offset: String(request.offset) }
13230
+ });
13231
+ if (!result.ok) return failureFromClientResult(result);
13232
+ const data = result.data;
13233
+ const hasMore = typeof data.hasMore === "boolean" ? data.hasMore : null;
13234
+ const page = {
13235
+ query: request.query?.trim() ?? "",
13236
+ results: data.results,
13237
+ hasMore
13238
+ };
13239
+ const next = hasMore ? {
13240
+ kind: "next_search_page",
13241
+ args: {
13242
+ ...request,
13243
+ offset: (request.offset ?? 0) + data.results.length
13244
+ },
13245
+ why: "More results exist; page on, or narrow the query."
13246
+ } : null;
13247
+ return {
13248
+ ok: true,
13249
+ state: data.results.length > 0 ? "results" : "empty",
13250
+ data: page,
13251
+ next,
13252
+ text: formatAgentSearchResults(page.query, {
13253
+ results: data.results,
13254
+ ...hasMore === null ? {} : { hasMore }
13255
+ }, request.offset, request.sort, request.limit)
13256
+ };
13257
+ }
13258
+ async function resolveMessage(client, request) {
13259
+ if (!request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id is required." }));
13260
+ const result = await client.messages.resolve({ msgId: request.messageId });
13261
+ if (!result.ok) return failureFromClientResult(result);
13262
+ const message = projectRaftMessage(result.data.message);
13263
+ if (!message) return failureOutcome(opError("INVALID_RESPONSE", { message: "The resolved message carries no conversation identity." }));
13264
+ return {
13265
+ ok: true,
13266
+ state: "message",
13267
+ data: message,
13268
+ next: {
13269
+ kind: "read_target",
13270
+ command: `raft message read --target "${message.target}" --around ${message.shortId ?? request.messageId}`,
13271
+ args: {
13272
+ target: message.target,
13273
+ around: message.id ?? request.messageId
13274
+ },
13275
+ why: "Read the surrounding context before acting on one message."
13276
+ },
13277
+ text: formatAgentMessages([toAgentMessageLike(message.raw)])
13278
+ };
13279
+ }
13280
+ async function reactToMessage(client, request, action = "add") {
13281
+ const emoji = request.emoji?.trim();
13282
+ if (!request.messageId?.trim() || !emoji || /\s/.test(emoji) || emoji.length > 16) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id and a single reaction emoji are required." }));
13283
+ const params = { msgId: request.messageId };
13284
+ const result = action === "add" ? await client.messages.addReaction(params, { emoji }) : await client.messages.removeReaction(params, { emoji });
13285
+ if (!result.ok) return failureFromClientResult(result);
13286
+ const verb = action === "add" ? "added to" : "removed from";
13287
+ return {
13288
+ ok: true,
13289
+ state: action === "add" ? "added" : "removed",
13290
+ data: {
13291
+ messageId: request.messageId,
13292
+ emoji
13293
+ },
13294
+ next: null,
13295
+ text: `Reaction ${emoji} ${verb} message ${request.messageId.slice(0, 8)}.`
13296
+ };
13297
+ }
13298
+ //#endregion
13299
+ //#region ../shared/src/agentText/mentions.ts
13300
+ function normalizeAgentMentionAction(action) {
13301
+ if (action === "notify" || action === "notify_only") return "notify";
13302
+ if (action === "add" || action === "invite") return "add";
13303
+ return null;
13304
+ }
13305
+ function formatActionCommands(action) {
13306
+ const verbs = action.availableActions.map(normalizeAgentMentionAction).filter((verb) => verb !== null);
13307
+ return Array.from(new Set(verbs)).map((verb) => ` ${verb}: raft mention ${verb} ${action.resolutionId}`);
13308
+ }
13309
+ function formatAuthoredMentionToken(targetHandle) {
13310
+ return targetHandle.startsWith("@") ? targetHandle : `@${targetHandle}`;
13311
+ }
13312
+ 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;
13313
+ /** Per-token mention recovery command line; null when the id is not a UUID (fail closed). */
13314
+ function formatAgentMentionNotifyRecoveryCommand(resolutionId) {
13315
+ return PENDING_MENTION_ACTION_ID_RE.test(resolutionId) ? `raft mention notify ${resolutionId}` : null;
13316
+ }
13317
+ function toAgentSenderPendingMentionAction(action) {
13318
+ return {
13319
+ resolutionId: action.resolutionId,
13320
+ messageId: action.messageId,
13321
+ targetHandle: formatAuthoredMentionToken(action.targetHandle),
13322
+ status: "not_queued",
13323
+ reason: "not_in_conversation",
13324
+ consequence: "This @mention did not notify anyone.",
13325
+ expiresAt: action.expiresAt ?? null,
13326
+ recoveryCommand: formatAgentMentionNotifyRecoveryCommand(action.resolutionId)
13327
+ };
13328
+ }
13329
+ function toAgentSenderUnresolvedMentionWarning(targetHandle) {
13330
+ return {
13331
+ targetHandle: formatAuthoredMentionToken(targetHandle),
13332
+ status: "not_queued",
13333
+ reason: "unknown_or_not_visible",
13334
+ consequence: "This @mention did not notify anyone.",
13335
+ expiresAt: null,
13336
+ recoveryCommand: null
13337
+ };
13338
+ }
13339
+ function formatPendingReason(reason) {
13340
+ if (reason === "not_member") return "not in the conversation at send time, so the @mention was not delivered";
13341
+ return reason;
13342
+ }
13343
+ function normalizeAgentPendingMentionActions(data) {
13344
+ const value = data;
13345
+ return (Array.isArray(value?.pendingMentionActions) ? value.pendingMentionActions : Array.isArray(value?.actions) ? value.actions : Array.isArray(value?.results) ? value.results : []).filter((item) => Boolean(item && typeof item === "object")).map((item) => ({
13346
+ resolutionId: String(item.resolutionId ?? item.id ?? ""),
13347
+ messageId: String(item.messageId ?? ""),
13348
+ targetType: String(item.targetType ?? "unknown"),
13349
+ targetHandle: String(item.targetHandle ?? ""),
13350
+ reason: String(item.reason ?? "Mention target was not notified at send time."),
13351
+ availableActions: Array.isArray(item.availableActions) ? item.availableActions.map(String) : [],
13352
+ expiresAt: typeof item.expiresAt === "string" ? item.expiresAt : null
13353
+ })).filter((item) => item.resolutionId.length > 0);
13354
+ }
13355
+ function normalizeAgentMentionActionResults(data) {
13356
+ const value = data;
13357
+ return (Array.isArray(value?.results) ? value.results : Array.isArray(value?.actionResults) ? value.actionResults : []).filter((item) => Boolean(item && typeof item === "object")).map((item) => ({
13358
+ resolutionId: String(item.resolutionId ?? item.id ?? ""),
13359
+ status: String(item.status ?? "not_found"),
13360
+ action: typeof item.action === "string" ? normalizeAgentMentionAction(item.action) : null,
13361
+ messageId: typeof item.messageId === "string" ? item.messageId : null,
13362
+ channelId: typeof item.channelId === "string" ? item.channelId : null,
13363
+ targetType: typeof item.targetType === "string" ? item.targetType : null,
13364
+ targetId: typeof item.targetId === "string" ? item.targetId : null,
13365
+ targetHandle: typeof item.targetHandle === "string" ? item.targetHandle : null,
13366
+ message: typeof item.message === "string" ? item.message : null,
13367
+ reason: typeof item.reason === "string" ? item.reason : null,
13368
+ dedupedResolutionIds: Array.isArray(item.dedupedResolutionIds) ? item.dedupedResolutionIds.filter((id) => typeof id === "string" && id.length > 0) : []
13369
+ })).filter((item) => item.resolutionId.length > 0);
13370
+ }
13371
+ const PENDING_DEFAULT_LIMIT = 50;
13372
+ function pendingPageVerdict(hasMore, limit, shown) {
13373
+ const asked = limit === void 0 ? "server default 50" : `--limit ${limit}`;
13374
+ 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`;
13375
+ if (hasMore === false) return `shown ${shown}, ${asked} — truncated=false · this is the complete list`;
13376
+ return `shown ${shown}, ${asked} — truncated=unknown · server did not report has_more, so completeness is NOT asserted`;
13377
+ }
13378
+ /** Undelivered-mentions partial result (send) or the pending list. */
13379
+ function formatAgentPendingMentionActions(actions, opts = {}) {
13380
+ const unresolvedMentionHandles = opts.source === "send" ? Array.from(new Set(opts.unresolvedMentionHandles ?? [])) : [];
13381
+ const pageNote = opts.source === "pending" ? pendingPageVerdict(opts.hasMore, opts.limit, actions.length) : "";
13382
+ if (actions.length === 0 && unresolvedMentionHandles.length === 0) return opts.source === "pending" ? `Pending mention actions\n\nNo pending mention actions. (${pageNote})\n` : "";
13383
+ if (opts.source === "send") {
13384
+ const lines = [
13385
+ "Undelivered mentions — partial result",
13386
+ "Message effect: status=queued. Queue acceptance is the only message proof.",
13387
+ "Do not rerun `raft message send`; the message is already queued and a retry could duplicate it.",
13388
+ "Each row below is bound to the literal @token from your message.",
13389
+ "For a literal name rather than a recipient, wrap the @handle in inline or fenced code.",
13390
+ ""
13391
+ ];
13392
+ for (const rawAction of actions) {
13393
+ const action = toAgentSenderPendingMentionAction(rawAction);
13394
+ lines.push(`- ${action.targetHandle} — status=${action.status}`);
13395
+ lines.push(` reason: ${action.reason}`);
13396
+ lines.push(` consequence: ${action.consequence}`);
13397
+ lines.push(` pending action: ${action.recoveryCommand ? action.resolutionId : "[invalid pending action id]"}`);
13398
+ if (action.messageId) lines.push(` message: ${action.messageId}`);
13399
+ lines.push(` expires: ${action.expiresAt ?? "unknown"}`);
13400
+ if (action.recoveryCommand) {
13401
+ lines.push(` recovery: ${action.recoveryCommand}`);
13402
+ 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.");
13403
+ lines.push(" note: notify exits nonzero unless the target queue accepts the delivery.");
13404
+ } else lines.push(" recovery: unavailable because the pending action id is invalid; inspect `raft mention pending` without resending the message.");
13405
+ }
13406
+ for (const rawHandle of unresolvedMentionHandles) {
13407
+ const warning = toAgentSenderUnresolvedMentionWarning(rawHandle);
13408
+ lines.push(`- ${warning.targetHandle} — status=${warning.status}`);
13409
+ lines.push(` reason: ${warning.reason}`);
13410
+ lines.push(` consequence: ${warning.consequence}`);
13411
+ lines.push(" pending action: none; no visible target resolved for this token");
13412
+ lines.push(" expires: n/a");
13413
+ lines.push(" recovery: if this was a literal name or prose, wrap it in inline/fenced code; otherwise verify the exact handle and send only a corrected follow-up mention; do not resend this message.");
13414
+ }
13415
+ return `${lines.join("\n")}\n`;
13416
+ }
13417
+ const lines = [
13418
+ "Pending mention actions",
13419
+ `(${pageNote})`,
13420
+ ""
13421
+ ];
13422
+ for (const action of actions) {
13423
+ const target = action.targetHandle ? `${action.targetHandle} (${action.targetType})` : action.targetType;
13424
+ lines.push(`- ${action.resolutionId} — ${target}`);
13425
+ if (action.messageId) lines.push(` message: ${action.messageId}`);
13426
+ lines.push(` reason: ${formatPendingReason(action.reason)}`);
13427
+ if (action.expiresAt) lines.push(` expires: ${action.expiresAt}`);
13428
+ const commands = formatActionCommands(action);
13429
+ if (commands.length > 0) {
13430
+ lines.push(" recovery commands:");
13431
+ lines.push(...commands);
13432
+ if (commands.some((command) => command.includes(" mention notify "))) lines.push(" note: notify exits nonzero unless the target queue accepts the delivery.");
13433
+ }
13434
+ }
13435
+ return `${lines.join("\n")}\n`;
13436
+ }
13437
+ function formatMentionActionDetail(result) {
13438
+ const detail = result.message ?? result.reason ?? null;
13439
+ if (result.status === "dropped") return detail ? `not delivered: ${detail}` : "not delivered";
13440
+ return detail;
13441
+ }
13442
+ /** notify/add action outcome rows. */
13443
+ function formatAgentMentionActionResults(action, results) {
13444
+ const lines = [`Mention ${action} results`, ""];
13445
+ if (results.length === 0) {
13446
+ lines.push("No result rows returned.");
13447
+ return `${lines.join("\n")}\n`;
13448
+ }
13449
+ for (const result of results) {
13450
+ const target = result.targetHandle ? ` ${result.targetHandle}` : "";
13451
+ const detail = formatMentionActionDetail(result);
13452
+ const suffix = detail ? ` — ${detail}` : "";
13453
+ lines.push(`- ${result.resolutionId}${target}: ${result.status}${suffix}`);
13454
+ if (result.dedupedResolutionIds && result.dedupedResolutionIds.length > 1) lines.push(` deduped: ${result.dedupedResolutionIds.join(", ")}`);
13455
+ }
13456
+ if (action === "notify" && results.some((result) => result.status === "queued" && result.reason !== "already_queued")) lines.push("", `Recipient guidance: ${formatAgentReplyAffordance({ non_member_mention: true })}`);
13457
+ return `${lines.join("\n")}\n`;
13458
+ }
13459
+ function describeDelivery(row) {
13460
+ switch (row.outcome) {
13461
+ case "delivered": return "delivered";
13462
+ case "pending": return "still in flight — do not conclude yet";
13463
+ case "unknown": return "UNKNOWN — our instrument could not see this one; re-check, do not conclude";
13464
+ case "lost": switch (row.reasonCategory) {
13465
+ case "quota": return "LOST — target is rate/quota limited; it will not arrive, re-route or retry later";
13466
+ case "runtime_error": return "LOST — target's runtime rejected delivery; re-route";
13467
+ case "not_launched": return "LOST — target was not running; re-route or start it";
13468
+ case "unclassified": return "LOST — cause is outside the seat-availability set; re-route";
13469
+ default: return "LOST — server sent no category for this row (malformed); treat the cause as unknown";
13470
+ }
13471
+ }
13472
+ }
13473
+ /** Per-target mention delivery outcome for a message the bound agent sent. */
13474
+ function formatAgentSenderMentionDeliveries(messageId, deliveries) {
13475
+ if (deliveries.length === 0) return `Message ${messageId}\n\nNo mention deliveries recorded for this message.\n`;
13476
+ const lines = [`Message ${messageId} — mention delivery per target`, ""];
13477
+ for (const row of deliveries) lines.push(` ${row.targetHandle || "(unknown target)"}: ${describeDelivery(row)}`);
13478
+ return `${lines.join("\n")}\n`;
13479
+ }
13480
+ //#endregion
13481
+ //#region ../shared/src/agentOps/mentions.ts
13482
+ async function pendingMentionActions(client, request = {}) {
13483
+ const result = await client.mentions.pendingActions(request.limit === void 0 ? {} : { limit: String(request.limit) });
13484
+ if (!result.ok) return failureFromClientResult(result);
13485
+ const actions = normalizeAgentPendingMentionActions(result.data);
13486
+ const hasMore = typeof result.data.has_more === "boolean" ? result.data.has_more : null;
13487
+ const first = actions.find((a) => a.availableActions.length > 0);
13488
+ const next = first ? {
13489
+ kind: "resolve_mention",
13490
+ command: `raft mention notify ${first.resolutionId}`,
13491
+ args: {
13492
+ action: "notify",
13493
+ resolutionIds: [first.resolutionId]
13494
+ },
13495
+ why: "This @mention reached nobody at send time; notify the target (or add them) so the message is seen."
13496
+ } : null;
13497
+ return {
13498
+ ok: true,
13499
+ state: actions.length > 0 ? "pending" : "empty",
13500
+ data: {
13501
+ actions,
13502
+ hasMore,
13503
+ limit: request.limit
13504
+ },
13505
+ next,
13506
+ text: formatAgentPendingMentionActions(actions, {
13507
+ source: "pending",
13508
+ ...hasMore === null ? {} : { hasMore },
13509
+ limit: request.limit
13510
+ })
13511
+ };
13512
+ }
13513
+ async function executeMentionAction(client, request) {
13514
+ const ids = (request.resolutionIds ?? []).map((id) => id.trim()).filter(Boolean);
13515
+ 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." }));
13516
+ const result = await client.mentions.executeAction({
13517
+ action: request.action,
13518
+ resolutionIds: ids
13519
+ });
13520
+ if (!result.ok) return failureFromClientResult(result);
13521
+ const results = normalizeAgentMentionActionResults(result.data);
13522
+ return {
13523
+ ok: true,
13524
+ state: "executed",
13525
+ data: {
13526
+ action: request.action,
13527
+ results
13528
+ },
13529
+ next: null,
13530
+ text: formatAgentMentionActionResults(request.action, results)
13531
+ };
13532
+ }
13533
+ async function senderMentionDeliveries(client, request) {
13534
+ if (!request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id is required." }));
13535
+ const result = await client.mentions.senderDeliveries({ messageId: request.messageId });
13536
+ if (!result.ok) return failureFromClientResult(result);
13537
+ const data = result.data;
13538
+ return {
13539
+ ok: true,
13540
+ state: data.deliveries.length > 0 ? "deliveries" : "empty",
13541
+ data,
13542
+ next: null,
13543
+ text: formatAgentSenderMentionDeliveries(data.messageId, data.deliveries)
13544
+ };
13545
+ }
13546
+ //#endregion
13547
+ //#region ../shared/src/agentText/knowledge.ts
13548
+ function formatMatchReason(result) {
13549
+ const parts = [];
13550
+ for (const { term, matched } of result.correctedTerms ?? []) parts.push(`${term} → ${matched} (typo)`);
13551
+ for (const { from, to } of result.expandedTerms ?? []) parts.push(`${from} → ${to} (concept)`);
13552
+ return parts.length > 0 ? ` matched: ${parts.join(", ")}` : "";
13553
+ }
13554
+ function formatAgentKnowledgeSearchResults(results) {
13555
+ return results.map((result, index) => {
13556
+ const body = result.firstScreen.trim().split(/\r?\n/).filter(Boolean).map((line) => ` ${line}`).join("\n");
13557
+ const reason = formatMatchReason(result);
13558
+ return `${index + 1}. ${result.slug} — ${result.title}${reason ? `\n${reason}` : ""}${body ? `\n${body}` : ""}`;
13559
+ }).join("\n\n") + "\n";
13560
+ }
13561
+ function formatAgentKnowledgeStdout(content) {
13562
+ return content.endsWith("\n") ? content : `${content}\n`;
13563
+ }
13564
+ //#endregion
13565
+ //#region ../shared/src/agentOps/manual.ts
13566
+ function requireContext(context) {
13567
+ if (!context?.intent?.trim() || !context?.reason?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "Manual calls need a short `intent` and `reason`." }));
13568
+ return null;
13569
+ }
13570
+ async function getManualTopic(client, request) {
13571
+ const invalid = requireContext(request);
13572
+ if (invalid) return invalid;
13573
+ if (!request.topic?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A topic id is required." }));
13574
+ const result = await client.knowledge.get({
13575
+ topic: request.topic.trim(),
13576
+ intent: request.intent.trim(),
13577
+ reason: request.reason.trim()
13578
+ });
13579
+ if (!result.ok) return failureFromClientResult(result);
13580
+ return {
13581
+ ok: true,
13582
+ state: "topic",
13583
+ data: result.data,
13584
+ next: null,
13585
+ text: formatAgentKnowledgeStdout(result.data.content)
13586
+ };
13587
+ }
13588
+ async function searchManual(client, request) {
13589
+ const invalid = requireContext(request);
13590
+ if (invalid) return invalid;
13591
+ if (!request.query?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "Search keywords are required." }));
13592
+ const result = await client.knowledge.search({
13593
+ query: request.query.trim(),
13594
+ ...request.scope ? { scope: request.scope } : {},
13595
+ intent: request.intent.trim(),
13596
+ reason: request.reason.trim()
13597
+ });
13598
+ if (!result.ok) return failureFromClientResult(result);
13599
+ const data = result.data;
13600
+ const results = data.results;
13601
+ const first = results[0];
13602
+ const next = first ? {
13603
+ kind: "read_manual_topic",
13604
+ command: `raft manual get ${first.slug} --intent "…" --reason "…"`,
13605
+ args: { topic: first.slug },
13606
+ why: "Open the best-matching topic."
13607
+ } : null;
13608
+ return {
13609
+ ok: true,
13610
+ state: results.length > 0 ? "results" : "empty",
13611
+ data,
13612
+ next,
13613
+ text: results.length > 0 ? formatAgentKnowledgeSearchResults(results) : "No Manual topics matched. Retry with different keywords, keeping your intent and reason.\n"
13614
+ };
13615
+ }
13616
+ //#endregion
11771
13617
  //#region src/raft.ts
11772
13618
  function createRaft(options) {
11773
13619
  const serverUrl = requireServerUrl(options.serverUrl);
@@ -11811,9 +13657,59 @@ function createRaft(options) {
11811
13657
  messages: {
11812
13658
  read: (request) => readHistory(routes, request, frontier),
11813
13659
  send: (request) => sendMessage(routes, request, frontier),
11814
- reply: (message, request) => replyTo(routes, message, request, frontier)
13660
+ reply: (message, request) => replyTo(routes, message, request, frontier),
13661
+ search: (request) => searchMessages(routes, request),
13662
+ resolve: (request) => resolveMessage(routes, request),
13663
+ react: (request) => reactToMessage(routes, request, "add"),
13664
+ unreact: (request) => reactToMessage(routes, request, "remove")
13665
+ },
13666
+ attachments: {
13667
+ upload: (request) => uploadAttachment(routes, {
13668
+ serverUrl,
13669
+ fetch: options.fetch ?? fetch,
13670
+ headers: Object.fromEntries(new Headers(options.headers)),
13671
+ authorization
13672
+ }, request),
13673
+ download: (request) => downloadAttachment(routes, request),
13674
+ comments: (request) => attachmentComments(routes, request)
13675
+ },
13676
+ mentions: {
13677
+ pending: (request) => pendingMentionActions(routes, request),
13678
+ execute: (request) => executeMentionAction(routes, request),
13679
+ deliveries: (request) => senderMentionDeliveries(routes, request)
13680
+ },
13681
+ manual: {
13682
+ get: (request) => getManualTopic(routes, request),
13683
+ search: (request) => searchManual(routes, request)
13684
+ },
13685
+ tasks: {
13686
+ claim: (request) => claimTasks(routes, request),
13687
+ list: (request) => listTasks(routes, request),
13688
+ create: (request) => createTasks(routes, request),
13689
+ unclaim: (request) => unclaimTask(routes, request),
13690
+ assign: (request) => assignTask(routes, request),
13691
+ updateStatus: (request) => updateTaskStatus(routes, request),
13692
+ amend: (request) => amendTask(routes, request),
13693
+ history: (request) => taskHistory(routes, request),
13694
+ convert: (request) => convertMessageToTask(routes, request),
13695
+ delete: (request) => deleteTask(routes, request)
13696
+ },
13697
+ channels: {
13698
+ join: (request) => joinChannel(routes, request),
13699
+ leave: (request) => leaveChannel(routes, request),
13700
+ mute: (request) => muteChannel(routes, request),
13701
+ unmute: (request) => unmuteChannel(routes, request),
13702
+ members: (request) => channelMembers(routes, request)
13703
+ },
13704
+ threads: {
13705
+ list: () => listThreads(routes),
13706
+ unfollow: (request) => unfollowThread(routes, request)
13707
+ },
13708
+ server: { info: (request) => serverInfo(routes, request) },
13709
+ profile: {
13710
+ show: (request) => showProfile(routes, request),
13711
+ update: (request) => updateProfile(routes, request)
11815
13712
  },
11816
- tasks: { claim: (request) => claimTasks(routes, request) },
11817
13713
  frontier,
11818
13714
  routes
11819
13715
  };
@@ -11832,7 +13728,11 @@ exports.createRaftClient = createRaftClient;
11832
13728
  exports.createRaftClientFromStore = createRaftClientFromStore;
11833
13729
  exports.createRaftRoutes = createRaftRoutes;
11834
13730
  exports.describeRaftRoute = describeRaftRoute;
13731
+ exports.hasAgentMessageIdentity = hasAgentMessageIdentity;
13732
+ exports.inferAttachmentMimeType = inferAttachmentMimeType;
11835
13733
  exports.joinRaftChannelByTarget = joinRaftChannelByTarget;
11836
13734
  exports.listRaftRoutes = listRaftRoutes;
11837
13735
  exports.parseRaftRegularChannelTarget = parseRaftRegularChannelTarget;
13736
+ exports.projectRaftMessage = projectRaftMessage;
13737
+ exports.projectRaftMessages = projectRaftMessages;
11838
13738
  exports.verifyInboxNotice = verifyInboxNotice;