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