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