@botiverse/raft-sdk 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/cjs/index.cjs +1148 -1051
- package/dist/esm/index.js +1148 -1052
- package/dist/index.d.ts +143 -3
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -4561,6 +4561,7 @@ const AGENT_API_ROUTE_META = {
|
|
|
4561
4561
|
integrationAppList: read,
|
|
4562
4562
|
integrationAppStatus: read,
|
|
4563
4563
|
actionPrepare: write,
|
|
4564
|
+
agentCommand: write,
|
|
4564
4565
|
attachmentUpload: write,
|
|
4565
4566
|
attachmentUploadCapabilities: read,
|
|
4566
4567
|
attachmentUploadSessionCreate: write,
|
|
@@ -5464,6 +5465,47 @@ const agentApiActionPrepareBodySchema = passthroughObject({
|
|
|
5464
5465
|
target: string().trim().min(1),
|
|
5465
5466
|
action: actionCardActionSchema
|
|
5466
5467
|
});
|
|
5468
|
+
/** Most UTF-8 bytes all argv items together may carry. */
|
|
5469
|
+
const AGENT_API_COMMAND_ARGV_MAX_BYTES = 16384;
|
|
5470
|
+
/** Most characters of `stdin`: the Server's message body cap (MAX_MESSAGE_LENGTH). */
|
|
5471
|
+
const AGENT_API_COMMAND_STDIN_MAX_LENGTH = 32e3;
|
|
5472
|
+
function isIanaTimeZone(value) {
|
|
5473
|
+
try {
|
|
5474
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value });
|
|
5475
|
+
return true;
|
|
5476
|
+
} catch {
|
|
5477
|
+
return false;
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
const agentApiCommandBodySchema = passthroughObject({
|
|
5481
|
+
argv: array(string()).max(64).refine((argv) => argv.reduce((total, item) => total + new TextEncoder().encode(item).length, 0) <= AGENT_API_COMMAND_ARGV_MAX_BYTES, { message: `argv must not exceed ${AGENT_API_COMMAND_ARGV_MAX_BYTES} bytes in total` }),
|
|
5482
|
+
stdin: string().max(AGENT_API_COMMAND_STDIN_MAX_LENGTH).optional(),
|
|
5483
|
+
origin: _enum(["model", "code"]).optional(),
|
|
5484
|
+
idempotencyKey: string().trim().min(1).max(200).optional(),
|
|
5485
|
+
timezone: string().trim().min(1).max(64).refine(isIanaTimeZone, { message: "timezone must be an IANA time zone" }).optional(),
|
|
5486
|
+
/**
|
|
5487
|
+
* The model's current context (opaque). Reads are booked under it and a send
|
|
5488
|
+
* only attests reads from the same context, so a gateway changes it when the
|
|
5489
|
+
* model's context no longer holds what it read (new session, compaction),
|
|
5490
|
+
* not per turn. Absent: reads count in any context.
|
|
5491
|
+
*/
|
|
5492
|
+
contextId: string().trim().min(1).max(200).optional(),
|
|
5493
|
+
/**
|
|
5494
|
+
* `message check` acknowledgement. A hosted check returns the batch it
|
|
5495
|
+
* handed out unacknowledged, with `outcome.data.eventsCursor`; the gateway
|
|
5496
|
+
* sends that value back here on a later request only after that result
|
|
5497
|
+
* reached the model, and the next check then acknowledges the batch.
|
|
5498
|
+
* Absent, or any value other than the cursor of the last batch handed out
|
|
5499
|
+
* (a retry carrying an older cursor included), acknowledges nothing: the
|
|
5500
|
+
* unacknowledged batch is delivered again.
|
|
5501
|
+
*/
|
|
5502
|
+
ackEventsCursor: string().trim().min(1).max(64).optional()
|
|
5503
|
+
});
|
|
5504
|
+
const agentApiCommandResponseSchema = passthroughObject({
|
|
5505
|
+
exitCode: number$1().int(),
|
|
5506
|
+
text: string(),
|
|
5507
|
+
outcome: record(string(), unknown()).nullable()
|
|
5508
|
+
});
|
|
5467
5509
|
const agentApiServerUpdateBodySchema = passthroughObject({
|
|
5468
5510
|
name: string().trim().min(1).max(100).optional(),
|
|
5469
5511
|
hideHumansFromMembers: boolean().optional()
|
|
@@ -7045,6 +7087,19 @@ const agentApiContract = {
|
|
|
7045
7087
|
request: { body: agentApiActionPrepareBodySchema },
|
|
7046
7088
|
response: { body: agentApiActionPrepareResponseSchema }
|
|
7047
7089
|
}),
|
|
7090
|
+
agentCommand: route({
|
|
7091
|
+
key: "agentCommand",
|
|
7092
|
+
method: "POST",
|
|
7093
|
+
path: "/command",
|
|
7094
|
+
client: {
|
|
7095
|
+
resource: "commands",
|
|
7096
|
+
method: "run"
|
|
7097
|
+
},
|
|
7098
|
+
capability: "read",
|
|
7099
|
+
description: "Run one `raft` command as the bound agent and return the text the CLI prints, its exit code and its outcome. HTTP 200 whenever the command ran, failed commands included; each request the command makes is authorized on its own.",
|
|
7100
|
+
request: { body: agentApiCommandBodySchema },
|
|
7101
|
+
response: { body: agentApiCommandResponseSchema }
|
|
7102
|
+
}),
|
|
7048
7103
|
attachmentUpload: route({
|
|
7049
7104
|
key: "attachmentUpload",
|
|
7050
7105
|
method: "POST",
|
|
@@ -9356,6 +9411,31 @@ const AGENT_API_ROUTE_MANIFEST = [
|
|
|
9356
9411
|
"body": true
|
|
9357
9412
|
}
|
|
9358
9413
|
},
|
|
9414
|
+
{
|
|
9415
|
+
"key": "agentCommand",
|
|
9416
|
+
"method": "POST",
|
|
9417
|
+
"path": "/command",
|
|
9418
|
+
"fullPath": "/internal/agent-api/command",
|
|
9419
|
+
"client": {
|
|
9420
|
+
"resource": "commands",
|
|
9421
|
+
"method": "run"
|
|
9422
|
+
},
|
|
9423
|
+
"capability": "read",
|
|
9424
|
+
"description": "Run one `raft` command as the bound agent and return the text the CLI prints, its exit code and its outcome. HTTP 200 whenever the command ran, failed commands included; each request the command makes is authorized on its own.",
|
|
9425
|
+
"sideEffect": "write",
|
|
9426
|
+
"idempotency": "none",
|
|
9427
|
+
"destructive": false,
|
|
9428
|
+
"audience": "both",
|
|
9429
|
+
"request": {
|
|
9430
|
+
"params": false,
|
|
9431
|
+
"query": false,
|
|
9432
|
+
"body": true
|
|
9433
|
+
},
|
|
9434
|
+
"response": {
|
|
9435
|
+
"kind": "json",
|
|
9436
|
+
"body": true
|
|
9437
|
+
}
|
|
9438
|
+
},
|
|
9359
9439
|
{
|
|
9360
9440
|
"key": "attachmentUpload",
|
|
9361
9441
|
"method": "POST",
|
|
@@ -9658,7 +9738,7 @@ const AGENT_API_ROUTE_MANIFEST = [
|
|
|
9658
9738
|
}
|
|
9659
9739
|
];
|
|
9660
9740
|
/** Content hash of AGENT_API_ROUTE_MANIFEST; see computeAgentApiManifestVersion. */
|
|
9661
|
-
const AGENT_API_MANIFEST_VERSION = "
|
|
9741
|
+
const AGENT_API_MANIFEST_VERSION = "311047956109765f";
|
|
9662
9742
|
//#endregion
|
|
9663
9743
|
//#region src/routes.ts
|
|
9664
9744
|
const ROUTE_INFO = Object.fromEntries(AGENT_API_ROUTE_MANIFEST.map((entry) => {
|
|
@@ -9928,613 +10008,288 @@ function createRaftManageClient(readApi, writeApi, avatar) {
|
|
|
9928
10008
|
};
|
|
9929
10009
|
}
|
|
9930
10010
|
//#endregion
|
|
9931
|
-
//#region src/
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
|
|
10011
|
+
//#region ../shared/src/agentOps/outcome.ts
|
|
10012
|
+
const SERVER_ERROR_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
10013
|
+
const DEFAULT_MESSAGES = {
|
|
10014
|
+
INVALID_REQUEST: "The request did not match the Agent API contract; nothing was sent.",
|
|
10015
|
+
TRANSPORT_ERROR: "The request did not reach the Raft Server.",
|
|
10016
|
+
HTTP_ERROR: "The Raft Server rejected the request.",
|
|
10017
|
+
INVALID_RESPONSE: "The Raft Server response did not match the Agent API contract.",
|
|
10018
|
+
CAPABILITY_NOT_AUTHORIZED: "This credential does not carry the capability this operation needs.",
|
|
10019
|
+
SCOPE_DENIED: "This agent's scope set does not allow this operation.",
|
|
10020
|
+
NOT_FOUND: "The target or message does not exist or is not visible to this agent.",
|
|
10021
|
+
CONFLICT: "The Raft Server refused the operation because of the current state.",
|
|
10022
|
+
IDEMPOTENCY_KEY_REUSED: "This idempotency key was already used for a different message.",
|
|
10023
|
+
UNSUPPORTED_FOR_EXTERNAL_AGENTS: "This operation is not available to External Agents.",
|
|
10024
|
+
UNAVAILABLE: "The Raft Server could not serve this operation right now."
|
|
9939
10025
|
};
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
10026
|
+
const DEFAULT_NEXT_ACTION = {
|
|
10027
|
+
INVALID_REQUEST: "Fix the request arguments; nothing was sent.",
|
|
10028
|
+
TRANSPORT_ERROR: "Check connectivity to the Raft Server and retry if the operation is safe to repeat.",
|
|
10029
|
+
HTTP_ERROR: "Read the Server's error code and adjust the request.",
|
|
10030
|
+
INVALID_RESPONSE: "Upgrade the SDK or report the Server version; the response shape is not the one this SDK knows.",
|
|
10031
|
+
CAPABILITY_NOT_AUTHORIZED: "Ask a human who can mint credentials to include the missing capability.",
|
|
10032
|
+
SCOPE_DENIED: "Ask a human with editAgents authority to extend this agent's scopes.",
|
|
10033
|
+
NOT_FOUND: "Check the target spelling with `raft server info --channels` or resolve the message id first.",
|
|
10034
|
+
CONFLICT: "Read the current state before repeating this operation.",
|
|
10035
|
+
IDEMPOTENCY_KEY_REUSED: "Use a new idempotency key for a different message, or resend the identical payload to reconcile.",
|
|
10036
|
+
UNSUPPORTED_FOR_EXTERNAL_AGENTS: "Use your own runtime for this; the Server does not provide it to External Agents.",
|
|
10037
|
+
UNAVAILABLE: "Retry in a moment."
|
|
10038
|
+
};
|
|
10039
|
+
const RETRYABLE = {
|
|
10040
|
+
INVALID_REQUEST: false,
|
|
10041
|
+
TRANSPORT_ERROR: true,
|
|
10042
|
+
HTTP_ERROR: false,
|
|
10043
|
+
INVALID_RESPONSE: false,
|
|
10044
|
+
CAPABILITY_NOT_AUTHORIZED: false,
|
|
10045
|
+
SCOPE_DENIED: false,
|
|
10046
|
+
NOT_FOUND: false,
|
|
10047
|
+
CONFLICT: false,
|
|
10048
|
+
IDEMPOTENCY_KEY_REUSED: false,
|
|
10049
|
+
UNSUPPORTED_FOR_EXTERNAL_AGENTS: false,
|
|
10050
|
+
UNAVAILABLE: true
|
|
10051
|
+
};
|
|
10052
|
+
function classifyHttp(status, serverCode) {
|
|
10053
|
+
const code = serverCode?.toLowerCase() ?? "";
|
|
10054
|
+
if (code === "idempotency_key_reused") return "IDEMPOTENCY_KEY_REUSED";
|
|
10055
|
+
if (code === "reminders_unsupported_for_external_agents") return "UNSUPPORTED_FOR_EXTERNAL_AGENTS";
|
|
10056
|
+
if (code === "capability_not_authorized") return "CAPABILITY_NOT_AUTHORIZED";
|
|
10057
|
+
if (code === "scope_denied") return "SCOPE_DENIED";
|
|
10058
|
+
if (status === 404) return "NOT_FOUND";
|
|
10059
|
+
if (status === 409) return "CONFLICT";
|
|
10060
|
+
if (status === 503) return "UNAVAILABLE";
|
|
10061
|
+
if (status >= 500) return "UNAVAILABLE";
|
|
10062
|
+
return "HTTP_ERROR";
|
|
10063
|
+
}
|
|
10064
|
+
function opErrorFromClientError(error, status) {
|
|
10065
|
+
if (error.kind === "transport") return {
|
|
10066
|
+
code: "TRANSPORT_ERROR",
|
|
10067
|
+
message: DEFAULT_MESSAGES.TRANSPORT_ERROR,
|
|
10068
|
+
nextAction: DEFAULT_NEXT_ACTION.TRANSPORT_ERROR,
|
|
10069
|
+
retryable: true
|
|
10070
|
+
};
|
|
10071
|
+
if (error.kind === "http") {
|
|
10072
|
+
const serverCode = typeof error.errorCode === "string" && SERVER_ERROR_CODE.test(error.errorCode) ? error.errorCode : void 0;
|
|
10073
|
+
const code = classifyHttp(error.status, serverCode);
|
|
10074
|
+
const nextAction = typeof error.suggestedNextAction === "string" && error.suggestedNextAction.trim() ? error.suggestedNextAction.trim() : DEFAULT_NEXT_ACTION[code];
|
|
10075
|
+
return {
|
|
10076
|
+
code,
|
|
10077
|
+
message: DEFAULT_MESSAGES[code],
|
|
10078
|
+
status: error.status,
|
|
10079
|
+
...serverCode ? { serverCode } : {},
|
|
10080
|
+
nextAction,
|
|
10081
|
+
retryable: RETRYABLE[code]
|
|
10082
|
+
};
|
|
9948
10083
|
}
|
|
9949
|
-
|
|
9950
|
-
|
|
9951
|
-
|
|
10084
|
+
const code = error.reason === "request_contract_mismatch" || error.reason === "missing_path_param" || error.reason === "missing_route" ? "INVALID_REQUEST" : "INVALID_RESPONSE";
|
|
10085
|
+
return {
|
|
10086
|
+
code,
|
|
10087
|
+
message: DEFAULT_MESSAGES[code],
|
|
10088
|
+
...status === void 0 ? {} : { status },
|
|
10089
|
+
nextAction: DEFAULT_NEXT_ACTION[code],
|
|
10090
|
+
retryable: false
|
|
10091
|
+
};
|
|
9952
10092
|
}
|
|
9953
|
-
function
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
10093
|
+
function opError(code, overrides = {}) {
|
|
10094
|
+
return {
|
|
10095
|
+
code,
|
|
10096
|
+
message: DEFAULT_MESSAGES[code],
|
|
10097
|
+
nextAction: DEFAULT_NEXT_ACTION[code],
|
|
10098
|
+
retryable: RETRYABLE[code],
|
|
10099
|
+
...overrides
|
|
10100
|
+
};
|
|
9958
10101
|
}
|
|
9959
|
-
/**
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
baseUrl: serverUrl,
|
|
9969
|
-
fetch: options.fetch,
|
|
9970
|
-
headers: options.headers,
|
|
9971
|
-
retry: options.retry,
|
|
9972
|
-
throttle: options.throttle,
|
|
9973
|
-
auth
|
|
9974
|
-
});
|
|
9975
|
-
const agentApi = createAgentApiClient({ fetch: {
|
|
9976
|
-
baseUrl: serverUrl,
|
|
9977
|
-
fetch: options.fetch,
|
|
9978
|
-
headers: options.headers,
|
|
9979
|
-
retry: { attempts: Math.min(Math.max(1, options.retry?.attempts ?? 1), 5) },
|
|
9980
|
-
throttle: options.throttle,
|
|
9981
|
-
auth
|
|
9982
|
-
} });
|
|
9983
|
-
const writeApi = createAgentApiClient({ fetch: {
|
|
9984
|
-
baseUrl: serverUrl,
|
|
9985
|
-
fetch: options.fetch,
|
|
9986
|
-
headers: options.headers,
|
|
9987
|
-
retry: { attempts: 1 },
|
|
9988
|
-
throttle: options.throttle,
|
|
9989
|
-
auth
|
|
9990
|
-
} });
|
|
9991
|
-
const eventsApi = createAgentApiClient({ fetch: {
|
|
9992
|
-
baseUrl: serverUrl,
|
|
9993
|
-
fetch: (input, init) => (options.fetch ?? fetch)(input, {
|
|
9994
|
-
...init,
|
|
9995
|
-
cache: "no-store",
|
|
9996
|
-
redirect: "error"
|
|
9997
|
-
}),
|
|
9998
|
-
headers: Object.fromEntries(new Headers(options.headers)),
|
|
9999
|
-
retry: { attempts: 1 },
|
|
10000
|
-
throttle: options.throttle,
|
|
10001
|
-
auth
|
|
10002
|
-
} });
|
|
10102
|
+
/** Render an error the way an agent should read it: what failed, the code, and the next action. */
|
|
10103
|
+
function formatOpErrorText(error) {
|
|
10104
|
+
const lines = [`Error: ${error.message}`, `Code: ${error.code}`];
|
|
10105
|
+
if (error.serverCode) lines.push(`Server code: ${error.serverCode}`);
|
|
10106
|
+
lines.push(`Retryable: ${error.retryable ? "yes" : "no"}`);
|
|
10107
|
+
lines.push(`Next action: ${error.nextAction}`);
|
|
10108
|
+
return lines.join("\n");
|
|
10109
|
+
}
|
|
10110
|
+
function failureOutcome(error) {
|
|
10003
10111
|
return {
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
events: { receive: (request) => receiveRaftEvents(eventsApi, request) },
|
|
10013
|
-
channels: { join: (request) => joinRaftChannelByTarget(agentApi, request) },
|
|
10014
|
-
routes: createRaftRoutes({
|
|
10015
|
-
serverUrl,
|
|
10016
|
-
fetch: options.fetch,
|
|
10017
|
-
headers: options.headers,
|
|
10018
|
-
authorization: auth.authorization,
|
|
10019
|
-
readAttempts: options.retry?.attempts,
|
|
10020
|
-
beforeRequest: options.throttle?.beforeRequest
|
|
10021
|
-
}),
|
|
10022
|
-
agent: { context: () => getRaftContext(agentApi) }
|
|
10112
|
+
ok: false,
|
|
10113
|
+
state: "error",
|
|
10114
|
+
error,
|
|
10115
|
+
next: {
|
|
10116
|
+
kind: "recover",
|
|
10117
|
+
why: error.nextAction
|
|
10118
|
+
},
|
|
10119
|
+
text: formatOpErrorText(error)
|
|
10023
10120
|
};
|
|
10024
10121
|
}
|
|
10122
|
+
/** Map a shared-client failure to a failure outcome. */
|
|
10123
|
+
function failureFromClientResult(result) {
|
|
10124
|
+
return failureOutcome(opErrorFromClientError(result.error, result.status));
|
|
10125
|
+
}
|
|
10025
10126
|
//#endregion
|
|
10026
|
-
//#region ../shared/src/
|
|
10027
|
-
|
|
10028
|
-
|
|
10127
|
+
//#region ../shared/src/agentInbox.ts
|
|
10128
|
+
const NON_MEMBER_MENTION_REPLY_GUIDANCE = "[Raft notice: You were notified as a non-member, so you cannot reply in that channel. If no reply is needed, no action is required. Otherwise, DM the person who mentioned you or join the channel to participate.]";
|
|
10129
|
+
function projectAgentReplyAffordance(facts) {
|
|
10130
|
+
if (facts.non_member_mention !== true) return null;
|
|
10131
|
+
return {
|
|
10132
|
+
kind: "non_member_mention",
|
|
10133
|
+
guidance: NON_MEMBER_MENTION_REPLY_GUIDANCE
|
|
10134
|
+
};
|
|
10029
10135
|
}
|
|
10030
|
-
function
|
|
10031
|
-
return
|
|
10136
|
+
function formatAgentReplyAffordance(facts) {
|
|
10137
|
+
return projectAgentReplyAffordance(facts)?.guidance ?? "";
|
|
10138
|
+
}
|
|
10139
|
+
function formatAgentReplyAffordanceSuffix(facts) {
|
|
10140
|
+
const guidance = formatAgentReplyAffordance(facts);
|
|
10141
|
+
return guidance ? `\n${guidance}` : "";
|
|
10032
10142
|
}
|
|
10033
10143
|
//#endregion
|
|
10034
|
-
//#region src/
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
function
|
|
10044
|
-
return
|
|
10144
|
+
//#region ../shared/src/raftRefs.ts
|
|
10145
|
+
const RAFT_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
|
|
10146
|
+
const RAFT_REF_USER_NAME_PATTERN = RAFT_REF_CHANNEL_NAME_PATTERN;
|
|
10147
|
+
const RAFT_REF_DM_PEER_PATTERN = String.raw`[\w.-]+(?:~(?:agent|human))?(?![\w.~-])`;
|
|
10148
|
+
const RAFT_REF_THREAD_SHORT_ID_PATTERN = String.raw`[\da-f]{6,8}`;
|
|
10149
|
+
const RAFT_REF_MESSAGE_ID_PATTERN = String.raw`[A-Za-z0-9][A-Za-z0-9-]{1,63}`;
|
|
10150
|
+
const RAFT_REF_TASK_NUMBER_PATTERN = String.raw`[1-9]\d*`;
|
|
10151
|
+
const RAFT_REF_COMPUTER_ID_PATTERN = String.raw`[\da-f]{8}-[\da-f]{4}-[1-5][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}`;
|
|
10152
|
+
const RAFT_REF_APP_ID_PATTERN = String.raw`[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+`;
|
|
10153
|
+
function createRaftUserRefRegex() {
|
|
10154
|
+
return new RegExp(`(^|[^\\p{L}\\p{N}_-])@(${RAFT_REF_USER_NAME_PATTERN})`, "gu");
|
|
10045
10155
|
}
|
|
10046
|
-
function
|
|
10047
|
-
|
|
10048
|
-
const input = value;
|
|
10049
|
-
if (!isNonEmptyString(input.agentId) || !isNonEmptyString(input.agentName) || input.agentDisplayName !== null && typeof input.agentDisplayName !== "string" || !isNonEmptyString(input.serverId) || !isNonEmptyString(input.credentialId) || !Array.isArray(input.scopes) || !input.scopes.every(isNonEmptyString)) return null;
|
|
10050
|
-
return {
|
|
10051
|
-
agentId: input.agentId,
|
|
10052
|
-
agentName: input.agentName,
|
|
10053
|
-
agentDisplayName: input.agentDisplayName,
|
|
10054
|
-
serverId: input.serverId,
|
|
10055
|
-
credentialId: input.credentialId,
|
|
10056
|
-
scopes: [...input.scopes]
|
|
10057
|
-
};
|
|
10156
|
+
function createRaftChannelThreadRefRegex() {
|
|
10157
|
+
return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})`, "giu");
|
|
10058
10158
|
}
|
|
10059
|
-
function
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
try {
|
|
10068
|
-
serverUrl = requireServerUrl(input.serverUrl);
|
|
10069
|
-
credential = requireAgentCredential(input.credential);
|
|
10070
|
-
} catch {
|
|
10071
|
-
return null;
|
|
10072
|
-
}
|
|
10073
|
-
return {
|
|
10074
|
-
schemaVersion: 1,
|
|
10075
|
-
serverUrl,
|
|
10076
|
-
credential,
|
|
10077
|
-
storedAt: input.storedAt,
|
|
10078
|
-
...identity
|
|
10079
|
-
};
|
|
10159
|
+
function createRaftChannelRefRegex() {
|
|
10160
|
+
return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN})`, "gu");
|
|
10161
|
+
}
|
|
10162
|
+
function createRaftDmThreadRefRegex() {
|
|
10163
|
+
return new RegExp(`dm:@(${RAFT_REF_DM_PEER_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})`, "giu");
|
|
10164
|
+
}
|
|
10165
|
+
function createRaftDmRefRegex() {
|
|
10166
|
+
return new RegExp(`dm:@(${RAFT_REF_DM_PEER_PATTERN})`, "giu");
|
|
10080
10167
|
}
|
|
10081
10168
|
/**
|
|
10082
|
-
*
|
|
10083
|
-
*
|
|
10084
|
-
*
|
|
10085
|
-
*
|
|
10169
|
+
* A `dm:@name~<suffix>` token whose suffix is not a known peer kind
|
|
10170
|
+
* (`~bot`, `~agentXYZ`, a bare `~`). Renderers claim it as plain text so it
|
|
10171
|
+
* never degrades into a link to a different target (`dm:@name` or `@name`).
|
|
10172
|
+
* Run after the valid DM passes; it also matches valid refs.
|
|
10086
10173
|
*/
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
|
|
10115
|
-
}
|
|
10116
|
-
let payload;
|
|
10117
|
-
try {
|
|
10118
|
-
payload = await response.json();
|
|
10119
|
-
} catch {
|
|
10120
|
-
throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
|
|
10121
|
-
}
|
|
10122
|
-
const identity = parseIdentityResponse(payload);
|
|
10123
|
-
if (!identity) throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
|
|
10124
|
-
if (!identity.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The Raft Agent credential does not include the send capability required by this SDK");
|
|
10125
|
-
if (existing && (existing.serverId !== identity.serverId || existing.agentId !== identity.agentId)) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store is already bound to a different Raft Agent identity");
|
|
10126
|
-
if (existing) return {
|
|
10127
|
-
serverUrl,
|
|
10128
|
-
...identity
|
|
10174
|
+
function createRaftMalformedDmKindRefRegex() {
|
|
10175
|
+
return new RegExp(String.raw`dm:@[\w.-]+~[\w.~-]*(?::${RAFT_REF_THREAD_SHORT_ID_PATTERN})?`, "giu");
|
|
10176
|
+
}
|
|
10177
|
+
function createRaftMessageRefRegex() {
|
|
10178
|
+
return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})`, "giu");
|
|
10179
|
+
}
|
|
10180
|
+
function createRaftBareTaskRefRegex() {
|
|
10181
|
+
return new RegExp(`(^|[^\\w/])(?:(task\\s+))?#(${RAFT_REF_TASK_NUMBER_PATTERN})\\b`, "giu");
|
|
10182
|
+
}
|
|
10183
|
+
const USER_RE = new RegExp(String.raw`^@(${RAFT_REF_USER_NAME_PATTERN})$`, "u");
|
|
10184
|
+
const COMPUTER_RE = new RegExp(String.raw`^computer:(${RAFT_REF_COMPUTER_ID_PATTERN})$`, "iu");
|
|
10185
|
+
const APP_RE = new RegExp(String.raw`^app:(${RAFT_REF_APP_ID_PATTERN})$`, "u");
|
|
10186
|
+
const CHANNEL_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN})$`, "u");
|
|
10187
|
+
const CHANNEL_THREAD_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})$`, "iu");
|
|
10188
|
+
const DM_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN})$`, "iu");
|
|
10189
|
+
const DM_THREAD_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})$`, "iu");
|
|
10190
|
+
const TASK_RE = new RegExp(String.raw`^task\s+#(${RAFT_REF_TASK_NUMBER_PATTERN})$`, "iu");
|
|
10191
|
+
const CHANNEL_MESSAGE_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})$`, "iu");
|
|
10192
|
+
const DM_MESSAGE_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})$`, "iu");
|
|
10193
|
+
function parseRaftRefTarget(rawTarget) {
|
|
10194
|
+
const target = rawTarget.trim();
|
|
10195
|
+
if (!target) return null;
|
|
10196
|
+
const channelMessage = CHANNEL_MESSAGE_RE.exec(target);
|
|
10197
|
+
if (channelMessage) return {
|
|
10198
|
+
kind: "message",
|
|
10199
|
+
channelName: channelMessage[1],
|
|
10200
|
+
threadParentShortId: channelMessage[2] ?? null,
|
|
10201
|
+
messageId: channelMessage[3]
|
|
10129
10202
|
};
|
|
10130
|
-
const
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10203
|
+
const dmMessage = DM_MESSAGE_RE.exec(target);
|
|
10204
|
+
if (dmMessage) return {
|
|
10205
|
+
kind: "dm-message",
|
|
10206
|
+
peerName: dmMessage[1],
|
|
10207
|
+
threadParentShortId: dmMessage[2] ?? null,
|
|
10208
|
+
messageId: dmMessage[3]
|
|
10136
10209
|
};
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
}
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10210
|
+
const channelThread = CHANNEL_THREAD_RE.exec(target);
|
|
10211
|
+
if (channelThread) return {
|
|
10212
|
+
kind: "channel-thread",
|
|
10213
|
+
channelName: channelThread[1],
|
|
10214
|
+
threadShortId: channelThread[2]
|
|
10215
|
+
};
|
|
10216
|
+
const dmThread = DM_THREAD_RE.exec(target);
|
|
10217
|
+
if (dmThread) return {
|
|
10218
|
+
kind: "dm-thread",
|
|
10219
|
+
peerName: dmThread[1],
|
|
10220
|
+
threadShortId: dmThread[2]
|
|
10221
|
+
};
|
|
10222
|
+
const task = TASK_RE.exec(target);
|
|
10223
|
+
if (task) return {
|
|
10224
|
+
kind: "task",
|
|
10225
|
+
taskNumber: Number(task[1])
|
|
10226
|
+
};
|
|
10227
|
+
const computer = COMPUTER_RE.exec(target);
|
|
10228
|
+
if (computer) return {
|
|
10229
|
+
kind: "computer",
|
|
10230
|
+
machineId: computer[1].toLowerCase()
|
|
10231
|
+
};
|
|
10232
|
+
const app = APP_RE.exec(target);
|
|
10233
|
+
if (app) return {
|
|
10234
|
+
kind: "app",
|
|
10235
|
+
appId: app[1]
|
|
10236
|
+
};
|
|
10237
|
+
const user = USER_RE.exec(target);
|
|
10238
|
+
if (user) return {
|
|
10239
|
+
kind: "user",
|
|
10240
|
+
name: user[1]
|
|
10241
|
+
};
|
|
10242
|
+
const channel = CHANNEL_RE.exec(target);
|
|
10243
|
+
if (channel) return {
|
|
10244
|
+
kind: "channel",
|
|
10245
|
+
channelName: channel[1]
|
|
10246
|
+
};
|
|
10247
|
+
const dm = DM_RE.exec(target);
|
|
10248
|
+
if (dm) return {
|
|
10249
|
+
kind: "dm",
|
|
10250
|
+
peerName: dm[1]
|
|
10146
10251
|
};
|
|
10252
|
+
return null;
|
|
10147
10253
|
}
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10254
|
+
function formatRaftRefTarget(target) {
|
|
10255
|
+
switch (target.kind) {
|
|
10256
|
+
case "user": return `@${target.name}`;
|
|
10257
|
+
case "computer": return `computer:${target.machineId}`;
|
|
10258
|
+
case "app": return `app:${target.appId}`;
|
|
10259
|
+
case "channel": return `#${target.channelName}`;
|
|
10260
|
+
case "channel-thread": return `#${target.channelName}:${target.threadShortId}`;
|
|
10261
|
+
case "dm": return `dm:@${target.peerName}`;
|
|
10262
|
+
case "dm-thread": return `dm:@${target.peerName}:${target.threadShortId}`;
|
|
10263
|
+
case "task": return `task #${target.taskNumber}`;
|
|
10264
|
+
case "message": return target.threadParentShortId ? `#${target.channelName}:${target.threadParentShortId} msg=${target.messageId}` : `#${target.channelName} msg=${target.messageId}`;
|
|
10265
|
+
case "dm-message": return target.threadParentShortId ? `dm:@${target.peerName}:${target.threadParentShortId} msg=${target.messageId}` : `dm:@${target.peerName} msg=${target.messageId}`;
|
|
10155
10266
|
}
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
credential: validated.credential,
|
|
10163
|
-
fetch: options.fetch,
|
|
10164
|
-
headers: options.headers,
|
|
10165
|
-
retry: options.retry,
|
|
10166
|
-
throttle: options.throttle
|
|
10267
|
+
}
|
|
10268
|
+
function markdownCodeSpans(source) {
|
|
10269
|
+
const spans = [];
|
|
10270
|
+
for (const match of source.matchAll(/```[\s\S]*?```/g)) spans.push({
|
|
10271
|
+
start: match.index ?? 0,
|
|
10272
|
+
end: (match.index ?? 0) + match[0].length
|
|
10167
10273
|
});
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
const
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
try {
|
|
10188
|
-
await mkdir(directory, {
|
|
10189
|
-
recursive: true,
|
|
10190
|
-
mode: 448
|
|
10191
|
-
});
|
|
10192
|
-
const existing = await loadFileCredential(filePath);
|
|
10193
|
-
if (existing) {
|
|
10194
|
-
assertSameCredential(existing, validated);
|
|
10195
|
-
return;
|
|
10196
|
-
}
|
|
10197
|
-
handle = await open(temporaryPath, "wx", 384);
|
|
10198
|
-
await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
|
|
10199
|
-
await handle.sync();
|
|
10200
|
-
await chmod(temporaryPath, 384);
|
|
10201
|
-
await handle.close();
|
|
10202
|
-
handle = void 0;
|
|
10203
|
-
try {
|
|
10204
|
-
await link(temporaryPath, filePath);
|
|
10205
|
-
} catch (error) {
|
|
10206
|
-
if (error.code !== "EEXIST") throw error;
|
|
10207
|
-
const winner = await loadFileCredential(filePath);
|
|
10208
|
-
if (!winner) throw error;
|
|
10209
|
-
assertSameCredential(winner, validated);
|
|
10210
|
-
}
|
|
10211
|
-
await unlink(temporaryPath);
|
|
10212
|
-
} catch (error) {
|
|
10213
|
-
if (handle) await handle.close().catch(() => void 0);
|
|
10214
|
-
await unlink(temporaryPath).catch(() => void 0);
|
|
10215
|
-
if (error instanceof RaftCredentialError) throw error;
|
|
10216
|
-
throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The Raft Agent credential could not be written atomically");
|
|
10217
|
-
}
|
|
10218
|
-
}
|
|
10219
|
-
};
|
|
10220
|
-
}
|
|
10221
|
-
async function loadFileCredential(filePath) {
|
|
10222
|
-
let initialMetadata;
|
|
10223
|
-
try {
|
|
10224
|
-
initialMetadata = await lstat(filePath);
|
|
10225
|
-
} catch (error) {
|
|
10226
|
-
if (error.code === "ENOENT") return null;
|
|
10227
|
-
throw fileReadError();
|
|
10228
|
-
}
|
|
10229
|
-
if (!initialMetadata.isFile() || initialMetadata.isSymbolicLink() || initialMetadata.size > MAX_CREDENTIAL_FILE_BYTES) throw fileReadError();
|
|
10230
|
-
let parsed;
|
|
10231
|
-
let handle;
|
|
10232
|
-
try {
|
|
10233
|
-
const noFollow = process.platform === "win32" ? 0 : constants.O_NOFOLLOW;
|
|
10234
|
-
handle = await open(filePath, constants.O_RDONLY | noFollow);
|
|
10235
|
-
const openedMetadata = await handle.stat();
|
|
10236
|
-
if (!openedMetadata.isFile() || openedMetadata.size > MAX_CREDENTIAL_FILE_BYTES || initialMetadata.dev !== openedMetadata.dev || initialMetadata.ino !== openedMetadata.ino) throw fileReadError();
|
|
10237
|
-
if (process.platform !== "win32" && (openedMetadata.mode & 63) !== 0) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file permissions are too broad; expected mode 0600");
|
|
10238
|
-
parsed = JSON.parse(await handle.readFile("utf8"));
|
|
10239
|
-
} catch (error) {
|
|
10240
|
-
if (error instanceof RaftCredentialError) throw error;
|
|
10241
|
-
throw fileReadError();
|
|
10242
|
-
} finally {
|
|
10243
|
-
await handle?.close().catch(() => void 0);
|
|
10244
|
-
}
|
|
10245
|
-
const record = parseStoredRaftCredential(parsed);
|
|
10246
|
-
if (!record) throw fileReadError();
|
|
10247
|
-
return record;
|
|
10248
|
-
}
|
|
10249
|
-
function assertSameCredential(existing, candidate) {
|
|
10250
|
-
if (existing.credential !== candidate.credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
|
|
10251
|
-
}
|
|
10252
|
-
function fileReadError() {
|
|
10253
|
-
return new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file is invalid or unreadable");
|
|
10254
|
-
}
|
|
10255
|
-
//#endregion
|
|
10256
|
-
//#region ../shared/src/agentOps/outcome.ts
|
|
10257
|
-
const SERVER_ERROR_CODE = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
10258
|
-
const DEFAULT_MESSAGES = {
|
|
10259
|
-
INVALID_REQUEST: "The request did not match the Agent API contract; nothing was sent.",
|
|
10260
|
-
TRANSPORT_ERROR: "The request did not reach the Raft Server.",
|
|
10261
|
-
HTTP_ERROR: "The Raft Server rejected the request.",
|
|
10262
|
-
INVALID_RESPONSE: "The Raft Server response did not match the Agent API contract.",
|
|
10263
|
-
CAPABILITY_NOT_AUTHORIZED: "This credential does not carry the capability this operation needs.",
|
|
10264
|
-
SCOPE_DENIED: "This agent's scope set does not allow this operation.",
|
|
10265
|
-
NOT_FOUND: "The target or message does not exist or is not visible to this agent.",
|
|
10266
|
-
CONFLICT: "The Raft Server refused the operation because of the current state.",
|
|
10267
|
-
IDEMPOTENCY_KEY_REUSED: "This idempotency key was already used for a different message.",
|
|
10268
|
-
UNSUPPORTED_FOR_EXTERNAL_AGENTS: "This operation is not available to External Agents.",
|
|
10269
|
-
UNAVAILABLE: "The Raft Server could not serve this operation right now."
|
|
10270
|
-
};
|
|
10271
|
-
const DEFAULT_NEXT_ACTION = {
|
|
10272
|
-
INVALID_REQUEST: "Fix the request arguments; nothing was sent.",
|
|
10273
|
-
TRANSPORT_ERROR: "Check connectivity to the Raft Server and retry if the operation is safe to repeat.",
|
|
10274
|
-
HTTP_ERROR: "Read the Server's error code and adjust the request.",
|
|
10275
|
-
INVALID_RESPONSE: "Upgrade the SDK or report the Server version; the response shape is not the one this SDK knows.",
|
|
10276
|
-
CAPABILITY_NOT_AUTHORIZED: "Ask a human who can mint credentials to include the missing capability.",
|
|
10277
|
-
SCOPE_DENIED: "Ask a human with editAgents authority to extend this agent's scopes.",
|
|
10278
|
-
NOT_FOUND: "Check the target spelling with `raft server info --channels` or resolve the message id first.",
|
|
10279
|
-
CONFLICT: "Read the current state before repeating this operation.",
|
|
10280
|
-
IDEMPOTENCY_KEY_REUSED: "Use a new idempotency key for a different message, or resend the identical payload to reconcile.",
|
|
10281
|
-
UNSUPPORTED_FOR_EXTERNAL_AGENTS: "Use your own runtime for this; the Server does not provide it to External Agents.",
|
|
10282
|
-
UNAVAILABLE: "Retry in a moment."
|
|
10283
|
-
};
|
|
10284
|
-
const RETRYABLE = {
|
|
10285
|
-
INVALID_REQUEST: false,
|
|
10286
|
-
TRANSPORT_ERROR: true,
|
|
10287
|
-
HTTP_ERROR: false,
|
|
10288
|
-
INVALID_RESPONSE: false,
|
|
10289
|
-
CAPABILITY_NOT_AUTHORIZED: false,
|
|
10290
|
-
SCOPE_DENIED: false,
|
|
10291
|
-
NOT_FOUND: false,
|
|
10292
|
-
CONFLICT: false,
|
|
10293
|
-
IDEMPOTENCY_KEY_REUSED: false,
|
|
10294
|
-
UNSUPPORTED_FOR_EXTERNAL_AGENTS: false,
|
|
10295
|
-
UNAVAILABLE: true
|
|
10296
|
-
};
|
|
10297
|
-
function classifyHttp(status, serverCode) {
|
|
10298
|
-
const code = serverCode?.toLowerCase() ?? "";
|
|
10299
|
-
if (code === "idempotency_key_reused") return "IDEMPOTENCY_KEY_REUSED";
|
|
10300
|
-
if (code === "reminders_unsupported_for_external_agents") return "UNSUPPORTED_FOR_EXTERNAL_AGENTS";
|
|
10301
|
-
if (code === "capability_not_authorized") return "CAPABILITY_NOT_AUTHORIZED";
|
|
10302
|
-
if (code === "scope_denied") return "SCOPE_DENIED";
|
|
10303
|
-
if (status === 404) return "NOT_FOUND";
|
|
10304
|
-
if (status === 409) return "CONFLICT";
|
|
10305
|
-
if (status === 503) return "UNAVAILABLE";
|
|
10306
|
-
if (status >= 500) return "UNAVAILABLE";
|
|
10307
|
-
return "HTTP_ERROR";
|
|
10308
|
-
}
|
|
10309
|
-
function opErrorFromClientError(error, status) {
|
|
10310
|
-
if (error.kind === "transport") return {
|
|
10311
|
-
code: "TRANSPORT_ERROR",
|
|
10312
|
-
message: DEFAULT_MESSAGES.TRANSPORT_ERROR,
|
|
10313
|
-
nextAction: DEFAULT_NEXT_ACTION.TRANSPORT_ERROR,
|
|
10314
|
-
retryable: true
|
|
10315
|
-
};
|
|
10316
|
-
if (error.kind === "http") {
|
|
10317
|
-
const serverCode = typeof error.errorCode === "string" && SERVER_ERROR_CODE.test(error.errorCode) ? error.errorCode : void 0;
|
|
10318
|
-
const code = classifyHttp(error.status, serverCode);
|
|
10319
|
-
const nextAction = typeof error.suggestedNextAction === "string" && error.suggestedNextAction.trim() ? error.suggestedNextAction.trim() : DEFAULT_NEXT_ACTION[code];
|
|
10320
|
-
return {
|
|
10321
|
-
code,
|
|
10322
|
-
message: DEFAULT_MESSAGES[code],
|
|
10323
|
-
status: error.status,
|
|
10324
|
-
...serverCode ? { serverCode } : {},
|
|
10325
|
-
nextAction,
|
|
10326
|
-
retryable: RETRYABLE[code]
|
|
10327
|
-
};
|
|
10328
|
-
}
|
|
10329
|
-
const code = error.reason === "request_contract_mismatch" || error.reason === "missing_path_param" || error.reason === "missing_route" ? "INVALID_REQUEST" : "INVALID_RESPONSE";
|
|
10330
|
-
return {
|
|
10331
|
-
code,
|
|
10332
|
-
message: DEFAULT_MESSAGES[code],
|
|
10333
|
-
...status === void 0 ? {} : { status },
|
|
10334
|
-
nextAction: DEFAULT_NEXT_ACTION[code],
|
|
10335
|
-
retryable: false
|
|
10336
|
-
};
|
|
10337
|
-
}
|
|
10338
|
-
function opError(code, overrides = {}) {
|
|
10339
|
-
return {
|
|
10340
|
-
code,
|
|
10341
|
-
message: DEFAULT_MESSAGES[code],
|
|
10342
|
-
nextAction: DEFAULT_NEXT_ACTION[code],
|
|
10343
|
-
retryable: RETRYABLE[code],
|
|
10344
|
-
...overrides
|
|
10345
|
-
};
|
|
10346
|
-
}
|
|
10347
|
-
/** Render an error the way an agent should read it: what failed, the code, and the next action. */
|
|
10348
|
-
function formatOpErrorText(error) {
|
|
10349
|
-
const lines = [`Error: ${error.message}`, `Code: ${error.code}`];
|
|
10350
|
-
if (error.serverCode) lines.push(`Server code: ${error.serverCode}`);
|
|
10351
|
-
lines.push(`Retryable: ${error.retryable ? "yes" : "no"}`);
|
|
10352
|
-
lines.push(`Next action: ${error.nextAction}`);
|
|
10353
|
-
return lines.join("\n");
|
|
10354
|
-
}
|
|
10355
|
-
function failureOutcome(error) {
|
|
10356
|
-
return {
|
|
10357
|
-
ok: false,
|
|
10358
|
-
state: "error",
|
|
10359
|
-
error,
|
|
10360
|
-
next: {
|
|
10361
|
-
kind: "recover",
|
|
10362
|
-
why: error.nextAction
|
|
10363
|
-
},
|
|
10364
|
-
text: formatOpErrorText(error)
|
|
10365
|
-
};
|
|
10366
|
-
}
|
|
10367
|
-
/** Map a shared-client failure to a failure outcome. */
|
|
10368
|
-
function failureFromClientResult(result) {
|
|
10369
|
-
return failureOutcome(opErrorFromClientError(result.error, result.status));
|
|
10370
|
-
}
|
|
10371
|
-
//#endregion
|
|
10372
|
-
//#region ../shared/src/agentInbox.ts
|
|
10373
|
-
const NON_MEMBER_MENTION_REPLY_GUIDANCE = "[Raft notice: You were notified as a non-member, so you cannot reply in that channel. If no reply is needed, no action is required. Otherwise, DM the person who mentioned you or join the channel to participate.]";
|
|
10374
|
-
function projectAgentReplyAffordance(facts) {
|
|
10375
|
-
if (facts.non_member_mention !== true) return null;
|
|
10376
|
-
return {
|
|
10377
|
-
kind: "non_member_mention",
|
|
10378
|
-
guidance: NON_MEMBER_MENTION_REPLY_GUIDANCE
|
|
10379
|
-
};
|
|
10380
|
-
}
|
|
10381
|
-
function formatAgentReplyAffordance(facts) {
|
|
10382
|
-
return projectAgentReplyAffordance(facts)?.guidance ?? "";
|
|
10383
|
-
}
|
|
10384
|
-
function formatAgentReplyAffordanceSuffix(facts) {
|
|
10385
|
-
const guidance = formatAgentReplyAffordance(facts);
|
|
10386
|
-
return guidance ? `\n${guidance}` : "";
|
|
10387
|
-
}
|
|
10388
|
-
//#endregion
|
|
10389
|
-
//#region ../shared/src/raftRefs.ts
|
|
10390
|
-
const RAFT_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
|
|
10391
|
-
const RAFT_REF_USER_NAME_PATTERN = RAFT_REF_CHANNEL_NAME_PATTERN;
|
|
10392
|
-
const RAFT_REF_DM_PEER_PATTERN = String.raw`[\w.-]+(?:~(?:agent|human))?(?![\w.~-])`;
|
|
10393
|
-
const RAFT_REF_THREAD_SHORT_ID_PATTERN = String.raw`[\da-f]{6,8}`;
|
|
10394
|
-
const RAFT_REF_MESSAGE_ID_PATTERN = String.raw`[A-Za-z0-9][A-Za-z0-9-]{1,63}`;
|
|
10395
|
-
const RAFT_REF_TASK_NUMBER_PATTERN = String.raw`[1-9]\d*`;
|
|
10396
|
-
const RAFT_REF_COMPUTER_ID_PATTERN = String.raw`[\da-f]{8}-[\da-f]{4}-[1-5][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}`;
|
|
10397
|
-
const RAFT_REF_APP_ID_PATTERN = String.raw`[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+`;
|
|
10398
|
-
function createRaftUserRefRegex() {
|
|
10399
|
-
return new RegExp(`(^|[^\\p{L}\\p{N}_-])@(${RAFT_REF_USER_NAME_PATTERN})`, "gu");
|
|
10400
|
-
}
|
|
10401
|
-
function createRaftChannelThreadRefRegex() {
|
|
10402
|
-
return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})`, "giu");
|
|
10403
|
-
}
|
|
10404
|
-
function createRaftChannelRefRegex() {
|
|
10405
|
-
return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN})`, "gu");
|
|
10406
|
-
}
|
|
10407
|
-
function createRaftDmThreadRefRegex() {
|
|
10408
|
-
return new RegExp(`dm:@(${RAFT_REF_DM_PEER_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})`, "giu");
|
|
10409
|
-
}
|
|
10410
|
-
function createRaftDmRefRegex() {
|
|
10411
|
-
return new RegExp(`dm:@(${RAFT_REF_DM_PEER_PATTERN})`, "giu");
|
|
10412
|
-
}
|
|
10413
|
-
/**
|
|
10414
|
-
* A `dm:@name~<suffix>` token whose suffix is not a known peer kind
|
|
10415
|
-
* (`~bot`, `~agentXYZ`, a bare `~`). Renderers claim it as plain text so it
|
|
10416
|
-
* never degrades into a link to a different target (`dm:@name` or `@name`).
|
|
10417
|
-
* Run after the valid DM passes; it also matches valid refs.
|
|
10418
|
-
*/
|
|
10419
|
-
function createRaftMalformedDmKindRefRegex() {
|
|
10420
|
-
return new RegExp(String.raw`dm:@[\w.-]+~[\w.~-]*(?::${RAFT_REF_THREAD_SHORT_ID_PATTERN})?`, "giu");
|
|
10421
|
-
}
|
|
10422
|
-
function createRaftMessageRefRegex() {
|
|
10423
|
-
return new RegExp(`#(${RAFT_REF_CHANNEL_NAME_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})`, "giu");
|
|
10424
|
-
}
|
|
10425
|
-
function createRaftBareTaskRefRegex() {
|
|
10426
|
-
return new RegExp(`(^|[^\\w/])(?:(task\\s+))?#(${RAFT_REF_TASK_NUMBER_PATTERN})\\b`, "giu");
|
|
10427
|
-
}
|
|
10428
|
-
const USER_RE = new RegExp(String.raw`^@(${RAFT_REF_USER_NAME_PATTERN})$`, "u");
|
|
10429
|
-
const COMPUTER_RE = new RegExp(String.raw`^computer:(${RAFT_REF_COMPUTER_ID_PATTERN})$`, "iu");
|
|
10430
|
-
const APP_RE = new RegExp(String.raw`^app:(${RAFT_REF_APP_ID_PATTERN})$`, "u");
|
|
10431
|
-
const CHANNEL_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN})$`, "u");
|
|
10432
|
-
const CHANNEL_THREAD_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})$`, "iu");
|
|
10433
|
-
const DM_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN})$`, "iu");
|
|
10434
|
-
const DM_THREAD_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN}):(${RAFT_REF_THREAD_SHORT_ID_PATTERN})$`, "iu");
|
|
10435
|
-
const TASK_RE = new RegExp(String.raw`^task\s+#(${RAFT_REF_TASK_NUMBER_PATTERN})$`, "iu");
|
|
10436
|
-
const CHANNEL_MESSAGE_RE = new RegExp(String.raw`^#(${RAFT_REF_CHANNEL_NAME_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})$`, "iu");
|
|
10437
|
-
const DM_MESSAGE_RE = new RegExp(String.raw`^dm:@(${RAFT_REF_DM_PEER_PATTERN})(?::(${RAFT_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${RAFT_REF_MESSAGE_ID_PATTERN})$`, "iu");
|
|
10438
|
-
function parseRaftRefTarget(rawTarget) {
|
|
10439
|
-
const target = rawTarget.trim();
|
|
10440
|
-
if (!target) return null;
|
|
10441
|
-
const channelMessage = CHANNEL_MESSAGE_RE.exec(target);
|
|
10442
|
-
if (channelMessage) return {
|
|
10443
|
-
kind: "message",
|
|
10444
|
-
channelName: channelMessage[1],
|
|
10445
|
-
threadParentShortId: channelMessage[2] ?? null,
|
|
10446
|
-
messageId: channelMessage[3]
|
|
10447
|
-
};
|
|
10448
|
-
const dmMessage = DM_MESSAGE_RE.exec(target);
|
|
10449
|
-
if (dmMessage) return {
|
|
10450
|
-
kind: "dm-message",
|
|
10451
|
-
peerName: dmMessage[1],
|
|
10452
|
-
threadParentShortId: dmMessage[2] ?? null,
|
|
10453
|
-
messageId: dmMessage[3]
|
|
10454
|
-
};
|
|
10455
|
-
const channelThread = CHANNEL_THREAD_RE.exec(target);
|
|
10456
|
-
if (channelThread) return {
|
|
10457
|
-
kind: "channel-thread",
|
|
10458
|
-
channelName: channelThread[1],
|
|
10459
|
-
threadShortId: channelThread[2]
|
|
10460
|
-
};
|
|
10461
|
-
const dmThread = DM_THREAD_RE.exec(target);
|
|
10462
|
-
if (dmThread) return {
|
|
10463
|
-
kind: "dm-thread",
|
|
10464
|
-
peerName: dmThread[1],
|
|
10465
|
-
threadShortId: dmThread[2]
|
|
10466
|
-
};
|
|
10467
|
-
const task = TASK_RE.exec(target);
|
|
10468
|
-
if (task) return {
|
|
10469
|
-
kind: "task",
|
|
10470
|
-
taskNumber: Number(task[1])
|
|
10471
|
-
};
|
|
10472
|
-
const computer = COMPUTER_RE.exec(target);
|
|
10473
|
-
if (computer) return {
|
|
10474
|
-
kind: "computer",
|
|
10475
|
-
machineId: computer[1].toLowerCase()
|
|
10476
|
-
};
|
|
10477
|
-
const app = APP_RE.exec(target);
|
|
10478
|
-
if (app) return {
|
|
10479
|
-
kind: "app",
|
|
10480
|
-
appId: app[1]
|
|
10481
|
-
};
|
|
10482
|
-
const user = USER_RE.exec(target);
|
|
10483
|
-
if (user) return {
|
|
10484
|
-
kind: "user",
|
|
10485
|
-
name: user[1]
|
|
10486
|
-
};
|
|
10487
|
-
const channel = CHANNEL_RE.exec(target);
|
|
10488
|
-
if (channel) return {
|
|
10489
|
-
kind: "channel",
|
|
10490
|
-
channelName: channel[1]
|
|
10491
|
-
};
|
|
10492
|
-
const dm = DM_RE.exec(target);
|
|
10493
|
-
if (dm) return {
|
|
10494
|
-
kind: "dm",
|
|
10495
|
-
peerName: dm[1]
|
|
10496
|
-
};
|
|
10497
|
-
return null;
|
|
10498
|
-
}
|
|
10499
|
-
function formatRaftRefTarget(target) {
|
|
10500
|
-
switch (target.kind) {
|
|
10501
|
-
case "user": return `@${target.name}`;
|
|
10502
|
-
case "computer": return `computer:${target.machineId}`;
|
|
10503
|
-
case "app": return `app:${target.appId}`;
|
|
10504
|
-
case "channel": return `#${target.channelName}`;
|
|
10505
|
-
case "channel-thread": return `#${target.channelName}:${target.threadShortId}`;
|
|
10506
|
-
case "dm": return `dm:@${target.peerName}`;
|
|
10507
|
-
case "dm-thread": return `dm:@${target.peerName}:${target.threadShortId}`;
|
|
10508
|
-
case "task": return `task #${target.taskNumber}`;
|
|
10509
|
-
case "message": return target.threadParentShortId ? `#${target.channelName}:${target.threadParentShortId} msg=${target.messageId}` : `#${target.channelName} msg=${target.messageId}`;
|
|
10510
|
-
case "dm-message": return target.threadParentShortId ? `dm:@${target.peerName}:${target.threadParentShortId} msg=${target.messageId}` : `dm:@${target.peerName} msg=${target.messageId}`;
|
|
10511
|
-
}
|
|
10512
|
-
}
|
|
10513
|
-
function markdownCodeSpans(source) {
|
|
10514
|
-
const spans = [];
|
|
10515
|
-
for (const match of source.matchAll(/```[\s\S]*?```/g)) spans.push({
|
|
10516
|
-
start: match.index ?? 0,
|
|
10517
|
-
end: (match.index ?? 0) + match[0].length
|
|
10518
|
-
});
|
|
10519
|
-
const fences = [...spans];
|
|
10520
|
-
const scanInline = (from, to) => {
|
|
10521
|
-
if (from >= to) return;
|
|
10522
|
-
const segment = source.slice(from, to);
|
|
10523
|
-
for (const match of segment.matchAll(/``[^`]+``|`[^`]+`/g)) {
|
|
10524
|
-
const start = from + (match.index ?? 0);
|
|
10525
|
-
spans.push({
|
|
10526
|
-
start,
|
|
10527
|
-
end: start + match[0].length
|
|
10528
|
-
});
|
|
10529
|
-
}
|
|
10530
|
-
};
|
|
10531
|
-
let cursor = 0;
|
|
10532
|
-
for (const fence of fences) {
|
|
10533
|
-
scanInline(cursor, fence.start);
|
|
10534
|
-
cursor = fence.end;
|
|
10535
|
-
}
|
|
10536
|
-
scanInline(cursor, source.length);
|
|
10537
|
-
return spans.sort((a, b) => a.start - b.start);
|
|
10274
|
+
const fences = [...spans];
|
|
10275
|
+
const scanInline = (from, to) => {
|
|
10276
|
+
if (from >= to) return;
|
|
10277
|
+
const segment = source.slice(from, to);
|
|
10278
|
+
for (const match of segment.matchAll(/``[^`]+``|`[^`]+`/g)) {
|
|
10279
|
+
const start = from + (match.index ?? 0);
|
|
10280
|
+
spans.push({
|
|
10281
|
+
start,
|
|
10282
|
+
end: start + match[0].length
|
|
10283
|
+
});
|
|
10284
|
+
}
|
|
10285
|
+
};
|
|
10286
|
+
let cursor = 0;
|
|
10287
|
+
for (const fence of fences) {
|
|
10288
|
+
scanInline(cursor, fence.start);
|
|
10289
|
+
cursor = fence.end;
|
|
10290
|
+
}
|
|
10291
|
+
scanInline(cursor, source.length);
|
|
10292
|
+
return spans.sort((a, b) => a.start - b.start);
|
|
10538
10293
|
}
|
|
10539
10294
|
function visitMarkdownProseChunks(source, visitor) {
|
|
10540
10295
|
const spans = markdownCodeSpans(source);
|
|
@@ -13458,532 +13213,872 @@ async function resolveMessage(client, request) {
|
|
|
13458
13213
|
text: formatAgentMessages([toAgentMessageLike(message.raw)])
|
|
13459
13214
|
};
|
|
13460
13215
|
}
|
|
13461
|
-
async function reactToMessage(client, request, action = "add") {
|
|
13462
|
-
const emoji = request.emoji?.trim();
|
|
13463
|
-
if (!request.messageId?.trim() || !emoji || /\s/.test(emoji) || emoji.length > 16) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id and a single reaction emoji are required." }));
|
|
13464
|
-
const params = { msgId: request.messageId };
|
|
13465
|
-
const result = action === "add" ? await client.messages.addReaction(params, { emoji }) : await client.messages.removeReaction(params, { emoji });
|
|
13216
|
+
async function reactToMessage(client, request, action = "add") {
|
|
13217
|
+
const emoji = request.emoji?.trim();
|
|
13218
|
+
if (!request.messageId?.trim() || !emoji || /\s/.test(emoji) || emoji.length > 16) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id and a single reaction emoji are required." }));
|
|
13219
|
+
const params = { msgId: request.messageId };
|
|
13220
|
+
const result = action === "add" ? await client.messages.addReaction(params, { emoji }) : await client.messages.removeReaction(params, { emoji });
|
|
13221
|
+
if (!result.ok) return failureFromClientResult(result);
|
|
13222
|
+
const verb = action === "add" ? "added to" : "removed from";
|
|
13223
|
+
return {
|
|
13224
|
+
ok: true,
|
|
13225
|
+
state: action === "add" ? "added" : "removed",
|
|
13226
|
+
data: {
|
|
13227
|
+
messageId: request.messageId,
|
|
13228
|
+
emoji
|
|
13229
|
+
},
|
|
13230
|
+
next: null,
|
|
13231
|
+
text: `Reaction ${emoji} ${verb} message ${request.messageId.slice(0, 8)}.`
|
|
13232
|
+
};
|
|
13233
|
+
}
|
|
13234
|
+
//#endregion
|
|
13235
|
+
//#region ../shared/src/agentText/mentions.ts
|
|
13236
|
+
function normalizeAgentMentionAction(action) {
|
|
13237
|
+
if (action === "notify" || action === "notify_only") return "notify";
|
|
13238
|
+
if (action === "add" || action === "invite") return "add";
|
|
13239
|
+
return null;
|
|
13240
|
+
}
|
|
13241
|
+
function formatActionCommands(action) {
|
|
13242
|
+
const verbs = action.availableActions.map(normalizeAgentMentionAction).filter((verb) => verb !== null);
|
|
13243
|
+
return Array.from(new Set(verbs)).map((verb) => ` ${verb}: raft mention ${verb} ${action.resolutionId}`);
|
|
13244
|
+
}
|
|
13245
|
+
function formatAuthoredMentionToken(targetHandle) {
|
|
13246
|
+
return targetHandle.startsWith("@") ? targetHandle : `@${targetHandle}`;
|
|
13247
|
+
}
|
|
13248
|
+
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;
|
|
13249
|
+
/** Per-token mention recovery command line; null when the id is not a UUID (fail closed). */
|
|
13250
|
+
function formatAgentMentionNotifyRecoveryCommand(resolutionId) {
|
|
13251
|
+
return PENDING_MENTION_ACTION_ID_RE.test(resolutionId) ? `raft mention notify ${resolutionId}` : null;
|
|
13252
|
+
}
|
|
13253
|
+
function toAgentSenderPendingMentionAction(action) {
|
|
13254
|
+
return {
|
|
13255
|
+
resolutionId: action.resolutionId,
|
|
13256
|
+
messageId: action.messageId,
|
|
13257
|
+
targetHandle: formatAuthoredMentionToken(action.targetHandle),
|
|
13258
|
+
status: "not_queued",
|
|
13259
|
+
reason: "not_in_conversation",
|
|
13260
|
+
consequence: "This @mention did not notify anyone.",
|
|
13261
|
+
expiresAt: action.expiresAt ?? null,
|
|
13262
|
+
recoveryCommand: formatAgentMentionNotifyRecoveryCommand(action.resolutionId)
|
|
13263
|
+
};
|
|
13264
|
+
}
|
|
13265
|
+
function toAgentSenderUnresolvedMentionWarning(targetHandle) {
|
|
13266
|
+
return {
|
|
13267
|
+
targetHandle: formatAuthoredMentionToken(targetHandle),
|
|
13268
|
+
status: "not_queued",
|
|
13269
|
+
reason: "unknown_or_not_visible",
|
|
13270
|
+
consequence: "This @mention did not notify anyone.",
|
|
13271
|
+
expiresAt: null,
|
|
13272
|
+
recoveryCommand: null
|
|
13273
|
+
};
|
|
13274
|
+
}
|
|
13275
|
+
function formatPendingReason(reason) {
|
|
13276
|
+
if (reason === "not_member") return "not in the conversation at send time, so the @mention was not delivered";
|
|
13277
|
+
return reason;
|
|
13278
|
+
}
|
|
13279
|
+
function normalizeAgentPendingMentionActions(data) {
|
|
13280
|
+
const value = data;
|
|
13281
|
+
return (Array.isArray(value?.pendingMentionActions) ? value.pendingMentionActions : Array.isArray(value?.actions) ? value.actions : Array.isArray(value?.results) ? value.results : []).filter((item) => Boolean(item && typeof item === "object")).map((item) => ({
|
|
13282
|
+
resolutionId: String(item.resolutionId ?? item.id ?? ""),
|
|
13283
|
+
messageId: String(item.messageId ?? ""),
|
|
13284
|
+
targetType: String(item.targetType ?? "unknown"),
|
|
13285
|
+
targetHandle: String(item.targetHandle ?? ""),
|
|
13286
|
+
reason: String(item.reason ?? "Mention target was not notified at send time."),
|
|
13287
|
+
availableActions: Array.isArray(item.availableActions) ? item.availableActions.map(String) : [],
|
|
13288
|
+
expiresAt: typeof item.expiresAt === "string" ? item.expiresAt : null
|
|
13289
|
+
})).filter((item) => item.resolutionId.length > 0);
|
|
13290
|
+
}
|
|
13291
|
+
function normalizeAgentMentionActionResults(data) {
|
|
13292
|
+
const value = data;
|
|
13293
|
+
return (Array.isArray(value?.results) ? value.results : Array.isArray(value?.actionResults) ? value.actionResults : []).filter((item) => Boolean(item && typeof item === "object")).map((item) => ({
|
|
13294
|
+
resolutionId: String(item.resolutionId ?? item.id ?? ""),
|
|
13295
|
+
status: String(item.status ?? "not_found"),
|
|
13296
|
+
action: typeof item.action === "string" ? normalizeAgentMentionAction(item.action) : null,
|
|
13297
|
+
messageId: typeof item.messageId === "string" ? item.messageId : null,
|
|
13298
|
+
channelId: typeof item.channelId === "string" ? item.channelId : null,
|
|
13299
|
+
targetType: typeof item.targetType === "string" ? item.targetType : null,
|
|
13300
|
+
targetId: typeof item.targetId === "string" ? item.targetId : null,
|
|
13301
|
+
targetHandle: typeof item.targetHandle === "string" ? item.targetHandle : null,
|
|
13302
|
+
message: typeof item.message === "string" ? item.message : null,
|
|
13303
|
+
reason: typeof item.reason === "string" ? item.reason : null,
|
|
13304
|
+
dedupedResolutionIds: Array.isArray(item.dedupedResolutionIds) ? item.dedupedResolutionIds.filter((id) => typeof id === "string" && id.length > 0) : []
|
|
13305
|
+
})).filter((item) => item.resolutionId.length > 0);
|
|
13306
|
+
}
|
|
13307
|
+
const PENDING_DEFAULT_LIMIT = 50;
|
|
13308
|
+
function pendingPageVerdict(hasMore, limit, shown) {
|
|
13309
|
+
const asked = limit === void 0 ? "server default 50" : `--limit ${limit}`;
|
|
13310
|
+
if (hasMore === true) return (limit ?? PENDING_DEFAULT_LIMIT) >= 100 ? `shown ${shown}, ${asked} — truncated=true · more pending actions exist AND this is the server's maximum page (100); this route has no offset or cursor, so the rest cannot be reached from here — resolve what you can and re-run; rows with no available actions are not actionable here and will remain until they expire` : `shown ${shown}, ${asked} — truncated=true · more pending actions exist; raise --limit (server caps at 100) or resolve these first and re-run`;
|
|
13311
|
+
if (hasMore === false) return `shown ${shown}, ${asked} — truncated=false · this is the complete list`;
|
|
13312
|
+
return `shown ${shown}, ${asked} — truncated=unknown · server did not report has_more, so completeness is NOT asserted`;
|
|
13313
|
+
}
|
|
13314
|
+
/** Undelivered-mentions partial result (send) or the pending list. */
|
|
13315
|
+
function formatAgentPendingMentionActions(actions, opts = {}) {
|
|
13316
|
+
const unresolvedMentionHandles = opts.source === "send" ? Array.from(new Set(opts.unresolvedMentionHandles ?? [])) : [];
|
|
13317
|
+
const pageNote = opts.source === "pending" ? pendingPageVerdict(opts.hasMore, opts.limit, actions.length) : "";
|
|
13318
|
+
if (actions.length === 0 && unresolvedMentionHandles.length === 0) return opts.source === "pending" ? `Pending mention actions\n\nNo pending mention actions. (${pageNote})\n` : "";
|
|
13319
|
+
if (opts.source === "send") {
|
|
13320
|
+
const lines = [
|
|
13321
|
+
"Undelivered mentions — partial result",
|
|
13322
|
+
"Message effect: status=queued. Queue acceptance is the only message proof.",
|
|
13323
|
+
"Do not rerun `raft message send`; the message is already queued and a retry could duplicate it.",
|
|
13324
|
+
"Each row below is bound to the literal @token from your message.",
|
|
13325
|
+
"For a literal name rather than a recipient, wrap the @handle in inline or fenced code.",
|
|
13326
|
+
""
|
|
13327
|
+
];
|
|
13328
|
+
for (const rawAction of actions) {
|
|
13329
|
+
const action = toAgentSenderPendingMentionAction(rawAction);
|
|
13330
|
+
lines.push(`- ${action.targetHandle} — status=${action.status}`);
|
|
13331
|
+
lines.push(` reason: ${action.reason}`);
|
|
13332
|
+
lines.push(` consequence: ${action.consequence}`);
|
|
13333
|
+
lines.push(` pending action: ${action.recoveryCommand ? action.resolutionId : "[invalid pending action id]"}`);
|
|
13334
|
+
if (action.messageId) lines.push(` message: ${action.messageId}`);
|
|
13335
|
+
lines.push(` expires: ${action.expiresAt ?? "unknown"}`);
|
|
13336
|
+
if (action.recoveryCommand) {
|
|
13337
|
+
lines.push(` recovery: ${action.recoveryCommand}`);
|
|
13338
|
+
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.");
|
|
13339
|
+
lines.push(" note: notify exits nonzero unless the target queue accepts the delivery.");
|
|
13340
|
+
} else lines.push(" recovery: unavailable because the pending action id is invalid; inspect `raft mention pending` without resending the message.");
|
|
13341
|
+
}
|
|
13342
|
+
for (const rawHandle of unresolvedMentionHandles) {
|
|
13343
|
+
const warning = toAgentSenderUnresolvedMentionWarning(rawHandle);
|
|
13344
|
+
lines.push(`- ${warning.targetHandle} — status=${warning.status}`);
|
|
13345
|
+
lines.push(` reason: ${warning.reason}`);
|
|
13346
|
+
lines.push(` consequence: ${warning.consequence}`);
|
|
13347
|
+
lines.push(" pending action: none; no visible target resolved for this token");
|
|
13348
|
+
lines.push(" expires: n/a");
|
|
13349
|
+
lines.push(" recovery: if this was a literal name or prose, wrap it in inline/fenced code; otherwise verify the exact handle and send only a corrected follow-up mention; do not resend this message.");
|
|
13350
|
+
}
|
|
13351
|
+
return `${lines.join("\n")}\n`;
|
|
13352
|
+
}
|
|
13353
|
+
const lines = [
|
|
13354
|
+
"Pending mention actions",
|
|
13355
|
+
`(${pageNote})`,
|
|
13356
|
+
""
|
|
13357
|
+
];
|
|
13358
|
+
for (const action of actions) {
|
|
13359
|
+
const target = action.targetHandle ? `${action.targetHandle} (${action.targetType})` : action.targetType;
|
|
13360
|
+
lines.push(`- ${action.resolutionId} — ${target}`);
|
|
13361
|
+
if (action.messageId) lines.push(` message: ${action.messageId}`);
|
|
13362
|
+
lines.push(` reason: ${formatPendingReason(action.reason)}`);
|
|
13363
|
+
if (action.expiresAt) lines.push(` expires: ${action.expiresAt}`);
|
|
13364
|
+
const commands = formatActionCommands(action);
|
|
13365
|
+
if (commands.length > 0) {
|
|
13366
|
+
lines.push(" recovery commands:");
|
|
13367
|
+
lines.push(...commands);
|
|
13368
|
+
if (commands.some((command) => command.includes(" mention notify "))) lines.push(" note: notify exits nonzero unless the target queue accepts the delivery.");
|
|
13369
|
+
}
|
|
13370
|
+
}
|
|
13371
|
+
return `${lines.join("\n")}\n`;
|
|
13372
|
+
}
|
|
13373
|
+
function formatMentionActionDetail(result) {
|
|
13374
|
+
const detail = result.message ?? result.reason ?? null;
|
|
13375
|
+
if (result.status === "dropped") return detail ? `not delivered: ${detail}` : "not delivered";
|
|
13376
|
+
return detail;
|
|
13377
|
+
}
|
|
13378
|
+
/** notify/add action outcome rows. */
|
|
13379
|
+
function formatAgentMentionActionResults(action, results) {
|
|
13380
|
+
const lines = [`Mention ${action} results`, ""];
|
|
13381
|
+
if (results.length === 0) {
|
|
13382
|
+
lines.push("No result rows returned.");
|
|
13383
|
+
return `${lines.join("\n")}\n`;
|
|
13384
|
+
}
|
|
13385
|
+
for (const result of results) {
|
|
13386
|
+
const target = result.targetHandle ? ` ${result.targetHandle}` : "";
|
|
13387
|
+
const detail = formatMentionActionDetail(result);
|
|
13388
|
+
const suffix = detail ? ` — ${detail}` : "";
|
|
13389
|
+
lines.push(`- ${result.resolutionId}${target}: ${result.status}${suffix}`);
|
|
13390
|
+
if (result.dedupedResolutionIds && result.dedupedResolutionIds.length > 1) lines.push(` deduped: ${result.dedupedResolutionIds.join(", ")}`);
|
|
13391
|
+
}
|
|
13392
|
+
if (action === "notify" && results.some((result) => result.status === "queued" && result.reason !== "already_queued")) lines.push("", `Recipient guidance: ${formatAgentReplyAffordance({ non_member_mention: true })}`);
|
|
13393
|
+
return `${lines.join("\n")}\n`;
|
|
13394
|
+
}
|
|
13395
|
+
function describeDelivery(row) {
|
|
13396
|
+
switch (row.outcome) {
|
|
13397
|
+
case "delivered": return "delivered";
|
|
13398
|
+
case "pending": return "still in flight — do not conclude yet";
|
|
13399
|
+
case "unknown": return "UNKNOWN — our instrument could not see this one; re-check, do not conclude";
|
|
13400
|
+
case "lost": switch (row.reasonCategory) {
|
|
13401
|
+
case "quota": return "LOST — target is rate/quota limited; it will not arrive, re-route or retry later";
|
|
13402
|
+
case "runtime_error": return "LOST — target's runtime rejected delivery; re-route";
|
|
13403
|
+
case "not_launched": return "LOST — target was not running; re-route or start it";
|
|
13404
|
+
case "unclassified": return "LOST — cause is outside the seat-availability set; re-route";
|
|
13405
|
+
default: return "LOST — server sent no category for this row (malformed); treat the cause as unknown";
|
|
13406
|
+
}
|
|
13407
|
+
}
|
|
13408
|
+
}
|
|
13409
|
+
/** Per-target mention delivery outcome for a message the bound agent sent. */
|
|
13410
|
+
function formatAgentSenderMentionDeliveries(messageId, deliveries) {
|
|
13411
|
+
if (deliveries.length === 0) return `Message ${messageId}\n\nNo mention deliveries recorded for this message.\n`;
|
|
13412
|
+
const lines = [`Message ${messageId} — mention delivery per target`, ""];
|
|
13413
|
+
for (const row of deliveries) lines.push(` ${row.targetHandle || "(unknown target)"}: ${describeDelivery(row)}`);
|
|
13414
|
+
return `${lines.join("\n")}\n`;
|
|
13415
|
+
}
|
|
13416
|
+
//#endregion
|
|
13417
|
+
//#region ../shared/src/agentOps/mentions.ts
|
|
13418
|
+
async function pendingMentionActions(client, request = {}) {
|
|
13419
|
+
const result = await client.mentions.pendingActions(request.limit === void 0 ? {} : { limit: String(request.limit) });
|
|
13420
|
+
if (!result.ok) return failureFromClientResult(result);
|
|
13421
|
+
const actions = normalizeAgentPendingMentionActions(result.data);
|
|
13422
|
+
const hasMore = typeof result.data.has_more === "boolean" ? result.data.has_more : null;
|
|
13423
|
+
const first = actions.find((a) => a.availableActions.length > 0);
|
|
13424
|
+
const next = first ? {
|
|
13425
|
+
kind: "resolve_mention",
|
|
13426
|
+
command: `raft mention notify ${first.resolutionId}`,
|
|
13427
|
+
args: {
|
|
13428
|
+
action: "notify",
|
|
13429
|
+
resolutionIds: [first.resolutionId]
|
|
13430
|
+
},
|
|
13431
|
+
why: "This @mention reached nobody at send time; notify the target (or add them) so the message is seen."
|
|
13432
|
+
} : null;
|
|
13433
|
+
return {
|
|
13434
|
+
ok: true,
|
|
13435
|
+
state: actions.length > 0 ? "pending" : "empty",
|
|
13436
|
+
data: {
|
|
13437
|
+
actions,
|
|
13438
|
+
hasMore,
|
|
13439
|
+
limit: request.limit
|
|
13440
|
+
},
|
|
13441
|
+
next,
|
|
13442
|
+
text: formatAgentPendingMentionActions(actions, {
|
|
13443
|
+
source: "pending",
|
|
13444
|
+
...hasMore === null ? {} : { hasMore },
|
|
13445
|
+
limit: request.limit
|
|
13446
|
+
})
|
|
13447
|
+
};
|
|
13448
|
+
}
|
|
13449
|
+
async function executeMentionAction(client, request) {
|
|
13450
|
+
const ids = (request.resolutionIds ?? []).map((id) => id.trim()).filter(Boolean);
|
|
13451
|
+
if (ids.length === 0 || request.action !== "notify" && request.action !== "add") return failureOutcome(opError("INVALID_REQUEST", { message: "Pass an action (notify | add) and at least one resolution id." }));
|
|
13452
|
+
const result = await client.mentions.executeAction({
|
|
13453
|
+
action: request.action,
|
|
13454
|
+
resolutionIds: ids
|
|
13455
|
+
});
|
|
13466
13456
|
if (!result.ok) return failureFromClientResult(result);
|
|
13467
|
-
const
|
|
13457
|
+
const results = normalizeAgentMentionActionResults(result.data);
|
|
13468
13458
|
return {
|
|
13469
13459
|
ok: true,
|
|
13470
|
-
state:
|
|
13460
|
+
state: "executed",
|
|
13471
13461
|
data: {
|
|
13472
|
-
|
|
13473
|
-
|
|
13462
|
+
action: request.action,
|
|
13463
|
+
results
|
|
13474
13464
|
},
|
|
13475
13465
|
next: null,
|
|
13476
|
-
text:
|
|
13466
|
+
text: formatAgentMentionActionResults(request.action, results)
|
|
13467
|
+
};
|
|
13468
|
+
}
|
|
13469
|
+
async function senderMentionDeliveries(client, request) {
|
|
13470
|
+
if (!request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id is required." }));
|
|
13471
|
+
const result = await client.mentions.senderDeliveries({ messageId: request.messageId });
|
|
13472
|
+
if (!result.ok) return failureFromClientResult(result);
|
|
13473
|
+
const data = result.data;
|
|
13474
|
+
return {
|
|
13475
|
+
ok: true,
|
|
13476
|
+
state: data.deliveries.length > 0 ? "deliveries" : "empty",
|
|
13477
|
+
data,
|
|
13478
|
+
next: null,
|
|
13479
|
+
text: formatAgentSenderMentionDeliveries(data.messageId, data.deliveries)
|
|
13477
13480
|
};
|
|
13478
13481
|
}
|
|
13479
13482
|
//#endregion
|
|
13480
|
-
//#region ../shared/src/agentText/
|
|
13481
|
-
function
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13483
|
+
//#region ../shared/src/agentText/knowledge.ts
|
|
13484
|
+
function formatMatchReason(result) {
|
|
13485
|
+
const parts = [];
|
|
13486
|
+
for (const { term, matched } of result.correctedTerms ?? []) parts.push(`${term} → ${matched} (typo)`);
|
|
13487
|
+
for (const { from, to } of result.expandedTerms ?? []) parts.push(`${from} → ${to} (concept)`);
|
|
13488
|
+
return parts.length > 0 ? ` matched: ${parts.join(", ")}` : "";
|
|
13485
13489
|
}
|
|
13486
|
-
function
|
|
13487
|
-
|
|
13488
|
-
|
|
13490
|
+
function formatAgentKnowledgeSearchResults(results) {
|
|
13491
|
+
return results.map((result, index) => {
|
|
13492
|
+
const body = result.firstScreen.trim().split(/\r?\n/).filter(Boolean).map((line) => ` ${line}`).join("\n");
|
|
13493
|
+
const reason = formatMatchReason(result);
|
|
13494
|
+
return `${index + 1}. ${result.slug} — ${result.title}${reason ? `\n${reason}` : ""}${body ? `\n${body}` : ""}`;
|
|
13495
|
+
}).join("\n\n") + "\n";
|
|
13489
13496
|
}
|
|
13490
|
-
function
|
|
13491
|
-
return
|
|
13497
|
+
function formatAgentKnowledgeStdout(content) {
|
|
13498
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
13492
13499
|
}
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
function
|
|
13496
|
-
|
|
13500
|
+
//#endregion
|
|
13501
|
+
//#region ../shared/src/agentOps/manual.ts
|
|
13502
|
+
function requireContext(context) {
|
|
13503
|
+
if (!context?.intent?.trim() || !context?.reason?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "Manual calls need a short `intent` and `reason`." }));
|
|
13504
|
+
return null;
|
|
13497
13505
|
}
|
|
13498
|
-
function
|
|
13506
|
+
async function getManualTopic(client, request) {
|
|
13507
|
+
const invalid = requireContext(request);
|
|
13508
|
+
if (invalid) return invalid;
|
|
13509
|
+
if (!request.topic?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A topic id is required." }));
|
|
13510
|
+
const result = await client.knowledge.get({
|
|
13511
|
+
topic: request.topic.trim(),
|
|
13512
|
+
intent: request.intent.trim(),
|
|
13513
|
+
reason: request.reason.trim()
|
|
13514
|
+
});
|
|
13515
|
+
if (!result.ok) return failureFromClientResult(result);
|
|
13499
13516
|
return {
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
consequence: "This @mention did not notify anyone.",
|
|
13506
|
-
expiresAt: action.expiresAt ?? null,
|
|
13507
|
-
recoveryCommand: formatAgentMentionNotifyRecoveryCommand(action.resolutionId)
|
|
13517
|
+
ok: true,
|
|
13518
|
+
state: "topic",
|
|
13519
|
+
data: result.data,
|
|
13520
|
+
next: null,
|
|
13521
|
+
text: formatAgentKnowledgeStdout(result.data.content)
|
|
13508
13522
|
};
|
|
13509
13523
|
}
|
|
13510
|
-
function
|
|
13524
|
+
async function searchManual(client, request) {
|
|
13525
|
+
const invalid = requireContext(request);
|
|
13526
|
+
if (invalid) return invalid;
|
|
13527
|
+
if (!request.query?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "Search keywords are required." }));
|
|
13528
|
+
const result = await client.knowledge.search({
|
|
13529
|
+
query: request.query.trim(),
|
|
13530
|
+
...request.scope ? { scope: request.scope } : {},
|
|
13531
|
+
intent: request.intent.trim(),
|
|
13532
|
+
reason: request.reason.trim()
|
|
13533
|
+
});
|
|
13534
|
+
if (!result.ok) return failureFromClientResult(result);
|
|
13535
|
+
const data = result.data;
|
|
13536
|
+
const results = data.results;
|
|
13537
|
+
const first = results[0];
|
|
13538
|
+
const next = first ? {
|
|
13539
|
+
kind: "read_manual_topic",
|
|
13540
|
+
command: `raft manual get ${first.slug} --intent "…" --reason "…"`,
|
|
13541
|
+
args: { topic: first.slug },
|
|
13542
|
+
why: "Open the best-matching topic."
|
|
13543
|
+
} : null;
|
|
13511
13544
|
return {
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
recoveryCommand: null
|
|
13545
|
+
ok: true,
|
|
13546
|
+
state: results.length > 0 ? "results" : "empty",
|
|
13547
|
+
data,
|
|
13548
|
+
next,
|
|
13549
|
+
text: results.length > 0 ? formatAgentKnowledgeSearchResults(results) : "No Manual topics matched. Retry with different keywords, keeping your intent and reason.\n"
|
|
13518
13550
|
};
|
|
13519
13551
|
}
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
return
|
|
13527
|
-
resolutionId: String(item.resolutionId ?? item.id ?? ""),
|
|
13528
|
-
messageId: String(item.messageId ?? ""),
|
|
13529
|
-
targetType: String(item.targetType ?? "unknown"),
|
|
13530
|
-
targetHandle: String(item.targetHandle ?? ""),
|
|
13531
|
-
reason: String(item.reason ?? "Mention target was not notified at send time."),
|
|
13532
|
-
availableActions: Array.isArray(item.availableActions) ? item.availableActions.map(String) : [],
|
|
13533
|
-
expiresAt: typeof item.expiresAt === "string" ? item.expiresAt : null
|
|
13534
|
-
})).filter((item) => item.resolutionId.length > 0);
|
|
13552
|
+
//#endregion
|
|
13553
|
+
//#region ../shared/src/agentOps/state.ts
|
|
13554
|
+
const RAFT_STATE_SCHEMA = "raft-sdk-state.v1";
|
|
13555
|
+
const RAFT_STATE_CONTINUATIONS_PER_TARGET = 3;
|
|
13556
|
+
const RAFT_STATE_CONTINUATIONS_TOTAL = 20;
|
|
13557
|
+
function isNullableCursor(value) {
|
|
13558
|
+
return value === null || typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
13535
13559
|
}
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13560
|
+
/** Validate a loaded value; anything unrecognised is treated as an empty store (safe: see file header). */
|
|
13561
|
+
function parseRaftState(value) {
|
|
13562
|
+
if (!value || typeof value !== "object") return null;
|
|
13563
|
+
const v = value;
|
|
13564
|
+
if (v.schema !== "raft-sdk-state.v1") return null;
|
|
13565
|
+
if (typeof v.version !== "number" || !Number.isInteger(v.version) || v.version < 0) return null;
|
|
13566
|
+
if (!isNullableCursor(v.cursor ?? null) || !isNullableCursor(v.pendingCursor ?? null)) return null;
|
|
13567
|
+
const frontier = v.frontier;
|
|
13568
|
+
const continuations = Array.isArray(v.continuations) ? v.continuations.filter((c) => {
|
|
13569
|
+
const r = c;
|
|
13570
|
+
return Boolean(r) && typeof r.target === "string" && typeof r.idempotencyKey === "string" && typeof r.contentHash === "string" && typeof r.heldAt === "string";
|
|
13571
|
+
}) : [];
|
|
13572
|
+
return {
|
|
13573
|
+
schema: RAFT_STATE_SCHEMA,
|
|
13574
|
+
version: v.version,
|
|
13575
|
+
cursor: v.cursor ?? null,
|
|
13576
|
+
pendingCursor: v.pendingCursor ?? null,
|
|
13577
|
+
frontier: frontier && frontier.version === 1 ? frontier : {
|
|
13578
|
+
version: 1,
|
|
13579
|
+
targets: {},
|
|
13580
|
+
aliases: {}
|
|
13581
|
+
},
|
|
13582
|
+
continuations
|
|
13583
|
+
};
|
|
13551
13584
|
}
|
|
13552
|
-
|
|
13553
|
-
function
|
|
13554
|
-
const
|
|
13555
|
-
|
|
13556
|
-
if (hasMore === false) return `shown ${shown}, ${asked} — truncated=false · this is the complete list`;
|
|
13557
|
-
return `shown ${shown}, ${asked} — truncated=unknown · server did not report has_more, so completeness is NOT asserted`;
|
|
13585
|
+
/** Hex SHA-256 identifying one logical message (WebCrypto; Workers-safe). */
|
|
13586
|
+
async function hashRaftSendContent(target, content, attachmentIds = []) {
|
|
13587
|
+
const bytes = new TextEncoder().encode(`${target}\n${[...attachmentIds].sort().join(",")}\n${content}`);
|
|
13588
|
+
return [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
13558
13589
|
}
|
|
13559
|
-
/**
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
} else lines.push(" recovery: unavailable because the pending action id is invalid; inspect `raft mention pending` without resending the message.");
|
|
13586
|
-
}
|
|
13587
|
-
for (const rawHandle of unresolvedMentionHandles) {
|
|
13588
|
-
const warning = toAgentSenderUnresolvedMentionWarning(rawHandle);
|
|
13589
|
-
lines.push(`- ${warning.targetHandle} — status=${warning.status}`);
|
|
13590
|
-
lines.push(` reason: ${warning.reason}`);
|
|
13591
|
-
lines.push(` consequence: ${warning.consequence}`);
|
|
13592
|
-
lines.push(" pending action: none; no visible target resolved for this token");
|
|
13593
|
-
lines.push(" expires: n/a");
|
|
13594
|
-
lines.push(" recovery: if this was a literal name or prose, wrap it in inline/fenced code; otherwise verify the exact handle and send only a corrected follow-up mention; do not resend this message.");
|
|
13595
|
-
}
|
|
13596
|
-
return `${lines.join("\n")}\n`;
|
|
13590
|
+
/**
|
|
13591
|
+
* One client's view of the persisted state: loads once, holds the working
|
|
13592
|
+
* copy, saves on request. Also used without a store (memory only), so the
|
|
13593
|
+
* pending/committed cursor flow behaves the same with or without persistence.
|
|
13594
|
+
*/
|
|
13595
|
+
var RaftStateSession = class {
|
|
13596
|
+
store;
|
|
13597
|
+
onError;
|
|
13598
|
+
frontier;
|
|
13599
|
+
cursor = null;
|
|
13600
|
+
pendingCursor = null;
|
|
13601
|
+
continuations = [];
|
|
13602
|
+
loadedVersion = void 0;
|
|
13603
|
+
loading = null;
|
|
13604
|
+
dirty = false;
|
|
13605
|
+
constructor(store, frontier, onError) {
|
|
13606
|
+
this.store = store;
|
|
13607
|
+
this.onError = onError;
|
|
13608
|
+
this.frontier = frontier;
|
|
13609
|
+
}
|
|
13610
|
+
get persistent() {
|
|
13611
|
+
return this.store !== void 0;
|
|
13612
|
+
}
|
|
13613
|
+
/** The version loaded from (or last saved to) the store; `undefined` before the first save of an empty store. */
|
|
13614
|
+
get version() {
|
|
13615
|
+
return this.loadedVersion;
|
|
13597
13616
|
}
|
|
13598
|
-
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
|
|
13602
|
-
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
|
|
13612
|
-
|
|
13613
|
-
if (
|
|
13614
|
-
|
|
13617
|
+
/** Load once. A failed or unreadable load starts from an empty state (reported, not thrown). */
|
|
13618
|
+
ensureLoaded() {
|
|
13619
|
+
if (!this.store) return Promise.resolve();
|
|
13620
|
+
this.loading ??= (async () => {
|
|
13621
|
+
let raw = null;
|
|
13622
|
+
try {
|
|
13623
|
+
raw = await this.store.load();
|
|
13624
|
+
} catch (error) {
|
|
13625
|
+
this.onError?.(error, {
|
|
13626
|
+
phase: "load",
|
|
13627
|
+
version: void 0
|
|
13628
|
+
});
|
|
13629
|
+
return;
|
|
13630
|
+
}
|
|
13631
|
+
const state = parseRaftState(raw);
|
|
13632
|
+
if (!state) return;
|
|
13633
|
+
this.loadedVersion = state.version;
|
|
13634
|
+
this.cursor = state.cursor;
|
|
13635
|
+
this.pendingCursor = state.pendingCursor;
|
|
13636
|
+
this.frontier.absorb(state.frontier);
|
|
13637
|
+
this.continuations = state.continuations ?? [];
|
|
13638
|
+
})();
|
|
13639
|
+
return this.loading;
|
|
13615
13640
|
}
|
|
13616
|
-
|
|
13617
|
-
|
|
13618
|
-
function formatMentionActionDetail(result) {
|
|
13619
|
-
const detail = result.message ?? result.reason ?? null;
|
|
13620
|
-
if (result.status === "dropped") return detail ? `not delivered: ${detail}` : "not delivered";
|
|
13621
|
-
return detail;
|
|
13622
|
-
}
|
|
13623
|
-
/** notify/add action outcome rows. */
|
|
13624
|
-
function formatAgentMentionActionResults(action, results) {
|
|
13625
|
-
const lines = [`Mention ${action} results`, ""];
|
|
13626
|
-
if (results.length === 0) {
|
|
13627
|
-
lines.push("No result rows returned.");
|
|
13628
|
-
return `${lines.join("\n")}\n`;
|
|
13641
|
+
markDirty() {
|
|
13642
|
+
this.dirty = true;
|
|
13629
13643
|
}
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
if (result.dedupedResolutionIds && result.dedupedResolutionIds.length > 1) lines.push(` deduped: ${result.dedupedResolutionIds.join(", ")}`);
|
|
13644
|
+
/** Record the cursor of a batch just returned. Acknowledges nothing. */
|
|
13645
|
+
setPending(cursor) {
|
|
13646
|
+
if (cursor === null || cursor === this.pendingCursor) return;
|
|
13647
|
+
this.pendingCursor = cursor;
|
|
13648
|
+
this.dirty = true;
|
|
13636
13649
|
}
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
13650
|
+
/**
|
|
13651
|
+
* Promote a cursor to committed. With no argument, commits `pendingCursor`.
|
|
13652
|
+
* Returns the committed cursor, or null when there was nothing to commit.
|
|
13653
|
+
* Never moves the committed cursor backwards.
|
|
13654
|
+
*/
|
|
13655
|
+
commit(cursor) {
|
|
13656
|
+
const next = cursor === void 0 ? this.pendingCursor : cursor;
|
|
13657
|
+
if (next === null || next === void 0) return null;
|
|
13658
|
+
if (this.cursor === null || next > this.cursor) this.cursor = next;
|
|
13659
|
+
if (this.pendingCursor !== null && this.pendingCursor <= this.cursor) this.pendingCursor = null;
|
|
13660
|
+
this.dirty = true;
|
|
13661
|
+
return this.cursor;
|
|
13662
|
+
}
|
|
13663
|
+
findContinuation(target, contentHash) {
|
|
13664
|
+
return this.continuations.find((c) => c.target === target && c.contentHash === contentHash);
|
|
13665
|
+
}
|
|
13666
|
+
rememberContinuation(entry) {
|
|
13667
|
+
this.continuations = this.continuations.filter((c) => !(c.target === entry.target && c.contentHash === entry.contentHash));
|
|
13668
|
+
this.continuations.push(entry);
|
|
13669
|
+
const perTarget = this.continuations.filter((c) => c.target === entry.target);
|
|
13670
|
+
for (const drop of perTarget.slice(0, Math.max(0, perTarget.length - 3))) this.continuations.splice(this.continuations.indexOf(drop), 1);
|
|
13671
|
+
if (this.continuations.length > 20) this.continuations.splice(0, this.continuations.length - 20);
|
|
13672
|
+
this.dirty = true;
|
|
13673
|
+
}
|
|
13674
|
+
forgetContinuation(target, contentHash) {
|
|
13675
|
+
const before = this.continuations.length;
|
|
13676
|
+
this.continuations = this.continuations.filter((c) => !(c.target === target && c.contentHash === contentHash));
|
|
13677
|
+
if (this.continuations.length !== before) this.dirty = true;
|
|
13678
|
+
}
|
|
13679
|
+
snapshot(nextVersion = (this.loadedVersion ?? 0) + 1) {
|
|
13680
|
+
return {
|
|
13681
|
+
schema: RAFT_STATE_SCHEMA,
|
|
13682
|
+
version: nextVersion,
|
|
13683
|
+
cursor: this.cursor,
|
|
13684
|
+
pendingCursor: this.pendingCursor,
|
|
13685
|
+
frontier: this.frontier.snapshot(),
|
|
13686
|
+
continuations: [...this.continuations]
|
|
13687
|
+
};
|
|
13688
|
+
}
|
|
13689
|
+
/**
|
|
13690
|
+
* Save if anything changed (or `force`). One attempt; a failure is reported
|
|
13691
|
+
* and swallowed. Returns whether the save succeeded (true when nothing to save).
|
|
13692
|
+
*/
|
|
13693
|
+
async save(options = {}) {
|
|
13694
|
+
if (!this.store) return true;
|
|
13695
|
+
if (!this.dirty && !options.force) return true;
|
|
13696
|
+
const expectedVersion = this.loadedVersion;
|
|
13697
|
+
const state = this.snapshot();
|
|
13698
|
+
try {
|
|
13699
|
+
await this.store.save(state, { expectedVersion });
|
|
13700
|
+
} catch (error) {
|
|
13701
|
+
this.onError?.(error, {
|
|
13702
|
+
phase: "save",
|
|
13703
|
+
version: expectedVersion
|
|
13704
|
+
});
|
|
13705
|
+
return false;
|
|
13651
13706
|
}
|
|
13707
|
+
this.loadedVersion = state.version;
|
|
13708
|
+
this.dirty = false;
|
|
13709
|
+
return true;
|
|
13652
13710
|
}
|
|
13711
|
+
};
|
|
13712
|
+
//#endregion
|
|
13713
|
+
//#region ../shared/src/agentOps/actions.ts
|
|
13714
|
+
/** Text is the CLI's `raft action prepare` output. */
|
|
13715
|
+
function formatAgentActionCardPosted(target, messageId) {
|
|
13716
|
+
const shortId = messageId ? messageId.slice(0, 8) : null;
|
|
13717
|
+
return shortId ? `Action card posted to ${target} as message ${messageId} (short ${shortId}). The human can click the action verb to commit.\n` : `Action card posted to ${target}.\n`;
|
|
13653
13718
|
}
|
|
13654
|
-
|
|
13655
|
-
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13719
|
+
async function prepareActionCard(client, request) {
|
|
13720
|
+
if (!request?.target?.trim() || !request.action) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and an action are required to prepare a card." }));
|
|
13721
|
+
const result = await client.actions.prepare(request);
|
|
13722
|
+
if (!result.ok) return failureFromClientResult(result);
|
|
13723
|
+
const card = {
|
|
13724
|
+
target: request.target,
|
|
13725
|
+
messageId: result.data.messageId
|
|
13726
|
+
};
|
|
13727
|
+
return {
|
|
13728
|
+
ok: true,
|
|
13729
|
+
state: "prepared",
|
|
13730
|
+
data: card,
|
|
13731
|
+
next: {
|
|
13732
|
+
kind: "await_confirmation",
|
|
13733
|
+
command: `raft message read --target "${request.target}" --around ${card.messageId.slice(0, 8)}`,
|
|
13734
|
+
args: {
|
|
13735
|
+
target: request.target,
|
|
13736
|
+
messageId: card.messageId
|
|
13737
|
+
},
|
|
13738
|
+
why: "A human must click the card to commit it. You are not notified of the result yet; read the card message to see its state instead of waiting."
|
|
13739
|
+
},
|
|
13740
|
+
text: formatAgentActionCardPosted(request.target, card.messageId)
|
|
13741
|
+
};
|
|
13660
13742
|
}
|
|
13661
13743
|
//#endregion
|
|
13662
|
-
//#region
|
|
13663
|
-
|
|
13664
|
-
|
|
13744
|
+
//#region src/command.ts
|
|
13745
|
+
/** Run one `raft` command on the Server for the agent this credential belongs to. */
|
|
13746
|
+
async function runCommand(routes, request) {
|
|
13747
|
+
const result = await routes.request("agentCommand", { body: request });
|
|
13665
13748
|
if (!result.ok) return failureFromClientResult(result);
|
|
13666
|
-
const actions = normalizeAgentPendingMentionActions(result.data);
|
|
13667
|
-
const hasMore = typeof result.data.has_more === "boolean" ? result.data.has_more : null;
|
|
13668
|
-
const first = actions.find((a) => a.availableActions.length > 0);
|
|
13669
|
-
const next = first ? {
|
|
13670
|
-
kind: "resolve_mention",
|
|
13671
|
-
command: `raft mention notify ${first.resolutionId}`,
|
|
13672
|
-
args: {
|
|
13673
|
-
action: "notify",
|
|
13674
|
-
resolutionIds: [first.resolutionId]
|
|
13675
|
-
},
|
|
13676
|
-
why: "This @mention reached nobody at send time; notify the target (or add them) so the message is seen."
|
|
13677
|
-
} : null;
|
|
13678
13749
|
return {
|
|
13679
13750
|
ok: true,
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
hasMore,
|
|
13684
|
-
limit: request.limit
|
|
13685
|
-
},
|
|
13686
|
-
next,
|
|
13687
|
-
text: formatAgentPendingMentionActions(actions, {
|
|
13688
|
-
source: "pending",
|
|
13689
|
-
...hasMore === null ? {} : { hasMore },
|
|
13690
|
-
limit: request.limit
|
|
13691
|
-
})
|
|
13751
|
+
exitCode: result.data.exitCode,
|
|
13752
|
+
text: result.data.text,
|
|
13753
|
+
outcome: result.data.outcome
|
|
13692
13754
|
};
|
|
13693
13755
|
}
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13756
|
+
//#endregion
|
|
13757
|
+
//#region src/client.ts
|
|
13758
|
+
var RaftSdkConfigurationError = class extends Error {
|
|
13759
|
+
code;
|
|
13760
|
+
name = "RaftSdkConfigurationError";
|
|
13761
|
+
constructor(code, message) {
|
|
13762
|
+
super(message);
|
|
13763
|
+
this.code = code;
|
|
13764
|
+
}
|
|
13765
|
+
};
|
|
13766
|
+
function requireServerUrl(value) {
|
|
13767
|
+
const serverUrl = value.trim();
|
|
13768
|
+
if (!serverUrl) throw new RaftSdkConfigurationError("MISSING_SERVER_URL", "Raft Server URL is required");
|
|
13769
|
+
let parsed;
|
|
13770
|
+
try {
|
|
13771
|
+
parsed = new URL(serverUrl);
|
|
13772
|
+
} catch {
|
|
13773
|
+
throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must be an absolute HTTP(S) URL");
|
|
13774
|
+
}
|
|
13775
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must use HTTP or HTTPS");
|
|
13776
|
+
if (parsed.username || parsed.password) throw new RaftSdkConfigurationError("INVALID_SERVER_URL", "Raft Server URL must not contain credentials");
|
|
13777
|
+
return serverUrl.replace(/\/+$/, "");
|
|
13778
|
+
}
|
|
13779
|
+
function requireAgentCredential(value) {
|
|
13780
|
+
const credential = value.trim();
|
|
13781
|
+
if (!credential) throw new RaftSdkConfigurationError("MISSING_AGENT_CREDENTIAL", "Raft Agent credential is required");
|
|
13782
|
+
if (!credential.startsWith("sk_agent_") || credential.length === 9) throw new RaftSdkConfigurationError("INVALID_AGENT_CREDENTIAL", "Raft Agent credential must be a long-lived External Agent credential");
|
|
13783
|
+
return credential;
|
|
13784
|
+
}
|
|
13785
|
+
/**
|
|
13786
|
+
* Create a typed client for the credential-derived `/internal/agent-api/*`
|
|
13787
|
+
* surface. The SDK does not read CLI profiles, environment variables, or the
|
|
13788
|
+
* host user's home directory: the caller chooses and supplies the credential.
|
|
13789
|
+
*/
|
|
13790
|
+
function createRaftClient(options) {
|
|
13791
|
+
const serverUrl = requireServerUrl(options.serverUrl);
|
|
13792
|
+
const auth = { authorization: `Bearer ${requireAgentCredential(options.credential)}` };
|
|
13793
|
+
const messageClient = createAgentApiMessageClient({
|
|
13794
|
+
baseUrl: serverUrl,
|
|
13795
|
+
fetch: options.fetch,
|
|
13796
|
+
headers: options.headers,
|
|
13797
|
+
retry: options.retry,
|
|
13798
|
+
throttle: options.throttle,
|
|
13799
|
+
auth
|
|
13800
|
+
});
|
|
13801
|
+
const agentApi = createAgentApiClient({ fetch: {
|
|
13802
|
+
baseUrl: serverUrl,
|
|
13803
|
+
fetch: options.fetch,
|
|
13804
|
+
headers: options.headers,
|
|
13805
|
+
retry: { attempts: Math.min(Math.max(1, options.retry?.attempts ?? 1), 5) },
|
|
13806
|
+
throttle: options.throttle,
|
|
13807
|
+
auth
|
|
13808
|
+
} });
|
|
13809
|
+
const writeApi = createAgentApiClient({ fetch: {
|
|
13810
|
+
baseUrl: serverUrl,
|
|
13811
|
+
fetch: options.fetch,
|
|
13812
|
+
headers: options.headers,
|
|
13813
|
+
retry: { attempts: 1 },
|
|
13814
|
+
throttle: options.throttle,
|
|
13815
|
+
auth
|
|
13816
|
+
} });
|
|
13817
|
+
const eventsApi = createAgentApiClient({ fetch: {
|
|
13818
|
+
baseUrl: serverUrl,
|
|
13819
|
+
fetch: (input, init) => (options.fetch ?? fetch)(input, {
|
|
13820
|
+
...init,
|
|
13821
|
+
cache: "no-store",
|
|
13822
|
+
redirect: "error"
|
|
13823
|
+
}),
|
|
13824
|
+
headers: Object.fromEntries(new Headers(options.headers)),
|
|
13825
|
+
retry: { attempts: 1 },
|
|
13826
|
+
throttle: options.throttle,
|
|
13827
|
+
auth
|
|
13828
|
+
} });
|
|
13829
|
+
const routes = createRaftRoutes({
|
|
13830
|
+
serverUrl,
|
|
13831
|
+
fetch: options.fetch,
|
|
13832
|
+
headers: options.headers,
|
|
13833
|
+
authorization: auth.authorization,
|
|
13834
|
+
readAttempts: options.retry?.attempts,
|
|
13835
|
+
beforeRequest: options.throttle?.beforeRequest
|
|
13700
13836
|
});
|
|
13701
|
-
if (!result.ok) return failureFromClientResult(result);
|
|
13702
|
-
const results = normalizeAgentMentionActionResults(result.data);
|
|
13703
|
-
return {
|
|
13704
|
-
ok: true,
|
|
13705
|
-
state: "executed",
|
|
13706
|
-
data: {
|
|
13707
|
-
action: request.action,
|
|
13708
|
-
results
|
|
13709
|
-
},
|
|
13710
|
-
next: null,
|
|
13711
|
-
text: formatAgentMentionActionResults(request.action, results)
|
|
13712
|
-
};
|
|
13713
|
-
}
|
|
13714
|
-
async function senderMentionDeliveries(client, request) {
|
|
13715
|
-
if (!request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id is required." }));
|
|
13716
|
-
const result = await client.mentions.senderDeliveries({ messageId: request.messageId });
|
|
13717
|
-
if (!result.ok) return failureFromClientResult(result);
|
|
13718
|
-
const data = result.data;
|
|
13719
13837
|
return {
|
|
13720
|
-
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
13724
|
-
|
|
13838
|
+
...messageClient,
|
|
13839
|
+
...createRaftManageClient(agentApi, writeApi, {
|
|
13840
|
+
serverUrl,
|
|
13841
|
+
fetch: options.fetch ?? fetch,
|
|
13842
|
+
headers: Object.fromEntries(new Headers(options.headers)),
|
|
13843
|
+
authorization: auth.authorization,
|
|
13844
|
+
beforeRequest: options.throttle?.beforeRequest
|
|
13845
|
+
}),
|
|
13846
|
+
events: { receive: (request) => receiveRaftEvents(eventsApi, request) },
|
|
13847
|
+
channels: { join: (request) => joinRaftChannelByTarget(agentApi, request) },
|
|
13848
|
+
routes,
|
|
13849
|
+
runCommand: (request) => runCommand(routes, request),
|
|
13850
|
+
agent: { context: () => getRaftContext(agentApi) }
|
|
13725
13851
|
};
|
|
13726
13852
|
}
|
|
13727
13853
|
//#endregion
|
|
13728
|
-
//#region ../shared/src/
|
|
13729
|
-
function
|
|
13730
|
-
|
|
13731
|
-
for (const { term, matched } of result.correctedTerms ?? []) parts.push(`${term} → ${matched} (typo)`);
|
|
13732
|
-
for (const { from, to } of result.expandedTerms ?? []) parts.push(`${from} → ${to} (concept)`);
|
|
13733
|
-
return parts.length > 0 ? ` matched: ${parts.join(", ")}` : "";
|
|
13734
|
-
}
|
|
13735
|
-
function formatAgentKnowledgeSearchResults(results) {
|
|
13736
|
-
return results.map((result, index) => {
|
|
13737
|
-
const body = result.firstScreen.trim().split(/\r?\n/).filter(Boolean).map((line) => ` ${line}`).join("\n");
|
|
13738
|
-
const reason = formatMatchReason(result);
|
|
13739
|
-
return `${index + 1}. ${result.slug} — ${result.title}${reason ? `\n${reason}` : ""}${body ? `\n${body}` : ""}`;
|
|
13740
|
-
}).join("\n\n") + "\n";
|
|
13854
|
+
//#region ../shared/src/clock.ts
|
|
13855
|
+
function currentTimeMs() {
|
|
13856
|
+
return Date.now();
|
|
13741
13857
|
}
|
|
13742
|
-
function
|
|
13743
|
-
return
|
|
13858
|
+
function currentDate() {
|
|
13859
|
+
return new Date(currentTimeMs());
|
|
13744
13860
|
}
|
|
13745
13861
|
//#endregion
|
|
13746
|
-
//#region
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13862
|
+
//#region src/credential.ts
|
|
13863
|
+
var RaftCredentialError = class extends Error {
|
|
13864
|
+
code;
|
|
13865
|
+
name = "RaftCredentialError";
|
|
13866
|
+
constructor(code, message) {
|
|
13867
|
+
super(message);
|
|
13868
|
+
this.code = code;
|
|
13869
|
+
}
|
|
13870
|
+
};
|
|
13871
|
+
function isNonEmptyString(value) {
|
|
13872
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
13750
13873
|
}
|
|
13751
|
-
|
|
13752
|
-
|
|
13753
|
-
|
|
13754
|
-
if (!
|
|
13755
|
-
const result = await client.knowledge.get({
|
|
13756
|
-
topic: request.topic.trim(),
|
|
13757
|
-
intent: request.intent.trim(),
|
|
13758
|
-
reason: request.reason.trim()
|
|
13759
|
-
});
|
|
13760
|
-
if (!result.ok) return failureFromClientResult(result);
|
|
13874
|
+
function parseIdentityResponse(value) {
|
|
13875
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
13876
|
+
const input = value;
|
|
13877
|
+
if (!isNonEmptyString(input.agentId) || !isNonEmptyString(input.agentName) || input.agentDisplayName !== null && typeof input.agentDisplayName !== "string" || !isNonEmptyString(input.serverId) || !isNonEmptyString(input.credentialId) || !Array.isArray(input.scopes) || !input.scopes.every(isNonEmptyString)) return null;
|
|
13761
13878
|
return {
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13879
|
+
agentId: input.agentId,
|
|
13880
|
+
agentName: input.agentName,
|
|
13881
|
+
agentDisplayName: input.agentDisplayName,
|
|
13882
|
+
serverId: input.serverId,
|
|
13883
|
+
credentialId: input.credentialId,
|
|
13884
|
+
scopes: [...input.scopes]
|
|
13767
13885
|
};
|
|
13768
13886
|
}
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
if (
|
|
13773
|
-
const
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
const next = first ? {
|
|
13784
|
-
kind: "read_manual_topic",
|
|
13785
|
-
command: `raft manual get ${first.slug} --intent "…" --reason "…"`,
|
|
13786
|
-
args: { topic: first.slug },
|
|
13787
|
-
why: "Open the best-matching topic."
|
|
13788
|
-
} : null;
|
|
13887
|
+
function parseStoredRaftCredential(value) {
|
|
13888
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
13889
|
+
const input = value;
|
|
13890
|
+
if (input.schemaVersion !== 1 || !isNonEmptyString(input.serverUrl) || !isNonEmptyString(input.credential) || !isNonEmptyString(input.storedAt)) return null;
|
|
13891
|
+
const identity = parseIdentityResponse(input);
|
|
13892
|
+
if (!identity || Number.isNaN(Date.parse(input.storedAt))) return null;
|
|
13893
|
+
let serverUrl;
|
|
13894
|
+
let credential;
|
|
13895
|
+
try {
|
|
13896
|
+
serverUrl = requireServerUrl(input.serverUrl);
|
|
13897
|
+
credential = requireAgentCredential(input.credential);
|
|
13898
|
+
} catch {
|
|
13899
|
+
return null;
|
|
13900
|
+
}
|
|
13789
13901
|
return {
|
|
13790
|
-
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
13902
|
+
schemaVersion: 1,
|
|
13903
|
+
serverUrl,
|
|
13904
|
+
credential,
|
|
13905
|
+
storedAt: input.storedAt,
|
|
13906
|
+
...identity
|
|
13795
13907
|
};
|
|
13796
13908
|
}
|
|
13797
|
-
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
const
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13909
|
+
/**
|
|
13910
|
+
* Validate an existing External Agent credential against Raft, derive its
|
|
13911
|
+
* bound identity from the credential-authenticated `whoami` surface, and save
|
|
13912
|
+
* it through the caller's explicit store. This does not mint or rotate a
|
|
13913
|
+
* credential and never returns the credential bytes.
|
|
13914
|
+
*/
|
|
13915
|
+
async function bootstrapRaftCredential(options) {
|
|
13916
|
+
const serverUrl = requireServerUrl(options.serverUrl);
|
|
13917
|
+
const credential = requireAgentCredential(options.credential);
|
|
13918
|
+
const httpFetch = options.fetch ?? globalThis.fetch;
|
|
13919
|
+
let existing;
|
|
13920
|
+
try {
|
|
13921
|
+
existing = await options.store.load();
|
|
13922
|
+
} catch {
|
|
13923
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential could not be checked before saving");
|
|
13924
|
+
}
|
|
13925
|
+
if (existing) {
|
|
13926
|
+
const validatedExisting = parseStoredRaftCredential(existing);
|
|
13927
|
+
if (!validatedExisting) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The existing Agent credential record is invalid");
|
|
13928
|
+
existing = validatedExisting;
|
|
13929
|
+
if (existing.serverUrl !== serverUrl || existing.credential !== credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
|
|
13930
|
+
}
|
|
13931
|
+
let response;
|
|
13932
|
+
try {
|
|
13933
|
+
response = await httpFetch(`${serverUrl}/internal/agent-api/`, {
|
|
13934
|
+
method: "GET",
|
|
13935
|
+
headers: { authorization: `Bearer ${credential}` }
|
|
13936
|
+
});
|
|
13937
|
+
} catch {
|
|
13938
|
+
throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", "Could not reach the Raft Server to verify the Agent credential");
|
|
13939
|
+
}
|
|
13940
|
+
if (!response.ok) {
|
|
13941
|
+
if (response.status === 401 || response.status === 403) throw new RaftCredentialError("CREDENTIAL_REJECTED", "The Raft Server rejected the Agent credential");
|
|
13942
|
+
throw new RaftCredentialError("CREDENTIAL_VERIFY_FAILED", `The Raft Server could not verify the Agent credential (status ${response.status})`);
|
|
13943
|
+
}
|
|
13944
|
+
let payload;
|
|
13945
|
+
try {
|
|
13946
|
+
payload = await response.json();
|
|
13947
|
+
} catch {
|
|
13948
|
+
throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
|
|
13949
|
+
}
|
|
13950
|
+
const identity = parseIdentityResponse(payload);
|
|
13951
|
+
if (!identity) throw new RaftCredentialError("CREDENTIAL_RESPONSE_INVALID", "The Raft Server returned an invalid Agent identity response");
|
|
13952
|
+
if (!identity.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The Raft Agent credential does not include the send capability required by this SDK");
|
|
13953
|
+
if (existing && (existing.serverId !== identity.serverId || existing.agentId !== identity.agentId)) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store is already bound to a different Raft Agent identity");
|
|
13954
|
+
if (existing) return {
|
|
13955
|
+
serverUrl,
|
|
13956
|
+
...identity
|
|
13957
|
+
};
|
|
13958
|
+
const record = {
|
|
13959
|
+
schemaVersion: 1,
|
|
13960
|
+
serverUrl,
|
|
13961
|
+
credential,
|
|
13962
|
+
storedAt: currentDate().toISOString(),
|
|
13963
|
+
...identity
|
|
13964
|
+
};
|
|
13965
|
+
try {
|
|
13966
|
+
await options.store.save(record);
|
|
13967
|
+
} catch (error) {
|
|
13968
|
+
if (error instanceof RaftCredentialError && error.code === "CREDENTIAL_STORE_CONFLICT") throw error;
|
|
13969
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The verified Agent credential could not be saved");
|
|
13970
|
+
}
|
|
13971
|
+
return {
|
|
13972
|
+
serverUrl,
|
|
13973
|
+
...identity
|
|
13828
13974
|
};
|
|
13829
13975
|
}
|
|
13830
|
-
/**
|
|
13831
|
-
async function
|
|
13832
|
-
|
|
13833
|
-
|
|
13976
|
+
/** Load the caller-selected credential store and create a normal Raft client. */
|
|
13977
|
+
async function createRaftClientFromStore(options) {
|
|
13978
|
+
let record;
|
|
13979
|
+
try {
|
|
13980
|
+
record = await options.store.load();
|
|
13981
|
+
} catch {
|
|
13982
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential could not be loaded");
|
|
13983
|
+
}
|
|
13984
|
+
if (!record) throw new RaftCredentialError("CREDENTIAL_NOT_FOUND", "No stored Raft Agent credential was found");
|
|
13985
|
+
const validated = parseStoredRaftCredential(record);
|
|
13986
|
+
if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The stored Agent credential is invalid");
|
|
13987
|
+
if (!validated.scopes.includes("send")) throw new RaftCredentialError("CREDENTIAL_SCOPE_MISSING", "The stored Raft Agent credential does not include the send capability required by this SDK");
|
|
13988
|
+
return createRaftClient({
|
|
13989
|
+
serverUrl: validated.serverUrl,
|
|
13990
|
+
credential: validated.credential,
|
|
13991
|
+
fetch: options.fetch,
|
|
13992
|
+
headers: options.headers,
|
|
13993
|
+
retry: options.retry,
|
|
13994
|
+
throttle: options.throttle
|
|
13995
|
+
});
|
|
13834
13996
|
}
|
|
13997
|
+
//#endregion
|
|
13998
|
+
//#region src/fileCredentialStore.ts
|
|
13999
|
+
const MAX_CREDENTIAL_FILE_BYTES = 65536;
|
|
13835
14000
|
/**
|
|
13836
|
-
*
|
|
13837
|
-
*
|
|
13838
|
-
*
|
|
14001
|
+
* Create a Node.js credential store at one explicit absolute path. The SDK
|
|
14002
|
+
* never discovers CLI profiles or a home directory. Writes use a same-directory
|
|
14003
|
+
* temporary file followed by atomic rename, and the final file is mode 0600.
|
|
13839
14004
|
*/
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
constructor(store, frontier, onError) {
|
|
13851
|
-
this.store = store;
|
|
13852
|
-
this.onError = onError;
|
|
13853
|
-
this.frontier = frontier;
|
|
13854
|
-
}
|
|
13855
|
-
get persistent() {
|
|
13856
|
-
return this.store !== void 0;
|
|
13857
|
-
}
|
|
13858
|
-
/** The version loaded from (or last saved to) the store; `undefined` before the first save of an empty store. */
|
|
13859
|
-
get version() {
|
|
13860
|
-
return this.loadedVersion;
|
|
13861
|
-
}
|
|
13862
|
-
/** Load once. A failed or unreadable load starts from an empty state (reported, not thrown). */
|
|
13863
|
-
ensureLoaded() {
|
|
13864
|
-
if (!this.store) return Promise.resolve();
|
|
13865
|
-
this.loading ??= (async () => {
|
|
13866
|
-
let raw = null;
|
|
14005
|
+
function createFileCredentialStore(filePath) {
|
|
14006
|
+
if (!filePath.trim() || !isAbsolute(filePath)) throw new RaftSdkConfigurationError("INVALID_CREDENTIAL_PATH", "Raft credential file path must be an explicit absolute path");
|
|
14007
|
+
return {
|
|
14008
|
+
load: () => loadFileCredential(filePath),
|
|
14009
|
+
async save(record) {
|
|
14010
|
+
const validated = parseStoredRaftCredential(record);
|
|
14011
|
+
if (!validated) throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "Refusing to write an invalid Raft Agent credential record");
|
|
14012
|
+
const directory = dirname(filePath);
|
|
14013
|
+
const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
14014
|
+
let handle;
|
|
13867
14015
|
try {
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
phase: "load",
|
|
13872
|
-
version: void 0
|
|
14016
|
+
await mkdir(directory, {
|
|
14017
|
+
recursive: true,
|
|
14018
|
+
mode: 448
|
|
13873
14019
|
});
|
|
13874
|
-
|
|
14020
|
+
const existing = await loadFileCredential(filePath);
|
|
14021
|
+
if (existing) {
|
|
14022
|
+
assertSameCredential(existing, validated);
|
|
14023
|
+
return;
|
|
14024
|
+
}
|
|
14025
|
+
handle = await open(temporaryPath, "wx", 384);
|
|
14026
|
+
await handle.writeFile(`${JSON.stringify(validated, null, 2)}\n`, "utf8");
|
|
14027
|
+
await handle.sync();
|
|
14028
|
+
await chmod(temporaryPath, 384);
|
|
14029
|
+
await handle.close();
|
|
14030
|
+
handle = void 0;
|
|
14031
|
+
try {
|
|
14032
|
+
await link(temporaryPath, filePath);
|
|
14033
|
+
} catch (error) {
|
|
14034
|
+
if (error.code !== "EEXIST") throw error;
|
|
14035
|
+
const winner = await loadFileCredential(filePath);
|
|
14036
|
+
if (!winner) throw error;
|
|
14037
|
+
assertSameCredential(winner, validated);
|
|
14038
|
+
}
|
|
14039
|
+
await unlink(temporaryPath);
|
|
14040
|
+
} catch (error) {
|
|
14041
|
+
if (handle) await handle.close().catch(() => void 0);
|
|
14042
|
+
await unlink(temporaryPath).catch(() => void 0);
|
|
14043
|
+
if (error instanceof RaftCredentialError) throw error;
|
|
14044
|
+
throw new RaftCredentialError("CREDENTIAL_STORE_WRITE_FAILED", "The Raft Agent credential could not be written atomically");
|
|
13875
14045
|
}
|
|
13876
|
-
const state = parseRaftState(raw);
|
|
13877
|
-
if (!state) return;
|
|
13878
|
-
this.loadedVersion = state.version;
|
|
13879
|
-
this.cursor = state.cursor;
|
|
13880
|
-
this.pendingCursor = state.pendingCursor;
|
|
13881
|
-
this.frontier.absorb(state.frontier);
|
|
13882
|
-
this.continuations = state.continuations ?? [];
|
|
13883
|
-
})();
|
|
13884
|
-
return this.loading;
|
|
13885
|
-
}
|
|
13886
|
-
markDirty() {
|
|
13887
|
-
this.dirty = true;
|
|
13888
|
-
}
|
|
13889
|
-
/** Record the cursor of a batch just returned. Acknowledges nothing. */
|
|
13890
|
-
setPending(cursor) {
|
|
13891
|
-
if (cursor === null || cursor === this.pendingCursor) return;
|
|
13892
|
-
this.pendingCursor = cursor;
|
|
13893
|
-
this.dirty = true;
|
|
13894
|
-
}
|
|
13895
|
-
/**
|
|
13896
|
-
* Promote a cursor to committed. With no argument, commits `pendingCursor`.
|
|
13897
|
-
* Returns the committed cursor, or null when there was nothing to commit.
|
|
13898
|
-
* Never moves the committed cursor backwards.
|
|
13899
|
-
*/
|
|
13900
|
-
commit(cursor) {
|
|
13901
|
-
const next = cursor === void 0 ? this.pendingCursor : cursor;
|
|
13902
|
-
if (next === null || next === void 0) return null;
|
|
13903
|
-
if (this.cursor === null || next > this.cursor) this.cursor = next;
|
|
13904
|
-
if (this.pendingCursor !== null && this.pendingCursor <= this.cursor) this.pendingCursor = null;
|
|
13905
|
-
this.dirty = true;
|
|
13906
|
-
return this.cursor;
|
|
13907
|
-
}
|
|
13908
|
-
findContinuation(target, contentHash) {
|
|
13909
|
-
return this.continuations.find((c) => c.target === target && c.contentHash === contentHash);
|
|
13910
|
-
}
|
|
13911
|
-
rememberContinuation(entry) {
|
|
13912
|
-
this.continuations = this.continuations.filter((c) => !(c.target === entry.target && c.contentHash === entry.contentHash));
|
|
13913
|
-
this.continuations.push(entry);
|
|
13914
|
-
const perTarget = this.continuations.filter((c) => c.target === entry.target);
|
|
13915
|
-
for (const drop of perTarget.slice(0, Math.max(0, perTarget.length - 3))) this.continuations.splice(this.continuations.indexOf(drop), 1);
|
|
13916
|
-
if (this.continuations.length > 20) this.continuations.splice(0, this.continuations.length - 20);
|
|
13917
|
-
this.dirty = true;
|
|
13918
|
-
}
|
|
13919
|
-
forgetContinuation(target, contentHash) {
|
|
13920
|
-
const before = this.continuations.length;
|
|
13921
|
-
this.continuations = this.continuations.filter((c) => !(c.target === target && c.contentHash === contentHash));
|
|
13922
|
-
if (this.continuations.length !== before) this.dirty = true;
|
|
13923
|
-
}
|
|
13924
|
-
snapshot(nextVersion = (this.loadedVersion ?? 0) + 1) {
|
|
13925
|
-
return {
|
|
13926
|
-
schema: RAFT_STATE_SCHEMA,
|
|
13927
|
-
version: nextVersion,
|
|
13928
|
-
cursor: this.cursor,
|
|
13929
|
-
pendingCursor: this.pendingCursor,
|
|
13930
|
-
frontier: this.frontier.snapshot(),
|
|
13931
|
-
continuations: [...this.continuations]
|
|
13932
|
-
};
|
|
13933
|
-
}
|
|
13934
|
-
/**
|
|
13935
|
-
* Save if anything changed (or `force`). One attempt; a failure is reported
|
|
13936
|
-
* and swallowed. Returns whether the save succeeded (true when nothing to save).
|
|
13937
|
-
*/
|
|
13938
|
-
async save(options = {}) {
|
|
13939
|
-
if (!this.store) return true;
|
|
13940
|
-
if (!this.dirty && !options.force) return true;
|
|
13941
|
-
const expectedVersion = this.loadedVersion;
|
|
13942
|
-
const state = this.snapshot();
|
|
13943
|
-
try {
|
|
13944
|
-
await this.store.save(state, { expectedVersion });
|
|
13945
|
-
} catch (error) {
|
|
13946
|
-
this.onError?.(error, {
|
|
13947
|
-
phase: "save",
|
|
13948
|
-
version: expectedVersion
|
|
13949
|
-
});
|
|
13950
|
-
return false;
|
|
13951
14046
|
}
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
14047
|
+
};
|
|
14048
|
+
}
|
|
14049
|
+
async function loadFileCredential(filePath) {
|
|
14050
|
+
let initialMetadata;
|
|
14051
|
+
try {
|
|
14052
|
+
initialMetadata = await lstat(filePath);
|
|
14053
|
+
} catch (error) {
|
|
14054
|
+
if (error.code === "ENOENT") return null;
|
|
14055
|
+
throw fileReadError();
|
|
13955
14056
|
}
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
14057
|
+
if (!initialMetadata.isFile() || initialMetadata.isSymbolicLink() || initialMetadata.size > MAX_CREDENTIAL_FILE_BYTES) throw fileReadError();
|
|
14058
|
+
let parsed;
|
|
14059
|
+
let handle;
|
|
14060
|
+
try {
|
|
14061
|
+
const noFollow = process.platform === "win32" ? 0 : constants.O_NOFOLLOW;
|
|
14062
|
+
handle = await open(filePath, constants.O_RDONLY | noFollow);
|
|
14063
|
+
const openedMetadata = await handle.stat();
|
|
14064
|
+
if (!openedMetadata.isFile() || openedMetadata.size > MAX_CREDENTIAL_FILE_BYTES || initialMetadata.dev !== openedMetadata.dev || initialMetadata.ino !== openedMetadata.ino) throw fileReadError();
|
|
14065
|
+
if (process.platform !== "win32" && (openedMetadata.mode & 63) !== 0) throw new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file permissions are too broad; expected mode 0600");
|
|
14066
|
+
parsed = JSON.parse(await handle.readFile("utf8"));
|
|
14067
|
+
} catch (error) {
|
|
14068
|
+
if (error instanceof RaftCredentialError) throw error;
|
|
14069
|
+
throw fileReadError();
|
|
14070
|
+
} finally {
|
|
14071
|
+
await handle?.close().catch(() => void 0);
|
|
14072
|
+
}
|
|
14073
|
+
const record = parseStoredRaftCredential(parsed);
|
|
14074
|
+
if (!record) throw fileReadError();
|
|
14075
|
+
return record;
|
|
13963
14076
|
}
|
|
13964
|
-
|
|
13965
|
-
if (
|
|
13966
|
-
|
|
13967
|
-
|
|
13968
|
-
|
|
13969
|
-
target: request.target,
|
|
13970
|
-
messageId: result.data.messageId
|
|
13971
|
-
};
|
|
13972
|
-
return {
|
|
13973
|
-
ok: true,
|
|
13974
|
-
state: "prepared",
|
|
13975
|
-
data: card,
|
|
13976
|
-
next: {
|
|
13977
|
-
kind: "await_confirmation",
|
|
13978
|
-
command: `raft message read --target "${request.target}" --around ${card.messageId.slice(0, 8)}`,
|
|
13979
|
-
args: {
|
|
13980
|
-
target: request.target,
|
|
13981
|
-
messageId: card.messageId
|
|
13982
|
-
},
|
|
13983
|
-
why: "A human must click the card to commit it. You are not notified of the result yet; read the card message to see its state instead of waiting."
|
|
13984
|
-
},
|
|
13985
|
-
text: formatAgentActionCardPosted(request.target, card.messageId)
|
|
13986
|
-
};
|
|
14077
|
+
function assertSameCredential(existing, candidate) {
|
|
14078
|
+
if (existing.credential !== candidate.credential) throw new RaftCredentialError("CREDENTIAL_STORE_CONFLICT", "The credential store already contains a different Raft Agent credential");
|
|
14079
|
+
}
|
|
14080
|
+
function fileReadError() {
|
|
14081
|
+
return new RaftCredentialError("CREDENTIAL_STORE_READ_FAILED", "The Raft credential file is invalid or unreadable");
|
|
13987
14082
|
}
|
|
13988
14083
|
//#endregion
|
|
13989
14084
|
//#region src/latestReadThread.ts
|
|
@@ -14217,8 +14312,9 @@ function createRaft(options) {
|
|
|
14217
14312
|
load: () => session.ensureLoaded(),
|
|
14218
14313
|
snapshot: () => session.snapshot(session.version ?? 0)
|
|
14219
14314
|
},
|
|
14220
|
-
routes
|
|
14315
|
+
routes,
|
|
14316
|
+
runCommand: (request) => runCommand(routes, request)
|
|
14221
14317
|
};
|
|
14222
14318
|
}
|
|
14223
14319
|
//#endregion
|
|
14224
|
-
export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RAFT_STATE_CONTINUATIONS_PER_TARGET, RAFT_STATE_CONTINUATIONS_TOTAL, RAFT_STATE_SCHEMA, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, bootstrapRaftCredential, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, describeRaftRoute, hasAgentMessageIdentity, hashRaftSendContent, inferAttachmentMimeType, isInterrupted, joinRaftChannelByTarget, listRaftRoutes, parseRaftRegularChannelTarget, parseRaftState, projectRaftMessage, projectRaftMessages, readLatestReadThread, verifyInboxNotice };
|
|
14320
|
+
export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RAFT_STATE_CONTINUATIONS_PER_TARGET, RAFT_STATE_CONTINUATIONS_TOTAL, RAFT_STATE_SCHEMA, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, bootstrapRaftCredential, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, describeRaftRoute, hasAgentMessageIdentity, hashRaftSendContent, inferAttachmentMimeType, isInterrupted, joinRaftChannelByTarget, listRaftRoutes, parseRaftRegularChannelTarget, parseRaftState, projectRaftMessage, projectRaftMessages, readLatestReadThread, runCommand, verifyInboxNotice };
|