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