@botiverse/raft-sdk 0.6.0 → 0.8.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 +158 -4
- package/dist/cjs/index.cjs +1780 -195
- package/dist/esm/index.js +1741 -196
- package/dist/index.d.ts +1498 -68
- package/operations.json +2087 -0
- package/package.json +6 -3
package/dist/esm/index.js
CHANGED
|
@@ -3112,6 +3112,12 @@ const numberProcessor = (schema, ctx, _json, _params) => {
|
|
|
3112
3112
|
const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
3113
3113
|
json.type = "boolean";
|
|
3114
3114
|
};
|
|
3115
|
+
const bigintProcessor = (_schema, ctx, _json, _params) => {
|
|
3116
|
+
if (ctx.unrepresentable === "throw") throw new Error("BigInt cannot be represented in JSON Schema");
|
|
3117
|
+
};
|
|
3118
|
+
const symbolProcessor = (_schema, ctx, _json, _params) => {
|
|
3119
|
+
if (ctx.unrepresentable === "throw") throw new Error("Symbols cannot be represented in JSON Schema");
|
|
3120
|
+
};
|
|
3115
3121
|
const nullProcessor = (_schema, ctx, json, _params) => {
|
|
3116
3122
|
if (ctx.target === "openapi-3.0") {
|
|
3117
3123
|
json.type = "string";
|
|
@@ -3119,9 +3125,20 @@ const nullProcessor = (_schema, ctx, json, _params) => {
|
|
|
3119
3125
|
json.enum = [null];
|
|
3120
3126
|
} else json.type = "null";
|
|
3121
3127
|
};
|
|
3128
|
+
const undefinedProcessor = (_schema, ctx, _json, _params) => {
|
|
3129
|
+
if (ctx.unrepresentable === "throw") throw new Error("Undefined cannot be represented in JSON Schema");
|
|
3130
|
+
};
|
|
3131
|
+
const voidProcessor = (_schema, ctx, _json, _params) => {
|
|
3132
|
+
if (ctx.unrepresentable === "throw") throw new Error("Void cannot be represented in JSON Schema");
|
|
3133
|
+
};
|
|
3122
3134
|
const neverProcessor = (_schema, _ctx, json, _params) => {
|
|
3123
3135
|
json.not = {};
|
|
3124
3136
|
};
|
|
3137
|
+
const anyProcessor = (_schema, _ctx, _json, _params) => {};
|
|
3138
|
+
const unknownProcessor = (_schema, _ctx, _json, _params) => {};
|
|
3139
|
+
const dateProcessor = (_schema, ctx, _json, _params) => {
|
|
3140
|
+
if (ctx.unrepresentable === "throw") throw new Error("Date cannot be represented in JSON Schema");
|
|
3141
|
+
};
|
|
3125
3142
|
const enumProcessor = (schema, _ctx, json, _params) => {
|
|
3126
3143
|
const def = schema._zod.def;
|
|
3127
3144
|
const values = getEnumValues(def.entries);
|
|
@@ -3151,12 +3168,54 @@ const literalProcessor = (schema, ctx, json, _params) => {
|
|
|
3151
3168
|
json.enum = vals;
|
|
3152
3169
|
}
|
|
3153
3170
|
};
|
|
3171
|
+
const nanProcessor = (_schema, ctx, _json, _params) => {
|
|
3172
|
+
if (ctx.unrepresentable === "throw") throw new Error("NaN cannot be represented in JSON Schema");
|
|
3173
|
+
};
|
|
3174
|
+
const templateLiteralProcessor = (schema, _ctx, json, _params) => {
|
|
3175
|
+
const _json = json;
|
|
3176
|
+
const pattern = schema._zod.pattern;
|
|
3177
|
+
if (!pattern) throw new Error("Pattern not found in template literal");
|
|
3178
|
+
_json.type = "string";
|
|
3179
|
+
_json.pattern = pattern.source;
|
|
3180
|
+
};
|
|
3181
|
+
const fileProcessor = (schema, _ctx, json, _params) => {
|
|
3182
|
+
const _json = json;
|
|
3183
|
+
const file = {
|
|
3184
|
+
type: "string",
|
|
3185
|
+
format: "binary",
|
|
3186
|
+
contentEncoding: "binary"
|
|
3187
|
+
};
|
|
3188
|
+
const { minimum, maximum, mime } = schema._zod.bag;
|
|
3189
|
+
if (minimum !== void 0) file.minLength = minimum;
|
|
3190
|
+
if (maximum !== void 0) file.maxLength = maximum;
|
|
3191
|
+
if (mime) {
|
|
3192
|
+
if (mime.length === 1) {
|
|
3193
|
+
file.contentMediaType = mime[0];
|
|
3194
|
+
Object.assign(_json, file);
|
|
3195
|
+
} else {
|
|
3196
|
+
Object.assign(_json, file);
|
|
3197
|
+
_json.anyOf = mime.map((m) => ({ contentMediaType: m }));
|
|
3198
|
+
}
|
|
3199
|
+
} else Object.assign(_json, file);
|
|
3200
|
+
};
|
|
3201
|
+
const successProcessor = (_schema, _ctx, json, _params) => {
|
|
3202
|
+
json.type = "boolean";
|
|
3203
|
+
};
|
|
3154
3204
|
const customProcessor = (_schema, ctx, _json, _params) => {
|
|
3155
3205
|
if (ctx.unrepresentable === "throw") throw new Error("Custom types cannot be represented in JSON Schema");
|
|
3156
3206
|
};
|
|
3207
|
+
const functionProcessor = (_schema, ctx, _json, _params) => {
|
|
3208
|
+
if (ctx.unrepresentable === "throw") throw new Error("Function types cannot be represented in JSON Schema");
|
|
3209
|
+
};
|
|
3157
3210
|
const transformProcessor = (_schema, ctx, _json, _params) => {
|
|
3158
3211
|
if (ctx.unrepresentable === "throw") throw new Error("Transforms cannot be represented in JSON Schema");
|
|
3159
3212
|
};
|
|
3213
|
+
const mapProcessor = (_schema, ctx, _json, _params) => {
|
|
3214
|
+
if (ctx.unrepresentable === "throw") throw new Error("Map cannot be represented in JSON Schema");
|
|
3215
|
+
};
|
|
3216
|
+
const setProcessor = (_schema, ctx, _json, _params) => {
|
|
3217
|
+
if (ctx.unrepresentable === "throw") throw new Error("Set cannot be represented in JSON Schema");
|
|
3218
|
+
};
|
|
3160
3219
|
const arrayProcessor = (schema, ctx, _json, params) => {
|
|
3161
3220
|
const json = _json;
|
|
3162
3221
|
const def = schema._zod.def;
|
|
@@ -3233,6 +3292,44 @@ const intersectionProcessor = (schema, ctx, json, params) => {
|
|
|
3233
3292
|
const isSimpleIntersection = (val) => "allOf" in val && Object.keys(val).length === 1;
|
|
3234
3293
|
json.allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]];
|
|
3235
3294
|
};
|
|
3295
|
+
const tupleProcessor = (schema, ctx, _json, params) => {
|
|
3296
|
+
const json = _json;
|
|
3297
|
+
const def = schema._zod.def;
|
|
3298
|
+
json.type = "array";
|
|
3299
|
+
const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
|
|
3300
|
+
const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
|
|
3301
|
+
const prefixItems = def.items.map((x, i) => process$1(x, ctx, {
|
|
3302
|
+
...params,
|
|
3303
|
+
path: [
|
|
3304
|
+
...params.path,
|
|
3305
|
+
prefixPath,
|
|
3306
|
+
i
|
|
3307
|
+
]
|
|
3308
|
+
}));
|
|
3309
|
+
const rest = def.rest ? process$1(def.rest, ctx, {
|
|
3310
|
+
...params,
|
|
3311
|
+
path: [
|
|
3312
|
+
...params.path,
|
|
3313
|
+
restPath,
|
|
3314
|
+
...ctx.target === "openapi-3.0" ? [def.items.length] : []
|
|
3315
|
+
]
|
|
3316
|
+
}) : null;
|
|
3317
|
+
if (ctx.target === "draft-2020-12") {
|
|
3318
|
+
json.prefixItems = prefixItems;
|
|
3319
|
+
if (rest) json.items = rest;
|
|
3320
|
+
} else if (ctx.target === "openapi-3.0") {
|
|
3321
|
+
json.items = { anyOf: prefixItems };
|
|
3322
|
+
if (rest) json.items.anyOf.push(rest);
|
|
3323
|
+
json.minItems = prefixItems.length;
|
|
3324
|
+
if (!rest) json.maxItems = prefixItems.length;
|
|
3325
|
+
} else {
|
|
3326
|
+
json.items = prefixItems;
|
|
3327
|
+
if (rest) json.additionalItems = rest;
|
|
3328
|
+
}
|
|
3329
|
+
const { minimum, maximum } = schema._zod.bag;
|
|
3330
|
+
if (typeof minimum === "number") json.minItems = minimum;
|
|
3331
|
+
if (typeof maximum === "number") json.maxItems = maximum;
|
|
3332
|
+
};
|
|
3236
3333
|
const recordProcessor = (schema, ctx, _json, params) => {
|
|
3237
3334
|
const json = _json;
|
|
3238
3335
|
const def = schema._zod.def;
|
|
@@ -3322,12 +3419,99 @@ const readonlyProcessor = (schema, ctx, json, params) => {
|
|
|
3322
3419
|
seen.ref = def.innerType;
|
|
3323
3420
|
json.readOnly = true;
|
|
3324
3421
|
};
|
|
3422
|
+
const promiseProcessor = (schema, ctx, _json, params) => {
|
|
3423
|
+
const def = schema._zod.def;
|
|
3424
|
+
process$1(def.innerType, ctx, params);
|
|
3425
|
+
const seen = ctx.seen.get(schema);
|
|
3426
|
+
seen.ref = def.innerType;
|
|
3427
|
+
};
|
|
3325
3428
|
const optionalProcessor = (schema, ctx, _json, params) => {
|
|
3326
3429
|
const def = schema._zod.def;
|
|
3327
3430
|
process$1(def.innerType, ctx, params);
|
|
3328
3431
|
const seen = ctx.seen.get(schema);
|
|
3329
3432
|
seen.ref = def.innerType;
|
|
3330
3433
|
};
|
|
3434
|
+
const lazyProcessor = (schema, ctx, _json, params) => {
|
|
3435
|
+
const innerType = schema._zod.innerType;
|
|
3436
|
+
process$1(innerType, ctx, params);
|
|
3437
|
+
const seen = ctx.seen.get(schema);
|
|
3438
|
+
seen.ref = innerType;
|
|
3439
|
+
};
|
|
3440
|
+
const allProcessors = {
|
|
3441
|
+
string: stringProcessor,
|
|
3442
|
+
number: numberProcessor,
|
|
3443
|
+
boolean: booleanProcessor,
|
|
3444
|
+
bigint: bigintProcessor,
|
|
3445
|
+
symbol: symbolProcessor,
|
|
3446
|
+
null: nullProcessor,
|
|
3447
|
+
undefined: undefinedProcessor,
|
|
3448
|
+
void: voidProcessor,
|
|
3449
|
+
never: neverProcessor,
|
|
3450
|
+
any: anyProcessor,
|
|
3451
|
+
unknown: unknownProcessor,
|
|
3452
|
+
date: dateProcessor,
|
|
3453
|
+
enum: enumProcessor,
|
|
3454
|
+
literal: literalProcessor,
|
|
3455
|
+
nan: nanProcessor,
|
|
3456
|
+
template_literal: templateLiteralProcessor,
|
|
3457
|
+
file: fileProcessor,
|
|
3458
|
+
success: successProcessor,
|
|
3459
|
+
custom: customProcessor,
|
|
3460
|
+
function: functionProcessor,
|
|
3461
|
+
transform: transformProcessor,
|
|
3462
|
+
map: mapProcessor,
|
|
3463
|
+
set: setProcessor,
|
|
3464
|
+
array: arrayProcessor,
|
|
3465
|
+
object: objectProcessor,
|
|
3466
|
+
union: unionProcessor,
|
|
3467
|
+
intersection: intersectionProcessor,
|
|
3468
|
+
tuple: tupleProcessor,
|
|
3469
|
+
record: recordProcessor,
|
|
3470
|
+
nullable: nullableProcessor,
|
|
3471
|
+
nonoptional: nonoptionalProcessor,
|
|
3472
|
+
default: defaultProcessor,
|
|
3473
|
+
prefault: prefaultProcessor,
|
|
3474
|
+
catch: catchProcessor,
|
|
3475
|
+
pipe: pipeProcessor,
|
|
3476
|
+
readonly: readonlyProcessor,
|
|
3477
|
+
promise: promiseProcessor,
|
|
3478
|
+
optional: optionalProcessor,
|
|
3479
|
+
lazy: lazyProcessor
|
|
3480
|
+
};
|
|
3481
|
+
function toJSONSchema(input, params) {
|
|
3482
|
+
if ("_idmap" in input) {
|
|
3483
|
+
const registry = input;
|
|
3484
|
+
const ctx = initializeContext({
|
|
3485
|
+
...params,
|
|
3486
|
+
processors: allProcessors
|
|
3487
|
+
});
|
|
3488
|
+
const defs = {};
|
|
3489
|
+
for (const entry of registry._idmap.entries()) {
|
|
3490
|
+
const [_, schema] = entry;
|
|
3491
|
+
process$1(schema, ctx);
|
|
3492
|
+
}
|
|
3493
|
+
const schemas = {};
|
|
3494
|
+
ctx.external = {
|
|
3495
|
+
registry,
|
|
3496
|
+
uri: params?.uri,
|
|
3497
|
+
defs
|
|
3498
|
+
};
|
|
3499
|
+
for (const entry of registry._idmap.entries()) {
|
|
3500
|
+
const [key, schema] = entry;
|
|
3501
|
+
extractDefs(ctx, schema);
|
|
3502
|
+
schemas[key] = finalize(ctx, schema);
|
|
3503
|
+
}
|
|
3504
|
+
if (Object.keys(defs).length > 0) schemas.__shared = { [ctx.target === "draft-2020-12" ? "$defs" : "definitions"]: defs };
|
|
3505
|
+
return { schemas };
|
|
3506
|
+
}
|
|
3507
|
+
const ctx = initializeContext({
|
|
3508
|
+
...params,
|
|
3509
|
+
processors: allProcessors
|
|
3510
|
+
});
|
|
3511
|
+
process$1(input, ctx);
|
|
3512
|
+
extractDefs(ctx, input);
|
|
3513
|
+
return finalize(ctx, input);
|
|
3514
|
+
}
|
|
3331
3515
|
//#endregion
|
|
3332
3516
|
//#region ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/iso.js
|
|
3333
3517
|
const ZodISODateTime = /*@__PURE__*/ $constructor("ZodISODateTime", (inst, def) => {
|
|
@@ -7621,7 +7805,7 @@ async function joinRaftChannelByTarget(client, request) {
|
|
|
7621
7805
|
//#endregion
|
|
7622
7806
|
//#region src/events.ts
|
|
7623
7807
|
const sequence = number$1().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
7624
|
-
const requestSchema = object({
|
|
7808
|
+
const requestSchema$1 = object({
|
|
7625
7809
|
since: union([sequence, literal("latest")]).optional(),
|
|
7626
7810
|
limit: number$1().int().min(1).max(200).optional(),
|
|
7627
7811
|
ack: literal("cursor").optional()
|
|
@@ -7677,7 +7861,7 @@ function failure$2(code, status, cursorAck = false) {
|
|
|
7677
7861
|
}
|
|
7678
7862
|
/** Internal adapter. The caller must supply a single-attempt transport: receive drains the returned batch. */
|
|
7679
7863
|
async function receiveRaftEvents(client, request = {}) {
|
|
7680
|
-
const parsed = requestSchema.safeParse(request);
|
|
7864
|
+
const parsed = requestSchema$1.safeParse(request);
|
|
7681
7865
|
if (!parsed.success) return failure$2("INVALID_REQUEST");
|
|
7682
7866
|
const cursorAck = parsed.data.ack === "cursor";
|
|
7683
7867
|
const result = await client.events.get({
|
|
@@ -10266,7 +10450,8 @@ const DEFAULT_MESSAGES = {
|
|
|
10266
10450
|
CONFLICT: "The Raft Server refused the operation because of the current state.",
|
|
10267
10451
|
IDEMPOTENCY_KEY_REUSED: "This idempotency key was already used for a different message.",
|
|
10268
10452
|
UNSUPPORTED_FOR_EXTERNAL_AGENTS: "This operation is not available to External Agents.",
|
|
10269
|
-
UNAVAILABLE: "The Raft Server could not serve this operation right now."
|
|
10453
|
+
UNAVAILABLE: "The Raft Server could not serve this operation right now.",
|
|
10454
|
+
MODEL_ONLY: "This operation only counts when the model sees its result, so it cannot be run from code; nothing was sent."
|
|
10270
10455
|
};
|
|
10271
10456
|
const DEFAULT_NEXT_ACTION = {
|
|
10272
10457
|
INVALID_REQUEST: "Fix the request arguments; nothing was sent.",
|
|
@@ -10279,7 +10464,8 @@ const DEFAULT_NEXT_ACTION = {
|
|
|
10279
10464
|
CONFLICT: "Read the current state before repeating this operation.",
|
|
10280
10465
|
IDEMPOTENCY_KEY_REUSED: "Use a new idempotency key for a different message, or resend the identical payload to reconcile.",
|
|
10281
10466
|
UNSUPPORTED_FOR_EXTERNAL_AGENTS: "Use your own runtime for this; the Server does not provide it to External Agents.",
|
|
10282
|
-
UNAVAILABLE: "Retry in a moment."
|
|
10467
|
+
UNAVAILABLE: "Retry in a moment.",
|
|
10468
|
+
MODEL_ONLY: "Call it as a model tool call instead of from code."
|
|
10283
10469
|
};
|
|
10284
10470
|
const RETRYABLE = {
|
|
10285
10471
|
INVALID_REQUEST: false,
|
|
@@ -10292,7 +10478,8 @@ const RETRYABLE = {
|
|
|
10292
10478
|
CONFLICT: false,
|
|
10293
10479
|
IDEMPOTENCY_KEY_REUSED: false,
|
|
10294
10480
|
UNSUPPORTED_FOR_EXTERNAL_AGENTS: false,
|
|
10295
|
-
UNAVAILABLE: true
|
|
10481
|
+
UNAVAILABLE: true,
|
|
10482
|
+
MODEL_ONLY: false
|
|
10296
10483
|
};
|
|
10297
10484
|
function classifyHttp(status, serverCode) {
|
|
10298
10485
|
const code = serverCode?.toLowerCase() ?? "";
|
|
@@ -10368,6 +10555,22 @@ function failureOutcome(error) {
|
|
|
10368
10555
|
function failureFromClientResult(result) {
|
|
10369
10556
|
return failureOutcome(opErrorFromClientError(result.error, result.status));
|
|
10370
10557
|
}
|
|
10558
|
+
const MAX_REPORTED_ISSUES = 3;
|
|
10559
|
+
/**
|
|
10560
|
+
* Validate an operation's input with its zod request schema. Returns null when
|
|
10561
|
+
* it is valid, otherwise an `INVALID_REQUEST` failure whose message names the
|
|
10562
|
+
* offending fields and what was expected (zod's issue text, which never
|
|
10563
|
+
* echoes the input value); nothing is sent.
|
|
10564
|
+
*/
|
|
10565
|
+
function validateOpRequest(schema, value) {
|
|
10566
|
+
const parsed = schema.safeParse(value);
|
|
10567
|
+
if (parsed.success) return null;
|
|
10568
|
+
const issues = parsed.error.issues.slice(0, MAX_REPORTED_ISSUES).map((issue) => {
|
|
10569
|
+
return `${issue.path.map((part) => typeof part === "number" ? `[${part}]` : String(part)).join(".").replace(/\.\[/g, "[") || "request"}: ${issue.message}`;
|
|
10570
|
+
});
|
|
10571
|
+
const more = parsed.error.issues.length > MAX_REPORTED_ISSUES ? ` (+${parsed.error.issues.length - MAX_REPORTED_ISSUES} more)` : "";
|
|
10572
|
+
return failureOutcome(opError("INVALID_REQUEST", { message: `Invalid request: ${issues.join("; ")}${more}. Nothing was sent.` }));
|
|
10573
|
+
}
|
|
10371
10574
|
//#endregion
|
|
10372
10575
|
//#region ../shared/src/agentInbox.ts
|
|
10373
10576
|
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.]";
|
|
@@ -11001,50 +11204,75 @@ const MAX_EXACT_SEQS = 2500;
|
|
|
11001
11204
|
function positiveInt(value) {
|
|
11002
11205
|
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
11003
11206
|
}
|
|
11207
|
+
function contextOrNull(value) {
|
|
11208
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
11209
|
+
}
|
|
11004
11210
|
var SeenFrontier = class SeenFrontier {
|
|
11005
|
-
|
|
11006
|
-
|
|
11211
|
+
core = {
|
|
11212
|
+
targets: /* @__PURE__ */ new Map(),
|
|
11213
|
+
aliases: /* @__PURE__ */ new Map(),
|
|
11214
|
+
context: null
|
|
11215
|
+
};
|
|
11216
|
+
/** Set on a view from `inContext`: the context this view books and attests under. */
|
|
11217
|
+
bound = void 0;
|
|
11007
11218
|
static fromSnapshot(snapshot) {
|
|
11008
11219
|
const frontier = new SeenFrontier();
|
|
11009
11220
|
frontier.absorb(snapshot);
|
|
11010
11221
|
return frontier;
|
|
11011
11222
|
}
|
|
11012
|
-
/**
|
|
11223
|
+
/** The model context this frontier books and attests under; null means no context scoping. */
|
|
11224
|
+
get contextId() {
|
|
11225
|
+
return this.bound !== void 0 ? this.bound : this.core.context;
|
|
11226
|
+
}
|
|
11227
|
+
/**
|
|
11228
|
+
* Set the current model context (shared by every view of this frontier that
|
|
11229
|
+
* is not bound to its own context). Call it when the model's context
|
|
11230
|
+
* changes (a new session, a compaction): reads booked under the previous
|
|
11231
|
+
* context then no longer attest a send. `null` turns scoping off.
|
|
11232
|
+
*/
|
|
11233
|
+
setContext(contextId) {
|
|
11234
|
+
this.core.context = contextOrNull(contextId);
|
|
11235
|
+
}
|
|
11236
|
+
/**
|
|
11237
|
+
* A view over the same records that books and attests under `contextId`
|
|
11238
|
+
* (for one call made on behalf of that model context), without changing the
|
|
11239
|
+
* frontier's current context. `undefined` returns this frontier.
|
|
11240
|
+
*/
|
|
11241
|
+
inContext(contextId) {
|
|
11242
|
+
if (contextId === void 0) return this;
|
|
11243
|
+
const view = new SeenFrontier();
|
|
11244
|
+
view.core = this.core;
|
|
11245
|
+
view.bound = contextOrNull(contextId);
|
|
11246
|
+
return view;
|
|
11247
|
+
}
|
|
11248
|
+
/**
|
|
11249
|
+
* Merge a snapshot into this frontier: each record is booked under the
|
|
11250
|
+
* context it was booked in (same context: monotonic, never lowers a mark).
|
|
11251
|
+
* Adopts the snapshot's current context when this frontier has none.
|
|
11252
|
+
*/
|
|
11013
11253
|
absorb(snapshot) {
|
|
11014
11254
|
if (!snapshot || snapshot.version !== 1) return;
|
|
11015
11255
|
for (const [alias, canonical] of Object.entries(snapshot.aliases ?? {})) this.recordAlias(alias, canonical);
|
|
11016
11256
|
for (const [target, state] of Object.entries(snapshot.targets ?? {})) {
|
|
11017
|
-
if (positiveInt(state.upTo)) this.
|
|
11018
|
-
if (Array.isArray(state.exact)) this.
|
|
11257
|
+
if (positiveInt(state.upTo)) this.bookUpTo(target, state.upTo, contextOrNull(state.upToContextId));
|
|
11258
|
+
if (Array.isArray(state.exact)) this.bookExact(target, state.exact, contextOrNull(state.exactContextId));
|
|
11019
11259
|
}
|
|
11260
|
+
if (this.core.context === null) this.core.context = contextOrNull(snapshot.contextId);
|
|
11020
11261
|
}
|
|
11021
11262
|
/** Remember that the Server resolved `requested` to `canonical` (a thread spelling, a `~agent` suffix, …). */
|
|
11022
11263
|
recordAlias(requested, canonical) {
|
|
11023
|
-
if (requested && canonical && requested !== canonical) this.aliases.set(requested, canonical);
|
|
11264
|
+
if (requested && canonical && requested !== canonical) this.core.aliases.set(requested, canonical);
|
|
11024
11265
|
}
|
|
11025
11266
|
canonical(target) {
|
|
11026
|
-
return this.aliases.get(target) ?? target;
|
|
11267
|
+
return this.core.aliases.get(target) ?? target;
|
|
11027
11268
|
}
|
|
11028
|
-
/** Advance the contiguous boundary for a target; never lowers it. */
|
|
11269
|
+
/** Advance the contiguous boundary for a target in the current context; never lowers it within one context. */
|
|
11029
11270
|
recordUpTo(target, seq) {
|
|
11030
|
-
|
|
11031
|
-
const state = this.state(this.canonical(target));
|
|
11032
|
-
if (state.upTo === void 0 || seq > state.upTo) state.upTo = seq;
|
|
11033
|
-
for (const exact of state.exact) if (exact <= state.upTo) state.exact.delete(exact);
|
|
11271
|
+
this.bookUpTo(target, seq, this.contextId);
|
|
11034
11272
|
}
|
|
11035
|
-
/** Record bodies that were rendered without proving contiguity. */
|
|
11273
|
+
/** Record bodies that were rendered without proving contiguity, in the current context. */
|
|
11036
11274
|
recordExact(target, seqs) {
|
|
11037
|
-
|
|
11038
|
-
for (const seq of seqs) {
|
|
11039
|
-
if (!positiveInt(seq)) continue;
|
|
11040
|
-
if (state.upTo !== void 0 && seq <= state.upTo) continue;
|
|
11041
|
-
state.exact.add(seq);
|
|
11042
|
-
}
|
|
11043
|
-
if (state.exact.size > MAX_EXACT_SEQS) {
|
|
11044
|
-
const keep = [...state.exact].sort((a, b) => b - a).slice(0, MAX_EXACT_SEQS);
|
|
11045
|
-
state.exact.clear();
|
|
11046
|
-
for (const seq of keep) state.exact.add(seq);
|
|
11047
|
-
}
|
|
11275
|
+
this.bookExact(target, seqs, this.contextId);
|
|
11048
11276
|
}
|
|
11049
11277
|
/**
|
|
11050
11278
|
* Attest that the model saw the context of an interrupt (`outcome.interrupt`
|
|
@@ -11059,32 +11287,75 @@ var SeenFrontier = class SeenFrontier {
|
|
|
11059
11287
|
this.recordUpTo(held.target, held.seenUpToSeq);
|
|
11060
11288
|
return true;
|
|
11061
11289
|
}
|
|
11062
|
-
/**
|
|
11290
|
+
/**
|
|
11291
|
+
* What to attest on a send or claim to `target`; omits `seenUpToSeq` when
|
|
11292
|
+
* nothing contiguous is known. With a current context, only bookings made
|
|
11293
|
+
* in that context count.
|
|
11294
|
+
*/
|
|
11063
11295
|
attestation(target) {
|
|
11064
|
-
const state = this.targets.get(this.canonical(target));
|
|
11296
|
+
const state = this.core.targets.get(this.canonical(target));
|
|
11065
11297
|
if (!state) return { seenExactSeqs: [] };
|
|
11298
|
+
const context = this.contextId;
|
|
11299
|
+
const upTo = context === null || state.upToContext === context ? state.upTo : void 0;
|
|
11300
|
+
const exact = context === null || state.exactContext === context ? [...state.exact] : [];
|
|
11066
11301
|
return {
|
|
11067
|
-
...
|
|
11068
|
-
seenExactSeqs:
|
|
11302
|
+
...upTo === void 0 ? {} : { seenUpToSeq: upTo },
|
|
11303
|
+
seenExactSeqs: exact.filter((seq) => upTo === void 0 || seq > upTo).sort((a, b) => a - b)
|
|
11069
11304
|
};
|
|
11070
11305
|
}
|
|
11071
11306
|
snapshot() {
|
|
11072
11307
|
const targets = {};
|
|
11073
|
-
for (const [target, state] of this.targets) targets[target] = {
|
|
11308
|
+
for (const [target, state] of this.core.targets) targets[target] = {
|
|
11074
11309
|
...state.upTo === void 0 ? {} : { upTo: state.upTo },
|
|
11075
|
-
...state.exact.size === 0 ? {} : { exact: [...state.exact].sort((a, b) => a - b) }
|
|
11310
|
+
...state.exact.size === 0 ? {} : { exact: [...state.exact].sort((a, b) => a - b) },
|
|
11311
|
+
...state.upTo === void 0 || state.upToContext === null ? {} : { upToContextId: state.upToContext },
|
|
11312
|
+
...state.exact.size === 0 || state.exactContext === null ? {} : { exactContextId: state.exactContext }
|
|
11076
11313
|
};
|
|
11077
11314
|
return {
|
|
11078
11315
|
version: 1,
|
|
11079
11316
|
targets,
|
|
11080
|
-
aliases: Object.fromEntries(this.aliases)
|
|
11317
|
+
aliases: Object.fromEntries(this.core.aliases),
|
|
11318
|
+
...this.core.context === null ? {} : { contextId: this.core.context }
|
|
11081
11319
|
};
|
|
11082
11320
|
}
|
|
11321
|
+
/** A high-water booking (seenPolicy `bookStreamEntries`): another context's record is replaced, not merged. */
|
|
11322
|
+
bookUpTo(target, seq, context) {
|
|
11323
|
+
if (!positiveInt(seq)) return;
|
|
11324
|
+
const state = this.state(this.canonical(target));
|
|
11325
|
+
if (state.exact.size > 0 && state.exactContext !== context) state.exact.clear();
|
|
11326
|
+
if (state.upTo === void 0 || state.upToContext !== context || seq > state.upTo) state.upTo = seq;
|
|
11327
|
+
state.upToContext = context;
|
|
11328
|
+
state.exactContext = context;
|
|
11329
|
+
for (const exact of state.exact) if (exact <= state.upTo) state.exact.delete(exact);
|
|
11330
|
+
}
|
|
11331
|
+
/** A sparse booking (seenPolicy `bookExactSeqs`): merges within a context, replaces across; never moves `upTo`. */
|
|
11332
|
+
bookExact(target, seqs, context) {
|
|
11333
|
+
const state = this.state(this.canonical(target));
|
|
11334
|
+
if (state.exactContext !== context) {
|
|
11335
|
+
state.exact.clear();
|
|
11336
|
+
state.exactContext = context;
|
|
11337
|
+
}
|
|
11338
|
+
const floor = state.upToContext === context ? state.upTo : void 0;
|
|
11339
|
+
for (const seq of seqs) {
|
|
11340
|
+
if (!positiveInt(seq)) continue;
|
|
11341
|
+
if (floor !== void 0 && seq <= floor) continue;
|
|
11342
|
+
state.exact.add(seq);
|
|
11343
|
+
}
|
|
11344
|
+
if (state.exact.size > MAX_EXACT_SEQS) {
|
|
11345
|
+
const keep = [...state.exact].sort((a, b) => b - a).slice(0, MAX_EXACT_SEQS);
|
|
11346
|
+
state.exact.clear();
|
|
11347
|
+
for (const seq of keep) state.exact.add(seq);
|
|
11348
|
+
}
|
|
11349
|
+
}
|
|
11083
11350
|
state(target) {
|
|
11084
|
-
let state = this.targets.get(target);
|
|
11351
|
+
let state = this.core.targets.get(target);
|
|
11085
11352
|
if (!state) {
|
|
11086
|
-
state = {
|
|
11087
|
-
|
|
11353
|
+
state = {
|
|
11354
|
+
upToContext: null,
|
|
11355
|
+
exact: /* @__PURE__ */ new Set(),
|
|
11356
|
+
exactContext: null
|
|
11357
|
+
};
|
|
11358
|
+
this.core.targets.set(target, state);
|
|
11088
11359
|
}
|
|
11089
11360
|
return state;
|
|
11090
11361
|
}
|
|
@@ -11157,7 +11428,18 @@ function latestReadThreadFromCliReadState(raw) {
|
|
|
11157
11428
|
};
|
|
11158
11429
|
}
|
|
11159
11430
|
//#endregion
|
|
11431
|
+
//#region ../shared/src/agentOps/requestSchema.ts
|
|
11432
|
+
function requestSchema() {
|
|
11433
|
+
return (schema) => schema;
|
|
11434
|
+
}
|
|
11435
|
+
//#endregion
|
|
11160
11436
|
//#region ../shared/src/agentOps/inbox.ts
|
|
11437
|
+
const inboxPullFields = {
|
|
11438
|
+
since: number$1().int().nonnegative().optional().describe("Cursor of the previous batch; under cursor acks it acknowledges that batch. Omit to continue from the last committed cursor."),
|
|
11439
|
+
limit: number$1().int().positive().optional().describe("Messages per batch, 1..200 (Server default 50)."),
|
|
11440
|
+
ack: _enum(["cursor", "immediate"]).optional().describe("cursor (default): a batch is acknowledged by the next pull; immediate: acknowledged as it is returned.")
|
|
11441
|
+
};
|
|
11442
|
+
const checkInboxRequestSchema = requestSchema()(object(inboxPullFields));
|
|
11161
11443
|
const MAX_DRAIN_ROUNDS = 50;
|
|
11162
11444
|
function batchNext(batch) {
|
|
11163
11445
|
if (batch.hasMore) return {
|
|
@@ -11196,6 +11478,8 @@ function recordExactSeen(frontier, messages) {
|
|
|
11196
11478
|
}
|
|
11197
11479
|
/** One bounded pull. Records exact seen seqs on `frontier` (sparse drains never advance the high-water mark). */
|
|
11198
11480
|
async function checkInbox(client, request = {}, frontier) {
|
|
11481
|
+
const invalid = validateOpRequest(checkInboxRequestSchema, request);
|
|
11482
|
+
if (invalid) return invalid;
|
|
11199
11483
|
const ack = request.ack ?? "cursor";
|
|
11200
11484
|
const result = await client.events.get({
|
|
11201
11485
|
since: request.since === void 0 ? "latest" : String(request.since),
|
|
@@ -11222,6 +11506,7 @@ async function checkInbox(client, request = {}, frontier) {
|
|
|
11222
11506
|
text: batchText(batch)
|
|
11223
11507
|
};
|
|
11224
11508
|
}
|
|
11509
|
+
const drainInboxRequestSchema = requestSchema()(object(inboxPullFields));
|
|
11225
11510
|
/**
|
|
11226
11511
|
* Pull until the Server reports nothing more, like `raft message check`, but
|
|
11227
11512
|
* as an async iterator so that acknowledgement follows consumption: under
|
|
@@ -11234,6 +11519,15 @@ async function checkInbox(client, request = {}, frontier) {
|
|
|
11234
11519
|
*/
|
|
11235
11520
|
async function* drainInbox(client, request = {}, frontier, onPull) {
|
|
11236
11521
|
const ack = request.ack ?? "cursor";
|
|
11522
|
+
const invalid = validateOpRequest(drainInboxRequestSchema, request);
|
|
11523
|
+
if (invalid) return {
|
|
11524
|
+
cursor: null,
|
|
11525
|
+
ackMode: ack,
|
|
11526
|
+
rounds: 0,
|
|
11527
|
+
hasMore: true,
|
|
11528
|
+
stillUnreadConversations: null,
|
|
11529
|
+
error: invalid
|
|
11530
|
+
};
|
|
11237
11531
|
let cursor = request.since ?? null;
|
|
11238
11532
|
let ackMode = ack;
|
|
11239
11533
|
let stillUnread = null;
|
|
@@ -11294,6 +11588,47 @@ async function* drainInbox(client, request = {}, frontier, onPull) {
|
|
|
11294
11588
|
error: null
|
|
11295
11589
|
};
|
|
11296
11590
|
}
|
|
11591
|
+
/** Fold a finished drain into one outcome; a drain that failed before its first batch is that failure. */
|
|
11592
|
+
function drainedInboxOutcome(batches, summary) {
|
|
11593
|
+
if (summary.error && batches.length === 0) return summary.error;
|
|
11594
|
+
const messages = batches.flatMap((batch) => batch.messages);
|
|
11595
|
+
const lines = [formatAgentMessages(messages.map((m) => toAgentMessageLike(m.raw)))];
|
|
11596
|
+
if (summary.error) lines.push("The drain stopped early on an error; more messages may be pending. Check the inbox again.");
|
|
11597
|
+
else if (summary.hasMore) lines.push("More messages are pending. Check the inbox again.");
|
|
11598
|
+
else if (messages.length > 0) lines.push("No more new inbox messages.");
|
|
11599
|
+
const stillUnread = summary.stillUnreadConversations ?? 0;
|
|
11600
|
+
if (stillUnread > 0) lines.push(formatAgentInboxHint({ unread_conversations: stillUnread }));
|
|
11601
|
+
const first = messages[0];
|
|
11602
|
+
const next = summary.hasMore ? {
|
|
11603
|
+
kind: "check_inbox_again",
|
|
11604
|
+
command: "raft message check",
|
|
11605
|
+
why: "More messages are pending."
|
|
11606
|
+
} : stillUnread > 0 ? {
|
|
11607
|
+
kind: "list_inbox",
|
|
11608
|
+
command: "raft inbox check",
|
|
11609
|
+
why: "Conversations remain unread beyond this drain."
|
|
11610
|
+
} : first ? {
|
|
11611
|
+
kind: "reply_or_act",
|
|
11612
|
+
args: { target: first.target },
|
|
11613
|
+
why: "Handle the messages above; reply where each one came from."
|
|
11614
|
+
} : null;
|
|
11615
|
+
return {
|
|
11616
|
+
ok: true,
|
|
11617
|
+
state: messages.length > 0 ? "batch" : "empty",
|
|
11618
|
+
data: {
|
|
11619
|
+
messages,
|
|
11620
|
+
batches: batches.length,
|
|
11621
|
+
summary
|
|
11622
|
+
},
|
|
11623
|
+
next,
|
|
11624
|
+
text: lines.join("\n")
|
|
11625
|
+
};
|
|
11626
|
+
}
|
|
11627
|
+
const listInboxRequestSchema = requestSchema()(object({
|
|
11628
|
+
view: _enum(["unread", "mentions"]).optional().describe("unread (default): every unread conversation; mentions: only those that @mention you."),
|
|
11629
|
+
before: number$1().int().nonnegative().optional().describe("Next page: the nextBeforeSeq of the previous page."),
|
|
11630
|
+
limit: number$1().int().positive().optional().describe("Conversations per page, 1..50 (Server default 20).")
|
|
11631
|
+
}));
|
|
11297
11632
|
function openCommandFor(item) {
|
|
11298
11633
|
return `raft message read --target "${item.target}" --after ${item.lastReadSeq}`;
|
|
11299
11634
|
}
|
|
@@ -11329,6 +11664,8 @@ function listingText(listing) {
|
|
|
11329
11664
|
}
|
|
11330
11665
|
/** The agent's Activity panel. Lists only; nothing is consumed. */
|
|
11331
11666
|
async function listInbox(client, request = {}) {
|
|
11667
|
+
const invalid = validateOpRequest(listInboxRequestSchema, request);
|
|
11668
|
+
if (invalid) return invalid;
|
|
11332
11669
|
const result = await client.inbox.list({
|
|
11333
11670
|
...request.view && request.view !== "unread" ? { view: request.view } : {},
|
|
11334
11671
|
...request.before === void 0 ? {} : { before_seq: String(request.before) },
|
|
@@ -11363,6 +11700,16 @@ async function listInbox(client, request = {}) {
|
|
|
11363
11700
|
}
|
|
11364
11701
|
//#endregion
|
|
11365
11702
|
//#region ../shared/src/agentOps/messages.ts
|
|
11703
|
+
/** A conversation target as agents spell it. */
|
|
11704
|
+
const raftTargetSchema = string().describe("Conversation: `#channel`, `dm:@peer`, or a thread `#channel:shortid` / `dm:@peer:shortid`.");
|
|
11705
|
+
const readHistoryRequestSchema = requestSchema()(object({
|
|
11706
|
+
target: raftTargetSchema,
|
|
11707
|
+
after: number$1().int().nonnegative().optional().describe("Only messages after this seq (exclusive): read what is new since a position, for example the `--after` an inbox listing prints."),
|
|
11708
|
+
before: number$1().int().nonnegative().optional().describe("Only messages before this seq (exclusive): browse older history."),
|
|
11709
|
+
around: union([number$1().int(), string()]).optional().describe("Anchor a window around a message: its seq number (for example \"12345\") or its message id."),
|
|
11710
|
+
limit: number$1().int().positive().optional().describe("Maximum messages in the window."),
|
|
11711
|
+
consume: boolean().optional().describe("false reads without consuming: the conversation is not marked read and nothing counts as seen.")
|
|
11712
|
+
}));
|
|
11366
11713
|
function historyNext(page) {
|
|
11367
11714
|
const seqs = page.messages.map((m) => m.seq).filter((s) => s !== null);
|
|
11368
11715
|
if (page.hasNewer && seqs.length > 0) {
|
|
@@ -11415,6 +11762,8 @@ function recordHistorySeen(frontier, requested, page, around) {
|
|
|
11415
11762
|
frontier.recordExact(page.target, seqs);
|
|
11416
11763
|
}
|
|
11417
11764
|
async function readHistory(client, request, frontier) {
|
|
11765
|
+
const invalid = validateOpRequest(readHistoryRequestSchema, request);
|
|
11766
|
+
if (invalid) return invalid;
|
|
11418
11767
|
if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target is required to read history." }));
|
|
11419
11768
|
const result = await client.history.read({
|
|
11420
11769
|
channel: request.target,
|
|
@@ -11445,6 +11794,24 @@ async function readHistory(client, request, frontier) {
|
|
|
11445
11794
|
}
|
|
11446
11795
|
/** Attempts for a keyed send whose request never reached the Server (transport failure only). */
|
|
11447
11796
|
const SEND_TRANSPORT_ATTEMPTS = 3;
|
|
11797
|
+
const sendMessageFields = {
|
|
11798
|
+
content: string().describe("Message text (Markdown). May be empty only when attachmentIds are given."),
|
|
11799
|
+
attachmentIds: array(string()).optional().describe("Attachment ids from an upload, posted with this message."),
|
|
11800
|
+
idempotencyKey: string().optional().describe("One key per logical message; generated when omitted. Reuse it to retry, and to resume an interrupted send (interrupt.resume.idempotencyKey)."),
|
|
11801
|
+
mentions: array(agentApiStructuredMentionSchema).optional().describe("Structured @mentions (user or agent id and name), in addition to @handles in the text."),
|
|
11802
|
+
seen: object({
|
|
11803
|
+
upToSeq: number$1().int().nonnegative().optional(),
|
|
11804
|
+
exactSeqs: array(number$1().int().positive()).optional()
|
|
11805
|
+
}).optional().describe("Override the seen attestation for this send (runtimes that track what their model saw themselves).")
|
|
11806
|
+
};
|
|
11807
|
+
const sendMessageRequestSchema = requestSchema()(object({
|
|
11808
|
+
target: raftTargetSchema,
|
|
11809
|
+
...sendMessageFields
|
|
11810
|
+
}));
|
|
11811
|
+
const replyToRequestSchema = requestSchema()(object({
|
|
11812
|
+
message: object({ target: raftTargetSchema }).describe("The received message to reply to; only its `target` is used."),
|
|
11813
|
+
...sendMessageFields
|
|
11814
|
+
}));
|
|
11448
11815
|
/** Today's held text (unchanged); it is the interrupt's `context` and the outcome's `text`. */
|
|
11449
11816
|
function heldText(held, action) {
|
|
11450
11817
|
const noun = held.newMessageCount === 1 ? "message" : "messages";
|
|
@@ -11508,6 +11875,8 @@ function sentOutcome(target, data) {
|
|
|
11508
11875
|
};
|
|
11509
11876
|
}
|
|
11510
11877
|
async function sendMessage(client, request, frontier) {
|
|
11878
|
+
const invalid = validateOpRequest(sendMessageRequestSchema, request);
|
|
11879
|
+
if (invalid) return invalid;
|
|
11511
11880
|
if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A target is required to send a message." }));
|
|
11512
11881
|
if (typeof request.content !== "string" || !request.content.trim() && !request.attachmentIds?.length) return failureOutcome(opError("INVALID_REQUEST", { message: "Message content (or an attachment) is required." }));
|
|
11513
11882
|
const idempotencyKey = request.idempotencyKey?.trim() || crypto.randomUUID();
|
|
@@ -11709,9 +12078,39 @@ function formatAgentTaskHistory(data) {
|
|
|
11709
12078
|
return `seq=${event.seq} time=${event.createdAt} actor=${actor} type=${event.eventType}\n ${JSON.stringify(event.payload)}`;
|
|
11710
12079
|
}).join("\n")}`;
|
|
11711
12080
|
}
|
|
12081
|
+
/**
|
|
12082
|
+
* `raft task show`: one task's current title and description (moved verbatim
|
|
12083
|
+
* from the CLI's commands/task/show.ts). Labels match the agent delivery
|
|
12084
|
+
* surface (`Current title:` / `Current description:`). `description` has three
|
|
12085
|
+
* states that must not collapse: a string, an explicit null, and a field the
|
|
12086
|
+
* envelope omitted.
|
|
12087
|
+
*/
|
|
12088
|
+
function formatAgentTaskShow(target, task) {
|
|
12089
|
+
const descriptionLine = typeof task.description === "string" ? `Current description: ${task.description}` : task.description === null ? "Current description: (none set)" : "Current description: (not returned by this surface)";
|
|
12090
|
+
return [
|
|
12091
|
+
`#${task.taskNumber} [${task.status ?? "unknown"}] in ${target}`,
|
|
12092
|
+
`Current title: ${task.title ?? "(none set)"}`,
|
|
12093
|
+
descriptionLine,
|
|
12094
|
+
""
|
|
12095
|
+
].join("\n");
|
|
12096
|
+
}
|
|
11712
12097
|
//#endregion
|
|
11713
12098
|
//#region ../shared/src/agentOps/tasks.ts
|
|
12099
|
+
const taskChannelSchema = string().describe("The channel the task board belongs to, for example `#proj-sdk`.");
|
|
12100
|
+
const taskNumberSchema = number$1().int().positive().describe("The task number on that channel's board.");
|
|
12101
|
+
const raftTaskStatusSchema = _enum([
|
|
12102
|
+
"todo",
|
|
12103
|
+
"in_progress",
|
|
12104
|
+
"in_review",
|
|
12105
|
+
"done",
|
|
12106
|
+
"closed"
|
|
12107
|
+
]);
|
|
11714
12108
|
const TASK_CLAIM_REASON_ALREADY_CLAIMED_BY_YOU = "already claimed by you";
|
|
12109
|
+
const claimTasksRequestSchema = requestSchema()(object({
|
|
12110
|
+
target: taskChannelSchema,
|
|
12111
|
+
taskNumbers: array(number$1().int().positive()).optional().describe("Task numbers to claim."),
|
|
12112
|
+
messageIds: array(string()).optional().describe("Full or short ids of top-level messages to claim as tasks.")
|
|
12113
|
+
}));
|
|
11715
12114
|
/**
|
|
11716
12115
|
* A held task call: the interrupt whose resume is the identical command, with
|
|
11717
12116
|
* no cancel (a held task call saved nothing). `context` is today's held text.
|
|
@@ -11760,6 +12159,8 @@ function claimText(claim) {
|
|
|
11760
12159
|
async function claimTasks(client, request) {
|
|
11761
12160
|
const numbers = (request.taskNumbers ?? []).filter((n) => Number.isInteger(n) && n > 0);
|
|
11762
12161
|
const ids = (request.messageIds ?? []).map((id) => id.trim()).filter(Boolean);
|
|
12162
|
+
const invalid = validateOpRequest(claimTasksRequestSchema, request);
|
|
12163
|
+
if (invalid) return invalid;
|
|
11763
12164
|
if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target is required to claim tasks." }));
|
|
11764
12165
|
if (numbers.length === 0 && ids.length === 0) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass at least one task number or message id to claim." }));
|
|
11765
12166
|
const result = await client.tasks.claim({
|
|
@@ -11811,7 +12212,21 @@ async function claimTasks(client, request) {
|
|
|
11811
12212
|
text: claimText(claim)
|
|
11812
12213
|
};
|
|
11813
12214
|
}
|
|
12215
|
+
const listTasksRequestSchema = requestSchema()(object({
|
|
12216
|
+
target: string().optional().describe("A channel's board, for example `#proj-sdk`. Pass exactly one of target or mine."),
|
|
12217
|
+
mine: boolean().optional().describe("true: your own tasks across channels instead of one board."),
|
|
12218
|
+
status: _enum([
|
|
12219
|
+
"todo",
|
|
12220
|
+
"in_progress",
|
|
12221
|
+
"in_review",
|
|
12222
|
+
"done",
|
|
12223
|
+
"closed",
|
|
12224
|
+
"all"
|
|
12225
|
+
]).optional().describe("Filter by status; all includes done and closed.")
|
|
12226
|
+
}));
|
|
11814
12227
|
async function listTasks(client, request) {
|
|
12228
|
+
const invalid = validateOpRequest(listTasksRequestSchema, request);
|
|
12229
|
+
if (invalid) return invalid;
|
|
11815
12230
|
const mine = request.mine === true;
|
|
11816
12231
|
if (mine === Boolean(request.target?.trim())) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass exactly one of a channel target or mine: true." }));
|
|
11817
12232
|
const result = await client.tasks.list({
|
|
@@ -11845,7 +12260,17 @@ async function listTasks(client, request) {
|
|
|
11845
12260
|
text
|
|
11846
12261
|
};
|
|
11847
12262
|
}
|
|
12263
|
+
const createTasksRequestSchema = requestSchema()(object({
|
|
12264
|
+
target: taskChannelSchema,
|
|
12265
|
+
tasks: array(object({
|
|
12266
|
+
title: string().describe("Task title."),
|
|
12267
|
+
createsResource: boolean().optional().describe("The task produces a resource (for example a document) that needs a receipt.")
|
|
12268
|
+
})).describe("One entry per task to create."),
|
|
12269
|
+
assignee: string().optional().describe("`@handle`: yourself to start in_progress, or (owner/admin) someone else to reserve a todo.")
|
|
12270
|
+
}));
|
|
11848
12271
|
async function createTasks(client, request) {
|
|
12272
|
+
const invalid = validateOpRequest(createTasksRequestSchema, request);
|
|
12273
|
+
if (invalid) return invalid;
|
|
11849
12274
|
if (!request.target?.trim() || !request.tasks?.length) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target and at least one task title are required." }));
|
|
11850
12275
|
const result = await client.tasks.create({
|
|
11851
12276
|
channel: request.target,
|
|
@@ -11874,7 +12299,14 @@ async function createTasks(client, request) {
|
|
|
11874
12299
|
text: formatAgentTasksCreated(request.target, data)
|
|
11875
12300
|
};
|
|
11876
12301
|
}
|
|
11877
|
-
|
|
12302
|
+
const taskRefFields = {
|
|
12303
|
+
target: taskChannelSchema,
|
|
12304
|
+
taskNumber: taskNumberSchema
|
|
12305
|
+
};
|
|
12306
|
+
const taskRefSchema = requestSchema()(object(taskRefFields));
|
|
12307
|
+
function requireTaskRef(request, schema = taskRefSchema) {
|
|
12308
|
+
const invalid = validateOpRequest(schema, request);
|
|
12309
|
+
if (invalid) return invalid;
|
|
11878
12310
|
if (!request.target?.trim() || !Number.isInteger(request.taskNumber) || request.taskNumber <= 0) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target and a positive task number are required." }));
|
|
11879
12311
|
return null;
|
|
11880
12312
|
}
|
|
@@ -11894,8 +12326,13 @@ async function unclaimTask(client, request) {
|
|
|
11894
12326
|
text: formatAgentTaskUnclaimed(request.taskNumber)
|
|
11895
12327
|
};
|
|
11896
12328
|
}
|
|
12329
|
+
const assignTaskRequestSchema = requestSchema()(object({
|
|
12330
|
+
...taskRefFields,
|
|
12331
|
+
assignee: string().trim().min(1).describe("`@handle` to assign (yourself, or someone else if you are an owner/admin). To clear an assignment use tasks.unassign."),
|
|
12332
|
+
expectedRevision: number$1().int().nonnegative().optional().describe("Revision of the task you just read; the change is refused if it moved.")
|
|
12333
|
+
}));
|
|
11897
12334
|
async function assignTask(client, request) {
|
|
11898
|
-
const invalid = requireTaskRef(request);
|
|
12335
|
+
const invalid = requireTaskRef(request, assignTaskRequestSchema);
|
|
11899
12336
|
if (invalid) return invalid;
|
|
11900
12337
|
const result = await client.tasks.assign({
|
|
11901
12338
|
channel: request.target,
|
|
@@ -11918,6 +12355,38 @@ async function assignTask(client, request) {
|
|
|
11918
12355
|
text: formatAgentTaskAssigned(request.taskNumber, assignee)
|
|
11919
12356
|
};
|
|
11920
12357
|
}
|
|
12358
|
+
const unassignTaskRequestSchema = requestSchema()(object({
|
|
12359
|
+
...taskRefFields,
|
|
12360
|
+
expectedRevision: number$1().int().nonnegative().optional().describe("Revision of the task you just read; the change is refused if it moved.")
|
|
12361
|
+
}));
|
|
12362
|
+
/** Clear a task's assignee (the CLI's `raft task unassign`): the explicit way to clear, never an omitted `assignee`. */
|
|
12363
|
+
async function unassignTask(client, request) {
|
|
12364
|
+
const invalid = requireTaskRef(request, unassignTaskRequestSchema);
|
|
12365
|
+
if (invalid) return invalid;
|
|
12366
|
+
const result = await client.tasks.assign({
|
|
12367
|
+
channel: request.target,
|
|
12368
|
+
task_number: request.taskNumber,
|
|
12369
|
+
assignee: null,
|
|
12370
|
+
...request.expectedRevision === void 0 ? {} : { expected_revision: request.expectedRevision }
|
|
12371
|
+
});
|
|
12372
|
+
if (!result.ok) return failureFromClientResult(result);
|
|
12373
|
+
return {
|
|
12374
|
+
ok: true,
|
|
12375
|
+
state: "unassigned",
|
|
12376
|
+
data: {
|
|
12377
|
+
target: request.target,
|
|
12378
|
+
taskNumber: request.taskNumber,
|
|
12379
|
+
assignee: null,
|
|
12380
|
+
revision: result.data.revision
|
|
12381
|
+
},
|
|
12382
|
+
next: null,
|
|
12383
|
+
text: formatAgentTaskAssigned(request.taskNumber, null)
|
|
12384
|
+
};
|
|
12385
|
+
}
|
|
12386
|
+
const updateTaskStatusRequestSchema = requestSchema()(object({
|
|
12387
|
+
...taskRefFields,
|
|
12388
|
+
status: raftTaskStatusSchema.describe("todo → in_progress → in_review → done; closed from anywhere.")
|
|
12389
|
+
}));
|
|
11921
12390
|
/** A held task write: the interrupt whose resume is the identical command; nothing to cancel. */
|
|
11922
12391
|
function heldTaskWrite(target, data, action, kind, argv) {
|
|
11923
12392
|
const interrupt = heldTaskInterrupt(target, data, action, "After reviewing, repeat the operation if it is still correct.", argv);
|
|
@@ -11935,7 +12404,7 @@ function heldTaskWrite(target, data, action, kind, argv) {
|
|
|
11935
12404
|
};
|
|
11936
12405
|
}
|
|
11937
12406
|
async function updateTaskStatus(client, request) {
|
|
11938
|
-
const invalid = requireTaskRef(request);
|
|
12407
|
+
const invalid = requireTaskRef(request, updateTaskStatusRequestSchema);
|
|
11939
12408
|
if (invalid) return invalid;
|
|
11940
12409
|
const result = await client.tasks.updateStatus({
|
|
11941
12410
|
channel: request.target,
|
|
@@ -11955,6 +12424,11 @@ async function updateTaskStatus(client, request) {
|
|
|
11955
12424
|
text: formatAgentTaskStatusUpdated(request.taskNumber, request.status)
|
|
11956
12425
|
};
|
|
11957
12426
|
}
|
|
12427
|
+
const amendTaskRequestSchema = requestSchema()(object({
|
|
12428
|
+
...taskRefFields,
|
|
12429
|
+
title: string().optional().describe("New title."),
|
|
12430
|
+
description: string().nullable().optional().describe("New description; null clears it.")
|
|
12431
|
+
}));
|
|
11958
12432
|
function taskAmendArgv(request) {
|
|
11959
12433
|
return [
|
|
11960
12434
|
"task",
|
|
@@ -11968,7 +12442,7 @@ function taskAmendArgv(request) {
|
|
|
11968
12442
|
];
|
|
11969
12443
|
}
|
|
11970
12444
|
async function amendTask(client, request) {
|
|
11971
|
-
const invalid = requireTaskRef(request);
|
|
12445
|
+
const invalid = requireTaskRef(request, amendTaskRequestSchema);
|
|
11972
12446
|
if (invalid) return invalid;
|
|
11973
12447
|
if (request.title === void 0 && request.description === void 0) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass a new title, a new description, or description: null to clear it." }));
|
|
11974
12448
|
const result = await client.tasks.amend({
|
|
@@ -12009,7 +12483,45 @@ async function taskHistory(client, request) {
|
|
|
12009
12483
|
text: formatAgentTaskHistory(result.data)
|
|
12010
12484
|
};
|
|
12011
12485
|
}
|
|
12486
|
+
/**
|
|
12487
|
+
* `raft task show`: one task's current title and description. Reads the
|
|
12488
|
+
* channel's whole board (`status: "all"`, so done and closed tasks are found)
|
|
12489
|
+
* and picks the task; a miss says whether the Server asserted the list is
|
|
12490
|
+
* complete, exactly as the CLI does.
|
|
12491
|
+
*/
|
|
12492
|
+
async function showTask(client, request) {
|
|
12493
|
+
const invalid = requireTaskRef(request);
|
|
12494
|
+
if (invalid) return invalid;
|
|
12495
|
+
const { target, taskNumber } = request;
|
|
12496
|
+
const result = await client.tasks.list({
|
|
12497
|
+
channel: target,
|
|
12498
|
+
status: "all"
|
|
12499
|
+
});
|
|
12500
|
+
if (!result.ok) return failureFromClientResult(result);
|
|
12501
|
+
const tasks = result.data.tasks ?? [];
|
|
12502
|
+
const task = tasks.find((candidate) => candidate.taskNumber === taskNumber);
|
|
12503
|
+
if (!task) return failureOutcome(opError("NOT_FOUND", {
|
|
12504
|
+
message: result.data.pagination?.mode === "complete" && result.data.pagination.truncated === false ? `task #${taskNumber} not found in ${target} (searched ${tasks.length} task(s), status=all; server asserts this list is complete)` : tasks.length === 0 ? `task #${taskNumber} not found in ${target}: the server returned 0 tasks and did not assert the list is complete. That can mean the channel has no tasks, or that the server failed to read them (it currently reports some read errors as an empty list), and this command cannot tell which — so this is not evidence that task #${taskNumber} does not exist` : `task #${taskNumber} not found in ${target} (searched ${tasks.length} task(s), status=all; this surface does NOT assert completeness — so this is "absent from what was returned", not "does not exist")`,
|
|
12505
|
+
nextAction: `Check the number with \`raft task list --target "${target}" --status all\`.`
|
|
12506
|
+
}));
|
|
12507
|
+
return {
|
|
12508
|
+
ok: true,
|
|
12509
|
+
state: "task",
|
|
12510
|
+
data: {
|
|
12511
|
+
target,
|
|
12512
|
+
task
|
|
12513
|
+
},
|
|
12514
|
+
next: null,
|
|
12515
|
+
text: formatAgentTaskShow(target, task)
|
|
12516
|
+
};
|
|
12517
|
+
}
|
|
12518
|
+
const convertMessageToTaskRequestSchema = requestSchema()(object({
|
|
12519
|
+
target: taskChannelSchema,
|
|
12520
|
+
messageId: string().describe("Full or short id of a top-level message in that channel.")
|
|
12521
|
+
}));
|
|
12012
12522
|
async function convertMessageToTask(client, request) {
|
|
12523
|
+
const invalid = validateOpRequest(convertMessageToTaskRequestSchema, request);
|
|
12524
|
+
if (invalid) return invalid;
|
|
12013
12525
|
if (!request.target?.trim() || !request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel target and a message id are required." }));
|
|
12014
12526
|
const result = await client.tasks.convert({
|
|
12015
12527
|
channel: request.target,
|
|
@@ -12331,6 +12843,30 @@ function formatPageFooter(page) {
|
|
|
12331
12843
|
if (page.nextCommand && end < page.total) lines.push(`More: ${page.nextCommand}`);
|
|
12332
12844
|
return `${lines.join("\n")}\n`;
|
|
12333
12845
|
}
|
|
12846
|
+
/** Single channel detail block. */
|
|
12847
|
+
function formatAgentChannelInfo(channel, memberCounts) {
|
|
12848
|
+
const lines = ["## Channel", ""];
|
|
12849
|
+
lines.push(`Channel: ${agentChannelRef(channel.name, channel.type)}`);
|
|
12850
|
+
if (channel.id) lines.push(`ID: ${channel.id}`);
|
|
12851
|
+
lines.push(`Visibility: ${channelVisibility(channel)}`);
|
|
12852
|
+
lines.push(`Joined: ${channel.joined ? "yes" : "no"}`);
|
|
12853
|
+
if (channel.channelRole) lines.push(`Channel role: ${channel.channelRole}`);
|
|
12854
|
+
if (channel.channelAdminBasis) lines.push(`Channel admin basis: ${channel.channelAdminBasis}`);
|
|
12855
|
+
const callableCapabilities = Object.entries(channel.channelCapabilities ?? {}).filter(([, allowed]) => allowed).map(([capability]) => capability);
|
|
12856
|
+
if (callableCapabilities.length > 0) lines.push(`Channel capabilities: ${callableCapabilities.join(", ")}`);
|
|
12857
|
+
const muted = channelMuted(channel);
|
|
12858
|
+
if (muted !== void 0) lines.push(`Muted: ${muted ? "yes" : "no"}`);
|
|
12859
|
+
if (typeof channel.archived === "boolean") lines.push(`Archived: ${channel.archived ? "yes" : "no"}`);
|
|
12860
|
+
lines.push(`Description: ${channel.description?.trim() || "(none)"}`);
|
|
12861
|
+
if (memberCounts) {
|
|
12862
|
+
const agents = memberCounts.agents ?? 0;
|
|
12863
|
+
const humans = memberCounts.humans ?? 0;
|
|
12864
|
+
lines.push(`Members: ${agents + humans} (${agents} agents, ${humans} humans)`);
|
|
12865
|
+
}
|
|
12866
|
+
lines.push("");
|
|
12867
|
+
lines.push(`More: raft channel members "${agentChannelRef(channel.name, channel.type)}"`);
|
|
12868
|
+
return `${lines.join("\n")}\n`;
|
|
12869
|
+
}
|
|
12334
12870
|
/** Compact server summary. */
|
|
12335
12871
|
function formatAgentServerSummary(data) {
|
|
12336
12872
|
const channels = data.channels ?? [];
|
|
@@ -12397,6 +12933,23 @@ function formatAgentServerHumans(humans, page) {
|
|
|
12397
12933
|
}
|
|
12398
12934
|
return `${lines.join("\n")}${formatPageFooter(page)}`;
|
|
12399
12935
|
}
|
|
12936
|
+
/** Narrow visible facts for one user/agent. */
|
|
12937
|
+
function formatAgentUserInfo(user, memberships, page, skippedChannels = 0) {
|
|
12938
|
+
const name = user.value.name;
|
|
12939
|
+
const role = roleLabel(user.value.role);
|
|
12940
|
+
const lines = ["## User", ""];
|
|
12941
|
+
lines.push(`User: @${name}`);
|
|
12942
|
+
lines.push(`Kind: ${user.kind}`);
|
|
12943
|
+
if (user.kind === "agent") lines.push(`Status: ${agentStatusLabel(user.value)}`);
|
|
12944
|
+
if (role) lines.push(`Role: ${role.slice(2, -1)}`);
|
|
12945
|
+
if (user.value.description) lines.push(`Description: ${user.value.description}`);
|
|
12946
|
+
lines.push("");
|
|
12947
|
+
lines.push("### Visible Channel Memberships");
|
|
12948
|
+
if (memberships.length === 0) lines.push("(none found in inspected visible channels)");
|
|
12949
|
+
else for (const channel of memberships) lines.push(`${agentChannelRef(channel.name, channel.type)} [${channelStatus(channel)}]`);
|
|
12950
|
+
if (skippedChannels > 0) lines.push(`Skipped ${skippedChannels} visible channel roster checks because the server rejected them.`);
|
|
12951
|
+
return `${lines.join("\n")}${formatPageFooter(page)}`;
|
|
12952
|
+
}
|
|
12400
12953
|
/** Channel membership with server-role labels. */
|
|
12401
12954
|
function formatAgentChannelMembers(data) {
|
|
12402
12955
|
let text = "## Channel Members\n\n";
|
|
@@ -12605,6 +13158,15 @@ function formatAgentProfile(profile, options = {}) {
|
|
|
12605
13158
|
}
|
|
12606
13159
|
//#endregion
|
|
12607
13160
|
//#region ../shared/src/agentOps/channels.ts
|
|
13161
|
+
/** `{ target }` of a regular channel (join, leave, mute, unmute). */
|
|
13162
|
+
const channelTargetRequestSchema = requestSchema()(object({ target: string().describe("A regular channel, `#channel-name`. DMs and threads are not accepted.") }));
|
|
13163
|
+
const channelMembersRequestSchema = requestSchema()(object({ target: string().describe("A channel, DM, or thread target.") }));
|
|
13164
|
+
/** `threads.list` takes no arguments. */
|
|
13165
|
+
const listThreadsRequestSchema = object({});
|
|
13166
|
+
const unfollowThreadRequestSchema = requestSchema()(object({
|
|
13167
|
+
target: string().describe("The thread, for example `#channel:shortid` or `dm:@peer:shortid`."),
|
|
13168
|
+
reason: string().optional().describe("Short reason, kept with the unfollow.")
|
|
13169
|
+
}));
|
|
12608
13170
|
const INVALID_CHANNEL_TARGET = "Target must be a regular channel in the form '#channel-name'. DMs and thread targets are not supported.";
|
|
12609
13171
|
async function resolveRegularChannel(client, target) {
|
|
12610
13172
|
const name = parseRaftRegularChannelTarget$1(target ?? "");
|
|
@@ -12623,6 +13185,8 @@ async function resolveRegularChannel(client, target) {
|
|
|
12623
13185
|
};
|
|
12624
13186
|
}
|
|
12625
13187
|
async function joinChannel(client, request) {
|
|
13188
|
+
const invalid = validateOpRequest(channelTargetRequestSchema, request);
|
|
13189
|
+
if (invalid) return invalid;
|
|
12626
13190
|
const result = await joinRaftChannelByTarget$1(client, { target: request.target });
|
|
12627
13191
|
if (!result.ok) {
|
|
12628
13192
|
const error = result.error;
|
|
@@ -12656,6 +13220,8 @@ async function joinChannel(client, request) {
|
|
|
12656
13220
|
};
|
|
12657
13221
|
}
|
|
12658
13222
|
async function leaveChannel(client, request) {
|
|
13223
|
+
const invalid = validateOpRequest(channelTargetRequestSchema, request);
|
|
13224
|
+
if (invalid) return invalid;
|
|
12659
13225
|
const channel = await resolveRegularChannel(client, request.target);
|
|
12660
13226
|
if ("ok" in channel) return channel;
|
|
12661
13227
|
if (!channel.joined) return {
|
|
@@ -12688,6 +13254,8 @@ function formatSeq(value) {
|
|
|
12688
13254
|
return value == null ? "none" : String(value);
|
|
12689
13255
|
}
|
|
12690
13256
|
async function setChannelMute(client, request, action) {
|
|
13257
|
+
const invalid = validateOpRequest(channelTargetRequestSchema, request);
|
|
13258
|
+
if (invalid) return invalid;
|
|
12691
13259
|
const channel = await resolveRegularChannel(client, request.target);
|
|
12692
13260
|
if ("ok" in channel) return channel;
|
|
12693
13261
|
const result = action === "mute" ? await client.channels.mute({ channelId: channel.id }, {}) : await client.channels.unmute({ channelId: channel.id });
|
|
@@ -12730,6 +13298,8 @@ function unmuteChannel(client, request) {
|
|
|
12730
13298
|
return setChannelMute(client, request, "unmute");
|
|
12731
13299
|
}
|
|
12732
13300
|
async function channelMembers(client, request) {
|
|
13301
|
+
const invalid = validateOpRequest(channelMembersRequestSchema, request);
|
|
13302
|
+
if (invalid) return invalid;
|
|
12733
13303
|
if (!request.target?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A channel, DM, or thread target is required." }));
|
|
12734
13304
|
const result = await client.channels.members({ channel: request.target });
|
|
12735
13305
|
if (!result.ok) return failureFromClientResult(result);
|
|
@@ -12754,6 +13324,8 @@ async function listThreads(client) {
|
|
|
12754
13324
|
};
|
|
12755
13325
|
}
|
|
12756
13326
|
async function unfollowThread(client, request) {
|
|
13327
|
+
const invalid = validateOpRequest(unfollowThreadRequestSchema, request);
|
|
13328
|
+
if (invalid) return invalid;
|
|
12757
13329
|
if (!request.target?.includes(":")) return failureOutcome(opError("INVALID_REQUEST", { message: "A thread target is required, for example '#channel:shortid' or 'dm:@peer:shortid'." }));
|
|
12758
13330
|
const result = await client.threads.unfollow({
|
|
12759
13331
|
thread: request.target,
|
|
@@ -12768,8 +13340,58 @@ async function unfollowThread(client, request) {
|
|
|
12768
13340
|
text: `Unfollowed ${request.target}. Ordinary delivery from this thread stops; a direct @mention reactivates the follow.`
|
|
12769
13341
|
};
|
|
12770
13342
|
}
|
|
13343
|
+
const channelInfoRequestSchema = requestSchema()(object({ target: string().describe("A regular channel, `#channel-name` (the `#` may be omitted). DMs and threads are not accepted.") }));
|
|
13344
|
+
/**
|
|
13345
|
+
* `raft channel info <target>`: the channel's facts from `server.info`, plus
|
|
13346
|
+
* member counts from its roster when the Server shows it.
|
|
13347
|
+
*/
|
|
13348
|
+
async function channelInfo(client, request) {
|
|
13349
|
+
const invalid = validateOpRequest(channelInfoRequestSchema, request);
|
|
13350
|
+
if (invalid) return invalid;
|
|
13351
|
+
const trimmed = request.target.trim();
|
|
13352
|
+
const input = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
|
|
13353
|
+
const name = parseRaftRegularChannelTarget$1(input);
|
|
13354
|
+
if (!name) return failureOutcome(opError("INVALID_REQUEST", { message: "Target must be a regular channel name, e.g. '#engineering' or 'engineering'. DMs and thread targets are not supported." }));
|
|
13355
|
+
const info = await client.server.info();
|
|
13356
|
+
if (!info.ok) return failureFromClientResult(info);
|
|
13357
|
+
const channel = info.data.channels.find((candidate) => candidate.name === name);
|
|
13358
|
+
if (!channel) return failureOutcome(opError("NOT_FOUND", {
|
|
13359
|
+
message: `Channel not found or not visible: ${input}`,
|
|
13360
|
+
nextAction: "Run `raft server info --channels --query <name>` to inspect visible channels, or ask a channel member to add you if this is private."
|
|
13361
|
+
}));
|
|
13362
|
+
const members = await client.channels.members({ channel: `#${name}` });
|
|
13363
|
+
const memberCounts = members.ok ? {
|
|
13364
|
+
agents: members.data.agents?.length ?? 0,
|
|
13365
|
+
humans: members.data.humans?.length ?? 0
|
|
13366
|
+
} : null;
|
|
13367
|
+
return {
|
|
13368
|
+
ok: true,
|
|
13369
|
+
state: "info",
|
|
13370
|
+
data: {
|
|
13371
|
+
channel,
|
|
13372
|
+
memberCounts
|
|
13373
|
+
},
|
|
13374
|
+
next: null,
|
|
13375
|
+
text: formatAgentChannelInfo(channel, memberCounts)
|
|
13376
|
+
};
|
|
13377
|
+
}
|
|
12771
13378
|
//#endregion
|
|
12772
13379
|
//#region ../shared/src/agentOps/server.ts
|
|
13380
|
+
const serverInfoRequestSchema = requestSchema()(object({
|
|
13381
|
+
view: _enum([
|
|
13382
|
+
"summary",
|
|
13383
|
+
"full",
|
|
13384
|
+
"channels",
|
|
13385
|
+
"agents",
|
|
13386
|
+
"humans"
|
|
13387
|
+
]).optional().describe("summary (default): counts; full: the whole overview; channels / agents / humans: one paged section."),
|
|
13388
|
+
offset: number$1().optional().describe("Section paging: rows to skip."),
|
|
13389
|
+
limit: number$1().optional().describe("Section paging: rows per page (default 50)."),
|
|
13390
|
+
joined: boolean().optional().describe("channels view only: only channels you have joined.")
|
|
13391
|
+
}));
|
|
13392
|
+
const showProfileRequestSchema = requestSchema()(object({ target: string().optional().describe("`@handle` of someone else; omit for your own profile.") }));
|
|
13393
|
+
/** The Agent API's profile body schema; at least one field is required (checked by the operation). */
|
|
13394
|
+
const updateProfileRequestSchema = agentApiProfileUpdateBodySchema;
|
|
12773
13395
|
function nextCommand(section, request, offset, limit, total) {
|
|
12774
13396
|
const nextOffset = offset + limit;
|
|
12775
13397
|
if (nextOffset >= total) return void 0;
|
|
@@ -12783,6 +13405,8 @@ function nextCommand(section, request, offset, limit, total) {
|
|
|
12783
13405
|
return parts.join(" ");
|
|
12784
13406
|
}
|
|
12785
13407
|
async function serverInfo(client, request = {}) {
|
|
13408
|
+
const invalid = validateOpRequest(serverInfoRequestSchema, request);
|
|
13409
|
+
if (invalid) return invalid;
|
|
12786
13410
|
const result = await client.server.info();
|
|
12787
13411
|
if (!result.ok) return failureFromClientResult(result);
|
|
12788
13412
|
const server = result.data;
|
|
@@ -12848,6 +13472,8 @@ async function serverInfo(client, request = {}) {
|
|
|
12848
13472
|
};
|
|
12849
13473
|
}
|
|
12850
13474
|
async function showProfile(client, request = {}, options = {}) {
|
|
13475
|
+
const invalid = validateOpRequest(showProfileRequestSchema, request);
|
|
13476
|
+
if (invalid) return invalid;
|
|
12851
13477
|
const result = await client.profile.show(request.target ? { target: request.target } : {});
|
|
12852
13478
|
if (!result.ok) return failureFromClientResult(result);
|
|
12853
13479
|
return {
|
|
@@ -12859,6 +13485,8 @@ async function showProfile(client, request = {}, options = {}) {
|
|
|
12859
13485
|
};
|
|
12860
13486
|
}
|
|
12861
13487
|
async function updateProfile(client, request, options = {}) {
|
|
13488
|
+
const invalid = validateOpRequest(updateProfileRequestSchema, request);
|
|
13489
|
+
if (invalid) return invalid;
|
|
12862
13490
|
if (request.displayName === void 0 && request.description === void 0 && request.avatarUrl === void 0) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass a displayName, description, or avatarUrl to update." }));
|
|
12863
13491
|
const result = await client.profile.update(request);
|
|
12864
13492
|
if (!result.ok) return failureFromClientResult(result);
|
|
@@ -12870,6 +13498,85 @@ async function updateProfile(client, request, options = {}) {
|
|
|
12870
13498
|
text: formatAgentProfile(result.data, options)
|
|
12871
13499
|
};
|
|
12872
13500
|
}
|
|
13501
|
+
const userInfoRequestSchema = requestSchema()(object({
|
|
13502
|
+
name: string().describe("The human or agent, `@handle` (or the bare handle)."),
|
|
13503
|
+
offset: number$1().int().nonnegative().optional().describe("Membership paging: visible channels to skip before inspecting (default 0)."),
|
|
13504
|
+
limit: number$1().int().positive().optional().describe("Membership paging: visible channels to inspect (default 50).")
|
|
13505
|
+
}));
|
|
13506
|
+
/**
|
|
13507
|
+
* `raft user info <name>`: the user's visible facts from `server.info`, and
|
|
13508
|
+
* their memberships among one page of the visible channels, checked one
|
|
13509
|
+
* channel roster at a time (a rejected roster is skipped and counted).
|
|
13510
|
+
*/
|
|
13511
|
+
async function userInfo(client, request) {
|
|
13512
|
+
const invalid = validateOpRequest(userInfoRequestSchema, request);
|
|
13513
|
+
if (invalid) return invalid;
|
|
13514
|
+
const trimmed = request.name.trim();
|
|
13515
|
+
const name = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
13516
|
+
if (!name) return failureOutcome(opError("INVALID_REQUEST", { message: "user name is required" }));
|
|
13517
|
+
const limit = request.limit ?? 50;
|
|
13518
|
+
const offset = request.offset ?? 0;
|
|
13519
|
+
const info = await client.server.info();
|
|
13520
|
+
if (!info.ok) return failureFromClientResult(info);
|
|
13521
|
+
const agent = info.data.agents.find((candidate) => candidate.name === name);
|
|
13522
|
+
const human = info.data.humans.find((candidate) => candidate.name === name);
|
|
13523
|
+
const user = agent ? {
|
|
13524
|
+
kind: "agent",
|
|
13525
|
+
value: agent
|
|
13526
|
+
} : human ? {
|
|
13527
|
+
kind: "human",
|
|
13528
|
+
value: human
|
|
13529
|
+
} : null;
|
|
13530
|
+
if (!user) return failureOutcome(opError("NOT_FOUND", {
|
|
13531
|
+
message: `User not found or not visible: @${name}`,
|
|
13532
|
+
nextAction: "Run `raft server info --agents --query <name>` or `raft server info --humans --query <name>` to inspect visible users."
|
|
13533
|
+
}));
|
|
13534
|
+
const visibleChannels = info.data.channels;
|
|
13535
|
+
const memberships = [];
|
|
13536
|
+
let skippedChannels = 0;
|
|
13537
|
+
for (const channel of visibleChannels.slice(offset, offset + limit)) {
|
|
13538
|
+
const members = await client.channels.members({ channel: `#${channel.name}` });
|
|
13539
|
+
if (!members.ok) {
|
|
13540
|
+
skippedChannels += 1;
|
|
13541
|
+
continue;
|
|
13542
|
+
}
|
|
13543
|
+
if ((user.kind === "agent" ? members.data.agents ?? [] : members.data.humans ?? []).some((candidate) => candidate.name === name)) memberships.push({
|
|
13544
|
+
...channel,
|
|
13545
|
+
joined: true,
|
|
13546
|
+
muted: void 0,
|
|
13547
|
+
activityMuted: void 0
|
|
13548
|
+
});
|
|
13549
|
+
}
|
|
13550
|
+
const nextOffset = offset + limit;
|
|
13551
|
+
const page = {
|
|
13552
|
+
total: visibleChannels.length,
|
|
13553
|
+
offset,
|
|
13554
|
+
limit,
|
|
13555
|
+
nextCommand: nextOffset < visibleChannels.length ? `raft user info @${name} --offset ${nextOffset} --limit ${limit}` : void 0
|
|
13556
|
+
};
|
|
13557
|
+
const next = page.nextCommand ? {
|
|
13558
|
+
kind: "next_page",
|
|
13559
|
+
command: page.nextCommand,
|
|
13560
|
+
args: {
|
|
13561
|
+
name: `@${name}`,
|
|
13562
|
+
offset: nextOffset,
|
|
13563
|
+
limit
|
|
13564
|
+
},
|
|
13565
|
+
why: "Only one page of visible channels was inspected for memberships."
|
|
13566
|
+
} : null;
|
|
13567
|
+
return {
|
|
13568
|
+
ok: true,
|
|
13569
|
+
state: "info",
|
|
13570
|
+
data: {
|
|
13571
|
+
user,
|
|
13572
|
+
memberships,
|
|
13573
|
+
skippedChannels,
|
|
13574
|
+
page
|
|
13575
|
+
},
|
|
13576
|
+
next,
|
|
13577
|
+
text: formatAgentUserInfo(user, memberships, page, skippedChannels)
|
|
13578
|
+
};
|
|
13579
|
+
}
|
|
12873
13580
|
//#endregion
|
|
12874
13581
|
//#region ../shared/src/agentText/attachments.ts
|
|
12875
13582
|
/** Upload receipt with attachment id and send-usage hint. */
|
|
@@ -12912,6 +13619,10 @@ function formatAgentAttachmentComments(attachmentId, comments, threadChannelId)
|
|
|
12912
13619
|
}
|
|
12913
13620
|
//#endregion
|
|
12914
13621
|
//#region ../shared/src/agentOps/attachments.ts
|
|
13622
|
+
const attachmentCommentsRequestSchema = requestSchema()(object({
|
|
13623
|
+
attachmentId: string().describe("The attachment id."),
|
|
13624
|
+
limit: number$1().int().positive().optional().describe("Maximum comments to return.")
|
|
13625
|
+
}));
|
|
12915
13626
|
/** Compatibility fallback for Servers that predate the capability endpoint (the CLI's constant). */
|
|
12916
13627
|
const AGENT_ATTACHMENT_UPLOAD_FALLBACK_MAX_BYTES = 52428800;
|
|
12917
13628
|
const FILENAME_MIME_MAP = {
|
|
@@ -13119,6 +13830,8 @@ async function downloadAttachment(client, request) {
|
|
|
13119
13830
|
};
|
|
13120
13831
|
}
|
|
13121
13832
|
async function attachmentComments(client, request) {
|
|
13833
|
+
const invalid = validateOpRequest(attachmentCommentsRequestSchema, request);
|
|
13834
|
+
if (invalid) return invalid;
|
|
13122
13835
|
if (!request.attachmentId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "An attachment id is required." }));
|
|
13123
13836
|
const result = await client.attachments.comments({ attachmentId: request.attachmentId }, request.limit === void 0 ? {} : { limit: String(request.limit) });
|
|
13124
13837
|
if (!result.ok) return failureFromClientResult(result);
|
|
@@ -13135,101 +13848,12 @@ async function attachmentComments(client, request) {
|
|
|
13135
13848
|
};
|
|
13136
13849
|
}
|
|
13137
13850
|
//#endregion
|
|
13138
|
-
//#region ../shared/src/zonedTime.ts
|
|
13139
|
-
const MS_PER_SECOND = 1e3;
|
|
13140
|
-
const MS_PER_MINUTE = 6e4;
|
|
13141
|
-
const MS_PER_DAY = 864e5;
|
|
13142
|
-
const formatters = /* @__PURE__ */ new Map();
|
|
13143
|
-
function formatterFor(timeZone) {
|
|
13144
|
-
let formatter = formatters.get(timeZone);
|
|
13145
|
-
if (!formatter) {
|
|
13146
|
-
formatter = new Intl.DateTimeFormat("en-US", {
|
|
13147
|
-
timeZone,
|
|
13148
|
-
hourCycle: "h23",
|
|
13149
|
-
era: "short",
|
|
13150
|
-
year: "numeric",
|
|
13151
|
-
month: "numeric",
|
|
13152
|
-
day: "numeric",
|
|
13153
|
-
hour: "numeric",
|
|
13154
|
-
minute: "numeric",
|
|
13155
|
-
second: "numeric"
|
|
13156
|
-
});
|
|
13157
|
-
formatters.set(timeZone, formatter);
|
|
13158
|
-
}
|
|
13159
|
-
return formatter;
|
|
13160
|
-
}
|
|
13161
|
-
/** Days from 1970-01-01 to the proleptic Gregorian date (H. Hinnant's days_from_civil). */
|
|
13162
|
-
function daysFromCivil(year, month, day) {
|
|
13163
|
-
const y = month <= 2 ? year - 1 : year;
|
|
13164
|
-
const era = Math.floor(y / 400);
|
|
13165
|
-
const yoe = y - era * 400;
|
|
13166
|
-
const mp = (month + 9) % 12;
|
|
13167
|
-
const doy = Math.floor((153 * mp + 2) / 5) + day - 1;
|
|
13168
|
-
const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
|
|
13169
|
-
return era * 146097 + doe - 719468;
|
|
13170
|
-
}
|
|
13171
|
-
/** The wall clock as milliseconds on a UTC timeline (no Date, so no 0-99 year quirk or range limit). */
|
|
13172
|
-
function wallClockMs(wall) {
|
|
13173
|
-
return daysFromCivil(wall.year, wall.month, wall.day) * MS_PER_DAY + wall.hour * 36e5 + wall.minute * MS_PER_MINUTE + wall.second * MS_PER_SECOND + wall.millisecond;
|
|
13174
|
-
}
|
|
13175
|
-
function floorMod(value, modulus) {
|
|
13176
|
-
return (value % modulus + modulus) % modulus;
|
|
13177
|
-
}
|
|
13178
|
-
/** Wall clock of `epochMs` in `timeZone` plus the exact offset in ms (seconds precision). */
|
|
13179
|
-
function wallClockAt(epochMs, timeZone) {
|
|
13180
|
-
const parts = {};
|
|
13181
|
-
for (const part of formatterFor(timeZone).formatToParts(epochMs)) parts[part.type] = part.value;
|
|
13182
|
-
const eraYear = Number(parts.year);
|
|
13183
|
-
const wall = {
|
|
13184
|
-
year: /^b/i.test(parts.era ?? "") ? 1 - eraYear : eraYear,
|
|
13185
|
-
month: Number(parts.month),
|
|
13186
|
-
day: Number(parts.day),
|
|
13187
|
-
hour: Number(parts.hour),
|
|
13188
|
-
minute: Number(parts.minute),
|
|
13189
|
-
second: Number(parts.second),
|
|
13190
|
-
millisecond: floorMod(epochMs, MS_PER_SECOND)
|
|
13191
|
-
};
|
|
13192
|
-
return {
|
|
13193
|
-
wall,
|
|
13194
|
-
offsetMs: wallClockMs(wall) - epochMs
|
|
13195
|
-
};
|
|
13196
|
-
}
|
|
13197
|
-
function offsetMinutesOf(offsetMs) {
|
|
13198
|
-
return -Math.trunc(-offsetMs / MS_PER_MINUTE);
|
|
13199
|
-
}
|
|
13200
|
-
/** The wall clock and getTimezoneOffset-style offset of `epochMs` in `timeZone`. */
|
|
13201
|
-
function zonedInstant(epochMs, timeZone) {
|
|
13202
|
-
const { wall, offsetMs } = wallClockAt(epochMs, timeZone);
|
|
13203
|
-
return {
|
|
13204
|
-
...wall,
|
|
13205
|
-
offsetMinutes: offsetMinutesOf(offsetMs)
|
|
13206
|
-
};
|
|
13207
|
-
}
|
|
13208
|
-
function pad2(n) {
|
|
13209
|
-
return String(n).padStart(2, "0");
|
|
13210
|
-
}
|
|
13211
|
-
/** `+08:00` / `-04:00` / `+05:30`, as the CLI has always printed offsets. */
|
|
13212
|
-
function formatUtcOffset(offsetMinutes) {
|
|
13213
|
-
const sign = offsetMinutes >= 0 ? "+" : "-";
|
|
13214
|
-
const abs = Math.abs(offsetMinutes);
|
|
13215
|
-
return `${sign}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`;
|
|
13216
|
-
}
|
|
13217
|
-
//#endregion
|
|
13218
13851
|
//#region ../shared/src/agentText/search.ts
|
|
13219
|
-
/**
|
|
13220
|
-
|
|
13221
|
-
* `timeZone` (an IANA name) the time is rendered in that zone; without it, in
|
|
13222
|
-
* the process's local zone (the SDK's historical behaviour). The two agree
|
|
13223
|
-
* byte for byte when `timeZone` is the process zone.
|
|
13224
|
-
*/
|
|
13225
|
-
function formatLocalTimeWithOffset(iso, timeZone) {
|
|
13852
|
+
/** Local wall-clock rendering with offset, as the CLI prints search hit times. */
|
|
13853
|
+
function formatLocalTimeWithOffset(iso) {
|
|
13226
13854
|
const d = new Date(iso);
|
|
13227
13855
|
if (isNaN(d.getTime())) return iso;
|
|
13228
13856
|
const pad = (n) => String(n).padStart(2, "0");
|
|
13229
|
-
if (timeZone !== void 0) {
|
|
13230
|
-
const z = zonedInstant(d.getTime(), timeZone);
|
|
13231
|
-
return `${z.year}-${pad(z.month)}-${pad(z.day)} ${pad(z.hour)}:${pad(z.minute)}:${pad(z.second)} ${formatUtcOffset(z.offsetMinutes)}`;
|
|
13232
|
-
}
|
|
13233
13857
|
const offsetMinutes = -d.getTimezoneOffset();
|
|
13234
13858
|
const sign = offsetMinutes >= 0 ? "+" : "-";
|
|
13235
13859
|
const absOffset = Math.abs(offsetMinutes);
|
|
@@ -13352,11 +13976,8 @@ function renderSearchPreview(content, query) {
|
|
|
13352
13976
|
trailingOmit
|
|
13353
13977
|
].join("");
|
|
13354
13978
|
}
|
|
13355
|
-
/**
|
|
13356
|
-
|
|
13357
|
-
* `timeZone` when given, else in the process's local zone.
|
|
13358
|
-
*/
|
|
13359
|
-
function formatAgentSearchResults(query, data, offset, sort, limit, timeZone) {
|
|
13979
|
+
/** Search results with <match>/<omit /> preview markup. */
|
|
13980
|
+
function formatAgentSearchResults(query, data, offset, sort, limit) {
|
|
13360
13981
|
const trimmedQuery = query.trim();
|
|
13361
13982
|
const oldestShown = data.results?.length ? data.results[data.results.length - 1]?.createdAt : void 0;
|
|
13362
13983
|
const nextPageHint = sort === "recent" && oldestShown ? `page with --before ${oldestShown} (pages OLDER only; NOT a complete traversal: stored times are microsecond but this key is millisecond, so rows inside the boundary millisecond can be skipped, and a full page sharing one timestamp can repeat indefinitely; copy the key verbatim)` : `page with --offset ${(offset ?? 0) + (data.results?.length ?? 0)}`;
|
|
@@ -13376,7 +13997,7 @@ function formatAgentSearchResults(query, data, offset, sort, limit, timeZone) {
|
|
|
13376
13997
|
`<result ref="${ref}">`,
|
|
13377
13998
|
`Source: ${renderSearchSource(result)}`,
|
|
13378
13999
|
`Sender: ${sender}${senderType}`,
|
|
13379
|
-
`Time: ${result.createdAt ? formatLocalTimeWithOffset(result.createdAt
|
|
14000
|
+
`Time: ${result.createdAt ? formatLocalTimeWithOffset(result.createdAt) : "-"}`,
|
|
13380
14001
|
...taskProjection ? [taskProjection] : [],
|
|
13381
14002
|
"",
|
|
13382
14003
|
"<preview>",
|
|
@@ -13397,7 +14018,24 @@ function formatAgentSearchResults(query, data, offset, sort, limit, timeZone) {
|
|
|
13397
14018
|
}
|
|
13398
14019
|
//#endregion
|
|
13399
14020
|
//#region ../shared/src/agentOps/search.ts
|
|
14021
|
+
const searchMessagesRequestSchema = requestSchema()(object({
|
|
14022
|
+
query: string().optional().describe("Free-text query; may be omitted when filtering by target or sender."),
|
|
14023
|
+
target: string().optional().describe("Only this channel, DM, or thread."),
|
|
14024
|
+
sender: string().optional().describe("Only messages from this handle."),
|
|
14025
|
+
sort: _enum(["relevance", "recent"]).optional(),
|
|
14026
|
+
before: string().optional().describe("ISO timestamp; only messages at or before it."),
|
|
14027
|
+
after: string().optional().describe("ISO timestamp; only messages at or after it."),
|
|
14028
|
+
limit: number$1().int().positive().optional().describe("Results per page, 1..50 (Server default 20)."),
|
|
14029
|
+
offset: number$1().int().nonnegative().optional().describe("Skip this many results (paging).")
|
|
14030
|
+
}));
|
|
14031
|
+
const resolveMessageRequestSchema = requestSchema()(object({ messageId: string().describe("Full or short message id.") }));
|
|
14032
|
+
const reactRequestSchema = requestSchema()(object({
|
|
14033
|
+
messageId: string().describe("Full or short message id."),
|
|
14034
|
+
emoji: string().describe("One reaction emoji.")
|
|
14035
|
+
}));
|
|
13400
14036
|
async function searchMessages(client, request) {
|
|
14037
|
+
const invalid = validateOpRequest(searchMessagesRequestSchema, request);
|
|
14038
|
+
if (invalid) return invalid;
|
|
13401
14039
|
if (!request.query?.trim() && !request.target && !request.sender) return failureOutcome(opError("INVALID_REQUEST", { message: "Pass a query, or filter by target or sender." }));
|
|
13402
14040
|
const result = await client.messages.search({
|
|
13403
14041
|
...request.query?.trim() ? { q: request.query.trim() } : {},
|
|
@@ -13437,6 +14075,8 @@ async function searchMessages(client, request) {
|
|
|
13437
14075
|
};
|
|
13438
14076
|
}
|
|
13439
14077
|
async function resolveMessage(client, request) {
|
|
14078
|
+
const invalid = validateOpRequest(resolveMessageRequestSchema, request);
|
|
14079
|
+
if (invalid) return invalid;
|
|
13440
14080
|
if (!request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id is required." }));
|
|
13441
14081
|
const result = await client.messages.resolve({ msgId: request.messageId });
|
|
13442
14082
|
if (!result.ok) return failureFromClientResult(result);
|
|
@@ -13459,6 +14099,8 @@ async function resolveMessage(client, request) {
|
|
|
13459
14099
|
};
|
|
13460
14100
|
}
|
|
13461
14101
|
async function reactToMessage(client, request, action = "add") {
|
|
14102
|
+
const invalid = validateOpRequest(reactRequestSchema, request);
|
|
14103
|
+
if (invalid) return invalid;
|
|
13462
14104
|
const emoji = request.emoji?.trim();
|
|
13463
14105
|
if (!request.messageId?.trim() || !emoji || /\s/.test(emoji) || emoji.length > 16) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id and a single reaction emoji are required." }));
|
|
13464
14106
|
const params = { msgId: request.messageId };
|
|
@@ -13660,7 +14302,15 @@ function formatAgentSenderMentionDeliveries(messageId, deliveries) {
|
|
|
13660
14302
|
}
|
|
13661
14303
|
//#endregion
|
|
13662
14304
|
//#region ../shared/src/agentOps/mentions.ts
|
|
14305
|
+
const pendingMentionActionsRequestSchema = requestSchema()(object({ limit: number$1().int().positive().optional().describe("Maximum pending mentions to list.") }));
|
|
14306
|
+
const executeMentionActionRequestSchema = requestSchema()(object({
|
|
14307
|
+
action: _enum(["notify", "add"]).describe("notify: tell the mentioned target about the message; add: add them to the conversation."),
|
|
14308
|
+
resolutionIds: array(string()).describe("resolutionId values from mentions.pending.")
|
|
14309
|
+
}));
|
|
14310
|
+
const senderMentionDeliveriesRequestSchema = requestSchema()(object({ messageId: string().describe("Id of a message you sent.") }));
|
|
13663
14311
|
async function pendingMentionActions(client, request = {}) {
|
|
14312
|
+
const invalid = validateOpRequest(pendingMentionActionsRequestSchema, request);
|
|
14313
|
+
if (invalid) return invalid;
|
|
13664
14314
|
const result = await client.mentions.pendingActions(request.limit === void 0 ? {} : { limit: String(request.limit) });
|
|
13665
14315
|
if (!result.ok) return failureFromClientResult(result);
|
|
13666
14316
|
const actions = normalizeAgentPendingMentionActions(result.data);
|
|
@@ -13692,6 +14342,8 @@ async function pendingMentionActions(client, request = {}) {
|
|
|
13692
14342
|
};
|
|
13693
14343
|
}
|
|
13694
14344
|
async function executeMentionAction(client, request) {
|
|
14345
|
+
const invalid = validateOpRequest(executeMentionActionRequestSchema, request);
|
|
14346
|
+
if (invalid) return invalid;
|
|
13695
14347
|
const ids = (request.resolutionIds ?? []).map((id) => id.trim()).filter(Boolean);
|
|
13696
14348
|
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." }));
|
|
13697
14349
|
const result = await client.mentions.executeAction({
|
|
@@ -13712,6 +14364,8 @@ async function executeMentionAction(client, request) {
|
|
|
13712
14364
|
};
|
|
13713
14365
|
}
|
|
13714
14366
|
async function senderMentionDeliveries(client, request) {
|
|
14367
|
+
const invalid = validateOpRequest(senderMentionDeliveriesRequestSchema, request);
|
|
14368
|
+
if (invalid) return invalid;
|
|
13715
14369
|
if (!request.messageId?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A message id is required." }));
|
|
13716
14370
|
const result = await client.mentions.senderDeliveries({ messageId: request.messageId });
|
|
13717
14371
|
if (!result.ok) return failureFromClientResult(result);
|
|
@@ -13744,11 +14398,26 @@ function formatAgentKnowledgeStdout(content) {
|
|
|
13744
14398
|
}
|
|
13745
14399
|
//#endregion
|
|
13746
14400
|
//#region ../shared/src/agentOps/manual.ts
|
|
14401
|
+
const manualContextFields = {
|
|
14402
|
+
intent: string().min(12).max(500).describe("What the user wants to accomplish with Raft, in a few words. Never a prompt, credential, URL, or message text."),
|
|
14403
|
+
reason: string().min(12).max(500).describe("Why the Manual is needed now, in a few words. Same rules as intent.")
|
|
14404
|
+
};
|
|
14405
|
+
const getManualTopicRequestSchema = requestSchema()(object({
|
|
14406
|
+
topic: string().describe("Topic id (slug), for example one returned by manual.search."),
|
|
14407
|
+
...manualContextFields
|
|
14408
|
+
}));
|
|
14409
|
+
const searchManualRequestSchema = requestSchema()(object({
|
|
14410
|
+
query: string().describe("Search keywords."),
|
|
14411
|
+
scope: string().optional().describe("Restrict the search to one Manual section."),
|
|
14412
|
+
...manualContextFields
|
|
14413
|
+
}));
|
|
13747
14414
|
function requireContext(context) {
|
|
13748
14415
|
if (!context?.intent?.trim() || !context?.reason?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "Manual calls need a short `intent` and `reason`." }));
|
|
13749
14416
|
return null;
|
|
13750
14417
|
}
|
|
13751
14418
|
async function getManualTopic(client, request) {
|
|
14419
|
+
const malformed = validateOpRequest(getManualTopicRequestSchema, request);
|
|
14420
|
+
if (malformed) return malformed;
|
|
13752
14421
|
const invalid = requireContext(request);
|
|
13753
14422
|
if (invalid) return invalid;
|
|
13754
14423
|
if (!request.topic?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "A topic id is required." }));
|
|
@@ -13767,6 +14436,8 @@ async function getManualTopic(client, request) {
|
|
|
13767
14436
|
};
|
|
13768
14437
|
}
|
|
13769
14438
|
async function searchManual(client, request) {
|
|
14439
|
+
const malformed = validateOpRequest(searchManualRequestSchema, request);
|
|
14440
|
+
if (malformed) return malformed;
|
|
13770
14441
|
const invalid = requireContext(request);
|
|
13771
14442
|
if (invalid) return invalid;
|
|
13772
14443
|
if (!request.query?.trim()) return failureOutcome(opError("INVALID_REQUEST", { message: "Search keywords are required." }));
|
|
@@ -13955,14 +14626,63 @@ var RaftStateSession = class {
|
|
|
13955
14626
|
}
|
|
13956
14627
|
};
|
|
13957
14628
|
//#endregion
|
|
14629
|
+
//#region ../shared/src/agentOps/seenPolicy/consumedSeqs.ts
|
|
14630
|
+
function getParentTargetForThread(target) {
|
|
14631
|
+
if (target.startsWith("dm:@")) {
|
|
14632
|
+
const separatorIndex = target.indexOf(":", 4);
|
|
14633
|
+
return separatorIndex > 0 ? target.slice(0, separatorIndex) : null;
|
|
14634
|
+
}
|
|
14635
|
+
if (target.startsWith("#")) {
|
|
14636
|
+
const separatorIndex = target.indexOf(":");
|
|
14637
|
+
return separatorIndex > 0 ? target.slice(0, separatorIndex) : null;
|
|
14638
|
+
}
|
|
14639
|
+
return null;
|
|
14640
|
+
}
|
|
14641
|
+
//#endregion
|
|
13958
14642
|
//#region ../shared/src/agentOps/actions.ts
|
|
14643
|
+
/**
|
|
14644
|
+
* The Agent API's own body schema (a discriminated union over the card
|
|
14645
|
+
* types), so the SDK checks exactly what the Server checks. The operation
|
|
14646
|
+
* manifest projects it flattened (see the SDK's operations.ts).
|
|
14647
|
+
*/
|
|
14648
|
+
const prepareActionCardRequestSchema = agentApiActionPrepareBodySchema;
|
|
13959
14649
|
/** Text is the CLI's `raft action prepare` output. */
|
|
13960
14650
|
function formatAgentActionCardPosted(target, messageId) {
|
|
13961
14651
|
const shortId = messageId ? messageId.slice(0, 8) : null;
|
|
13962
14652
|
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`;
|
|
13963
14653
|
}
|
|
14654
|
+
/**
|
|
14655
|
+
* Where the card's outcome arrives (Server #8604): when a human executes the
|
|
14656
|
+
* card, or execution fails, the Server posts a system reply that @mentions
|
|
14657
|
+
* the preparer in the card's own thread; a card posted inside a thread has no
|
|
14658
|
+
* thread of its own, so the reply lands in that same thread.
|
|
14659
|
+
*/
|
|
14660
|
+
function awaitConfirmationNext(target, messageId) {
|
|
14661
|
+
const short = messageId.slice(0, 8);
|
|
14662
|
+
if (getParentTargetForThread(target) !== null) return {
|
|
14663
|
+
kind: "await_confirmation",
|
|
14664
|
+
command: `raft message read --target "${target}" --around ${short}`,
|
|
14665
|
+
args: {
|
|
14666
|
+
target,
|
|
14667
|
+
messageId
|
|
14668
|
+
},
|
|
14669
|
+
why: "A human must click the card to commit it. When it is executed (or fails), the outcome arrives as a reply in this thread that @mentions you; you do not need to poll."
|
|
14670
|
+
};
|
|
14671
|
+
const thread = agentTaskThreadTarget(target, messageId);
|
|
14672
|
+
return {
|
|
14673
|
+
kind: "await_confirmation",
|
|
14674
|
+
command: `raft message read --target "${thread}"`,
|
|
14675
|
+
args: {
|
|
14676
|
+
target: thread,
|
|
14677
|
+
messageId
|
|
14678
|
+
},
|
|
14679
|
+
why: "A human must click the card to commit it. When it is executed (or fails), the outcome arrives as a reply in the card's thread that @mentions you; you do not need to poll."
|
|
14680
|
+
};
|
|
14681
|
+
}
|
|
13964
14682
|
async function prepareActionCard(client, request) {
|
|
13965
|
-
if (
|
|
14683
|
+
if (typeof request?.target !== "string" || !request.target.trim() || !request.action) return failureOutcome(opError("INVALID_REQUEST", { message: "A target and an action are required to prepare a card." }));
|
|
14684
|
+
const invalid = validateOpRequest(prepareActionCardRequestSchema, request);
|
|
14685
|
+
if (invalid) return invalid;
|
|
13966
14686
|
const result = await client.actions.prepare(request);
|
|
13967
14687
|
if (!result.ok) return failureFromClientResult(result);
|
|
13968
14688
|
const card = {
|
|
@@ -13973,15 +14693,7 @@ async function prepareActionCard(client, request) {
|
|
|
13973
14693
|
ok: true,
|
|
13974
14694
|
state: "prepared",
|
|
13975
14695
|
data: card,
|
|
13976
|
-
next:
|
|
13977
|
-
kind: "await_confirmation",
|
|
13978
|
-
command: `raft message read --target "${request.target}" --around ${card.messageId.slice(0, 8)}`,
|
|
13979
|
-
args: {
|
|
13980
|
-
target: request.target,
|
|
13981
|
-
messageId: card.messageId
|
|
13982
|
-
},
|
|
13983
|
-
why: "A human must click the card to commit it. You are not notified of the result yet; read the card message to see its state instead of waiting."
|
|
13984
|
-
},
|
|
14696
|
+
next: awaitConfirmationNext(request.target, card.messageId),
|
|
13985
14697
|
text: formatAgentActionCardPosted(request.target, card.messageId)
|
|
13986
14698
|
};
|
|
13987
14699
|
}
|
|
@@ -14041,6 +14753,725 @@ async function readLatestReadThread(options = {}) {
|
|
|
14041
14753
|
return latestReadThreadFromCliReadState(raw);
|
|
14042
14754
|
}
|
|
14043
14755
|
//#endregion
|
|
14756
|
+
//#region src/operationSchemas.ts
|
|
14757
|
+
/** `identity.whoami` takes no arguments. */
|
|
14758
|
+
const whoamiRequestSchema = object({});
|
|
14759
|
+
/** `inbox.commit`: no cursor commits the pending batch; a cursor commits up to it. */
|
|
14760
|
+
const commitInboxRequestSchema = requestSchema()(object({ cursor: number$1().int().nonnegative().nullable().optional().describe("Commit up to this batch cursor; omit to commit the batch the last inbox pull returned.") }));
|
|
14761
|
+
//#endregion
|
|
14762
|
+
//#region src/toolSchema.ts
|
|
14763
|
+
/** Every keyword the subset allows. */
|
|
14764
|
+
const RAFT_JSON_SCHEMA_KEYWORDS = [
|
|
14765
|
+
"type",
|
|
14766
|
+
"description",
|
|
14767
|
+
"properties",
|
|
14768
|
+
"required",
|
|
14769
|
+
"items",
|
|
14770
|
+
"enum",
|
|
14771
|
+
"minimum",
|
|
14772
|
+
"maximum",
|
|
14773
|
+
"minLength",
|
|
14774
|
+
"maxLength"
|
|
14775
|
+
];
|
|
14776
|
+
const PRIMITIVES = /* @__PURE__ */ new Set([
|
|
14777
|
+
"string",
|
|
14778
|
+
"number",
|
|
14779
|
+
"integer",
|
|
14780
|
+
"boolean",
|
|
14781
|
+
"null"
|
|
14782
|
+
]);
|
|
14783
|
+
const SAFE = Number.MAX_SAFE_INTEGER;
|
|
14784
|
+
function isRaw(value) {
|
|
14785
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
14786
|
+
}
|
|
14787
|
+
function jsonTypeOf(value) {
|
|
14788
|
+
if (value === null) return "null";
|
|
14789
|
+
if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
|
|
14790
|
+
if (typeof value === "boolean") return "boolean";
|
|
14791
|
+
return "string";
|
|
14792
|
+
}
|
|
14793
|
+
function project(node, where, options) {
|
|
14794
|
+
if (!isRaw(node)) throw new Error(`${where}: not a JSON Schema object`);
|
|
14795
|
+
const branches = node.anyOf ?? node.oneOf;
|
|
14796
|
+
if (Array.isArray(branches)) {
|
|
14797
|
+
const projected = branches.map((branch, index) => project(branch, `${where}|${index}`, options));
|
|
14798
|
+
const flattened = projected.every((b) => b.type !== void 0 && b.type !== "array" && b.type !== "object") ? primitiveUnion(projected, where, options) : flattenDiscriminatedUnion(projected, where);
|
|
14799
|
+
return typeof node.description === "string" ? {
|
|
14800
|
+
...flattened,
|
|
14801
|
+
description: node.description
|
|
14802
|
+
} : flattened;
|
|
14803
|
+
}
|
|
14804
|
+
if (node.allOf !== void 0 || node.$ref !== void 0 || node.not !== void 0) throw new Error(`${where}: allOf / $ref / not cannot be projected to the tool-schema subset`);
|
|
14805
|
+
const out = {};
|
|
14806
|
+
if (node.type !== void 0) {
|
|
14807
|
+
if (Array.isArray(node.type)) throw new Error(`${where}: type arrays are outside the tool-schema subset`);
|
|
14808
|
+
const type = String(node.type);
|
|
14809
|
+
if (type !== "object" && type !== "array" && !PRIMITIVES.has(type)) throw new Error(`${where}: unknown type ${type}`);
|
|
14810
|
+
out.type = type;
|
|
14811
|
+
}
|
|
14812
|
+
if (typeof node.description === "string") out.description = node.description;
|
|
14813
|
+
if (node.const !== void 0) {
|
|
14814
|
+
out.enum = [node.const];
|
|
14815
|
+
out.type ??= jsonTypeOf(node.const);
|
|
14816
|
+
}
|
|
14817
|
+
if (Array.isArray(node.enum)) out.enum = [...node.enum];
|
|
14818
|
+
if (isRaw(node.properties)) {
|
|
14819
|
+
out.properties = {};
|
|
14820
|
+
const required = Array.isArray(node.required) ? node.required : [];
|
|
14821
|
+
for (const [key, value] of Object.entries(node.properties)) {
|
|
14822
|
+
if (required.includes(key) && isNullable(value)) throw new Error(`${where}.${key}: a required nullable field has no tool-schema form; make it optional (omitted = null)`);
|
|
14823
|
+
out.properties[key] = project(value, `${where}.${key}`, options);
|
|
14824
|
+
}
|
|
14825
|
+
out.type ??= "object";
|
|
14826
|
+
}
|
|
14827
|
+
if (Array.isArray(node.required) && node.required.length > 0) out.required = [...node.required];
|
|
14828
|
+
if (node.items !== void 0) out.items = project(node.items, `${where}[]`, options);
|
|
14829
|
+
const integer = out.type === "integer";
|
|
14830
|
+
const lower = [typeof node.minimum === "number" ? node.minimum : void 0, typeof node.exclusiveMinimum === "number" ? node.exclusiveMinimum + (integer ? 1 : 0) : void 0].filter((v) => v !== void 0 && v > -SAFE);
|
|
14831
|
+
const upper = [typeof node.maximum === "number" ? node.maximum : void 0, typeof node.exclusiveMaximum === "number" ? node.exclusiveMaximum - (integer ? 1 : 0) : void 0].filter((v) => v !== void 0 && v < SAFE);
|
|
14832
|
+
if (lower.length > 0) out.minimum = Math.max(...lower);
|
|
14833
|
+
if (upper.length > 0) out.maximum = Math.min(...upper);
|
|
14834
|
+
if (typeof node.minLength === "number" && node.minLength > 0) out.minLength = node.minLength;
|
|
14835
|
+
if (typeof node.maxLength === "number") out.maxLength = node.maxLength;
|
|
14836
|
+
return out;
|
|
14837
|
+
}
|
|
14838
|
+
function isNullable(node) {
|
|
14839
|
+
if (!isRaw(node)) return false;
|
|
14840
|
+
const branches = node.anyOf ?? node.oneOf;
|
|
14841
|
+
return node.type === "null" || Array.isArray(branches) && branches.some((b) => isRaw(b) && b.type === "null");
|
|
14842
|
+
}
|
|
14843
|
+
/**
|
|
14844
|
+
* `T | null` → `T` (the field is optional, so omitting it is the JSON form of
|
|
14845
|
+
* null); any other union of primitives → the type the operation names in
|
|
14846
|
+
* `unionAs`. The branch constraints are dropped (looser).
|
|
14847
|
+
*/
|
|
14848
|
+
function primitiveUnion(branches, where, options) {
|
|
14849
|
+
const types = [...new Set(branches.map((b) => b.type).filter((t) => t !== "null"))];
|
|
14850
|
+
if (types.length === 1) return { type: types[0] };
|
|
14851
|
+
const chosen = options.unionAs[where];
|
|
14852
|
+
if (!chosen || !types.includes(chosen)) throw new Error(`${where}: a union of ${types.join(" | ")} needs unionAs (the one type to advertise)`);
|
|
14853
|
+
return { type: chosen };
|
|
14854
|
+
}
|
|
14855
|
+
/** The one property every object branch pins to a distinct single value. */
|
|
14856
|
+
function discriminatorOf(branches) {
|
|
14857
|
+
const first = branches[0];
|
|
14858
|
+
if (!first?.properties) return null;
|
|
14859
|
+
for (const key of Object.keys(first.properties)) {
|
|
14860
|
+
const values = branches.map((b) => b.properties?.[key]?.enum);
|
|
14861
|
+
if (values.every((v) => Array.isArray(v) && v.length === 1) && new Set(values.map((v) => v[0])).size === branches.length) return key;
|
|
14862
|
+
}
|
|
14863
|
+
return null;
|
|
14864
|
+
}
|
|
14865
|
+
function flattenDiscriminatedUnion(branches, where) {
|
|
14866
|
+
if (!branches.every((b) => b.type === "object")) throw new Error(`${where}: only unions of primitives or discriminated unions of objects can be projected`);
|
|
14867
|
+
const discriminator = discriminatorOf(branches);
|
|
14868
|
+
if (!discriminator) throw new Error(`${where}: a union of objects needs a discriminator property to be flattened`);
|
|
14869
|
+
const tags = branches.map((b) => b.properties[discriminator].enum[0]);
|
|
14870
|
+
const properties = { [discriminator]: {
|
|
14871
|
+
type: "string",
|
|
14872
|
+
enum: tags,
|
|
14873
|
+
description: `Which variant this is; the other fields apply per ${discriminator} as their descriptions say.`
|
|
14874
|
+
} };
|
|
14875
|
+
const order = [];
|
|
14876
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
14877
|
+
branches.forEach((branch, index) => {
|
|
14878
|
+
for (const [key, schema] of Object.entries(branch.properties ?? {})) {
|
|
14879
|
+
if (key === discriminator) continue;
|
|
14880
|
+
if (!byKey.has(key)) {
|
|
14881
|
+
byKey.set(key, []);
|
|
14882
|
+
order.push(key);
|
|
14883
|
+
}
|
|
14884
|
+
byKey.get(key).push({
|
|
14885
|
+
tag: tags[index],
|
|
14886
|
+
schema
|
|
14887
|
+
});
|
|
14888
|
+
}
|
|
14889
|
+
});
|
|
14890
|
+
for (const key of order) {
|
|
14891
|
+
const uses = byKey.get(key);
|
|
14892
|
+
const merged = uses.map((u) => u.schema).reduce(loosest);
|
|
14893
|
+
const descriptions = [...new Set(uses.map((u) => u.schema.description).filter((d) => Boolean(d)))];
|
|
14894
|
+
const usedBy = uses.length === branches.length ? "" : `Only for ${discriminator} ${uses.map((u) => u.tag).join(", ")}.`;
|
|
14895
|
+
const description = [...descriptions, usedBy].filter(Boolean).join(" ");
|
|
14896
|
+
const { description: _drop, ...rest } = merged;
|
|
14897
|
+
properties[key] = description ? {
|
|
14898
|
+
...rest,
|
|
14899
|
+
description
|
|
14900
|
+
} : rest;
|
|
14901
|
+
}
|
|
14902
|
+
return {
|
|
14903
|
+
type: "object",
|
|
14904
|
+
properties,
|
|
14905
|
+
required: [discriminator]
|
|
14906
|
+
};
|
|
14907
|
+
}
|
|
14908
|
+
/** The loosest schema accepting everything either side accepts (within the subset). */
|
|
14909
|
+
function loosest(a, b) {
|
|
14910
|
+
if (JSON.stringify(a) === JSON.stringify(b)) return a;
|
|
14911
|
+
const out = {};
|
|
14912
|
+
if (a.type !== void 0 && a.type === b.type) out.type = a.type;
|
|
14913
|
+
if (a.enum && b.enum) out.enum = [.../* @__PURE__ */ new Set([...a.enum, ...b.enum])];
|
|
14914
|
+
if (a.minimum !== void 0 && b.minimum !== void 0) out.minimum = Math.min(a.minimum, b.minimum);
|
|
14915
|
+
if (a.maximum !== void 0 && b.maximum !== void 0) out.maximum = Math.max(a.maximum, b.maximum);
|
|
14916
|
+
if (a.minLength !== void 0 && b.minLength !== void 0) out.minLength = Math.min(a.minLength, b.minLength);
|
|
14917
|
+
if (a.maxLength !== void 0 && b.maxLength !== void 0) out.maxLength = Math.max(a.maxLength, b.maxLength);
|
|
14918
|
+
if (a.items && b.items) out.items = loosest(a.items, b.items);
|
|
14919
|
+
if (a.properties && b.properties) {
|
|
14920
|
+
out.properties = {};
|
|
14921
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(a.properties), ...Object.keys(b.properties)])) {
|
|
14922
|
+
const pa = a.properties[key];
|
|
14923
|
+
const pb = b.properties[key];
|
|
14924
|
+
out.properties[key] = pa && pb ? loosest(pa, pb) : pa ?? pb;
|
|
14925
|
+
}
|
|
14926
|
+
const required = (a.required ?? []).filter((key) => (b.required ?? []).includes(key));
|
|
14927
|
+
if (required.length > 0) out.required = required;
|
|
14928
|
+
}
|
|
14929
|
+
if (a.description) out.description = a.description;
|
|
14930
|
+
return out;
|
|
14931
|
+
}
|
|
14932
|
+
/** The tool-schema projection of an operation's zod request schema (input side). */
|
|
14933
|
+
function toRaftToolInputSchema(schema, options = {}) {
|
|
14934
|
+
const projected = project(toJSONSchema(schema, {
|
|
14935
|
+
io: "input",
|
|
14936
|
+
target: "draft-2020-12",
|
|
14937
|
+
reused: "inline",
|
|
14938
|
+
unrepresentable: "throw"
|
|
14939
|
+
}), "input", { unionAs: Object.fromEntries(Object.entries(options.unionAs ?? {}).map(([key, type]) => [`input.${key}`, type])) });
|
|
14940
|
+
if (projected.type !== "object") throw new Error("an operation's input schema must be an object");
|
|
14941
|
+
for (const key of options.omit ?? []) {
|
|
14942
|
+
if (projected.properties) delete projected.properties[key];
|
|
14943
|
+
if (projected.required) projected.required = projected.required.filter((r) => r !== key);
|
|
14944
|
+
}
|
|
14945
|
+
for (const [key, description] of Object.entries(options.describe ?? {})) {
|
|
14946
|
+
const property = projected.properties?.[key];
|
|
14947
|
+
if (!property) throw new Error(`cannot describe unknown field ${key}`);
|
|
14948
|
+
property.description = description;
|
|
14949
|
+
}
|
|
14950
|
+
if (projected.required?.length === 0) delete projected.required;
|
|
14951
|
+
projected.properties ??= {};
|
|
14952
|
+
return projected;
|
|
14953
|
+
}
|
|
14954
|
+
//#endregion
|
|
14955
|
+
//#region src/operations.ts
|
|
14956
|
+
const nothing = {
|
|
14957
|
+
model: [],
|
|
14958
|
+
code: []
|
|
14959
|
+
};
|
|
14960
|
+
const small = {
|
|
14961
|
+
mayBeLarge: false,
|
|
14962
|
+
boundBy: []
|
|
14963
|
+
};
|
|
14964
|
+
const OPERATION_DEFS = [
|
|
14965
|
+
{
|
|
14966
|
+
name: "identity.whoami",
|
|
14967
|
+
description: "Who this credential is: your agent, the server, the capabilities granted to the credential, and the operating guide for External Agents.",
|
|
14968
|
+
schema: whoamiRequestSchema,
|
|
14969
|
+
routes: ["agentContext"],
|
|
14970
|
+
consumes: nothing,
|
|
14971
|
+
output: {
|
|
14972
|
+
mayBeLarge: true,
|
|
14973
|
+
boundBy: []
|
|
14974
|
+
}
|
|
14975
|
+
},
|
|
14976
|
+
{
|
|
14977
|
+
name: "inbox.check",
|
|
14978
|
+
description: "Pull one batch of new messages addressed to you (DMs, @mentions, followed threads, joined channels). Handle them, then call inbox.commit; the next check acknowledges the committed batch.",
|
|
14979
|
+
schema: checkInboxRequestSchema,
|
|
14980
|
+
routes: ["events"],
|
|
14981
|
+
consumes: {
|
|
14982
|
+
model: ["inbox", "seen"],
|
|
14983
|
+
code: "refused"
|
|
14984
|
+
},
|
|
14985
|
+
output: {
|
|
14986
|
+
mayBeLarge: true,
|
|
14987
|
+
boundBy: ["limit"]
|
|
14988
|
+
}
|
|
14989
|
+
},
|
|
14990
|
+
{
|
|
14991
|
+
name: "inbox.drain",
|
|
14992
|
+
description: "Pull new messages until the server reports nothing more, returned together. Each batch is acknowledged by the pull after it; call inbox.commit after handling the result.",
|
|
14993
|
+
schema: drainInboxRequestSchema,
|
|
14994
|
+
routes: ["events"],
|
|
14995
|
+
consumes: {
|
|
14996
|
+
model: ["inbox", "seen"],
|
|
14997
|
+
code: "refused"
|
|
14998
|
+
},
|
|
14999
|
+
output: {
|
|
15000
|
+
mayBeLarge: true,
|
|
15001
|
+
boundBy: ["limit"]
|
|
15002
|
+
}
|
|
15003
|
+
},
|
|
15004
|
+
{
|
|
15005
|
+
name: "inbox.commit",
|
|
15006
|
+
description: "Mark the last pulled inbox batch as handled (or the batch with the given cursor); the next inbox check acknowledges it on the server. Call it only after the messages were handled.",
|
|
15007
|
+
schema: commitInboxRequestSchema,
|
|
15008
|
+
routes: [],
|
|
15009
|
+
local: {
|
|
15010
|
+
sideEffect: "write",
|
|
15011
|
+
idempotency: "natural"
|
|
15012
|
+
},
|
|
15013
|
+
consumes: {
|
|
15014
|
+
model: ["inbox"],
|
|
15015
|
+
code: "refused"
|
|
15016
|
+
},
|
|
15017
|
+
output: small
|
|
15018
|
+
},
|
|
15019
|
+
{
|
|
15020
|
+
name: "inbox.list",
|
|
15021
|
+
description: "List conversations with unread messages, newest activity first, each with where to start reading. Consumes nothing.",
|
|
15022
|
+
schema: listInboxRequestSchema,
|
|
15023
|
+
routes: ["inboxList"],
|
|
15024
|
+
consumes: nothing,
|
|
15025
|
+
output: {
|
|
15026
|
+
mayBeLarge: true,
|
|
15027
|
+
boundBy: ["before", "limit"]
|
|
15028
|
+
}
|
|
15029
|
+
},
|
|
15030
|
+
{
|
|
15031
|
+
name: "messages.read",
|
|
15032
|
+
description: "Read a conversation (channel, DM, or thread): the newest messages, or a window after / before / around a position. Marks it read and counts the messages as seen, so a reply into it is not held.",
|
|
15033
|
+
schema: readHistoryRequestSchema,
|
|
15034
|
+
unionAs: { around: "string" },
|
|
15035
|
+
routes: ["historyRead"],
|
|
15036
|
+
consumes: {
|
|
15037
|
+
model: ["read_cursor", "seen"],
|
|
15038
|
+
code: []
|
|
15039
|
+
},
|
|
15040
|
+
output: {
|
|
15041
|
+
mayBeLarge: true,
|
|
15042
|
+
boundBy: [
|
|
15043
|
+
"after",
|
|
15044
|
+
"before",
|
|
15045
|
+
"around",
|
|
15046
|
+
"limit"
|
|
15047
|
+
]
|
|
15048
|
+
}
|
|
15049
|
+
},
|
|
15050
|
+
{
|
|
15051
|
+
name: "messages.send",
|
|
15052
|
+
description: "Send a message to a channel, DM, or thread. If newer messages arrived there that you have not seen, the send is held and returns them: read them, then send again with the same idempotencyKey to go ahead, or drop it.",
|
|
15053
|
+
schema: sendMessageRequestSchema,
|
|
15054
|
+
hiddenFields: ["seen"],
|
|
15055
|
+
routes: ["messageSendV2"],
|
|
15056
|
+
idempotencyArg: "idempotencyKey",
|
|
15057
|
+
mayInterrupt: true,
|
|
15058
|
+
consumes: nothing,
|
|
15059
|
+
output: small
|
|
15060
|
+
},
|
|
15061
|
+
{
|
|
15062
|
+
name: "messages.reply",
|
|
15063
|
+
description: "Reply where a received message came from (its conversation or thread). Held like messages.send when newer messages arrived there.",
|
|
15064
|
+
schema: replyToRequestSchema,
|
|
15065
|
+
hiddenFields: ["seen"],
|
|
15066
|
+
routes: ["messageSendV2"],
|
|
15067
|
+
idempotencyArg: "idempotencyKey",
|
|
15068
|
+
mayInterrupt: true,
|
|
15069
|
+
consumes: nothing,
|
|
15070
|
+
output: small
|
|
15071
|
+
},
|
|
15072
|
+
{
|
|
15073
|
+
name: "messages.search",
|
|
15074
|
+
description: "Search messages by text, conversation, sender, or time; results are previews. Use it to find a specific message, not to check what is new.",
|
|
15075
|
+
schema: searchMessagesRequestSchema,
|
|
15076
|
+
routes: ["messageSearch"],
|
|
15077
|
+
consumes: nothing,
|
|
15078
|
+
output: {
|
|
15079
|
+
mayBeLarge: true,
|
|
15080
|
+
boundBy: ["limit", "offset"]
|
|
15081
|
+
}
|
|
15082
|
+
},
|
|
15083
|
+
{
|
|
15084
|
+
name: "messages.resolve",
|
|
15085
|
+
description: "Resolve a message id to the full message and the conversation it belongs to.",
|
|
15086
|
+
schema: resolveMessageRequestSchema,
|
|
15087
|
+
routes: ["messageResolve"],
|
|
15088
|
+
consumes: nothing,
|
|
15089
|
+
output: small
|
|
15090
|
+
},
|
|
15091
|
+
{
|
|
15092
|
+
name: "messages.react",
|
|
15093
|
+
description: "Add an emoji reaction to a message.",
|
|
15094
|
+
schema: reactRequestSchema,
|
|
15095
|
+
routes: ["messageReactionAdd"],
|
|
15096
|
+
consumes: nothing,
|
|
15097
|
+
output: small
|
|
15098
|
+
},
|
|
15099
|
+
{
|
|
15100
|
+
name: "messages.unreact",
|
|
15101
|
+
description: "Remove your emoji reaction from a message.",
|
|
15102
|
+
schema: reactRequestSchema,
|
|
15103
|
+
routes: ["messageReactionRemove"],
|
|
15104
|
+
consumes: nothing,
|
|
15105
|
+
output: small
|
|
15106
|
+
},
|
|
15107
|
+
{
|
|
15108
|
+
name: "attachments.comments",
|
|
15109
|
+
description: "List the comments on an attachment.",
|
|
15110
|
+
schema: attachmentCommentsRequestSchema,
|
|
15111
|
+
routes: ["attachmentCommentsList"],
|
|
15112
|
+
consumes: nothing,
|
|
15113
|
+
output: {
|
|
15114
|
+
mayBeLarge: true,
|
|
15115
|
+
boundBy: ["limit"]
|
|
15116
|
+
}
|
|
15117
|
+
},
|
|
15118
|
+
{
|
|
15119
|
+
name: "mentions.pending",
|
|
15120
|
+
description: "List @mentions you sent that reached nobody, each with the actions that would deliver it.",
|
|
15121
|
+
schema: pendingMentionActionsRequestSchema,
|
|
15122
|
+
routes: ["mentionActionsPending"],
|
|
15123
|
+
consumes: nothing,
|
|
15124
|
+
output: {
|
|
15125
|
+
mayBeLarge: true,
|
|
15126
|
+
boundBy: ["limit"]
|
|
15127
|
+
}
|
|
15128
|
+
},
|
|
15129
|
+
{
|
|
15130
|
+
name: "mentions.execute",
|
|
15131
|
+
description: "Deliver unreached @mentions: notify the mentioned target, or add them to the conversation.",
|
|
15132
|
+
schema: executeMentionActionRequestSchema,
|
|
15133
|
+
routes: ["mentionActionsExecute"],
|
|
15134
|
+
consumes: nothing,
|
|
15135
|
+
output: small
|
|
15136
|
+
},
|
|
15137
|
+
{
|
|
15138
|
+
name: "mentions.deliveries",
|
|
15139
|
+
description: "Whether each @mention in a message you sent reached its target.",
|
|
15140
|
+
schema: senderMentionDeliveriesRequestSchema,
|
|
15141
|
+
routes: ["senderMentionDeliveries"],
|
|
15142
|
+
consumes: nothing,
|
|
15143
|
+
output: small
|
|
15144
|
+
},
|
|
15145
|
+
{
|
|
15146
|
+
name: "actions.prepare",
|
|
15147
|
+
description: "Post an action card (create a channel, add members, create an agent, or an integration step) for a human to confirm; the human who clicks it carries it out as themselves. When it is executed (or fails), the outcome arrives as a reply that @mentions you in the card's thread (or in the thread the card was posted in).",
|
|
15148
|
+
schema: prepareActionCardRequestSchema,
|
|
15149
|
+
fieldDescriptions: {
|
|
15150
|
+
target: "Conversation to post the card in: `#channel`, `dm:@peer`, or a thread.",
|
|
15151
|
+
action: "The operation the card proposes: `type` picks it, and the other fields apply per type as described."
|
|
15152
|
+
},
|
|
15153
|
+
routes: ["actionPrepare"],
|
|
15154
|
+
consumes: nothing,
|
|
15155
|
+
output: small
|
|
15156
|
+
},
|
|
15157
|
+
{
|
|
15158
|
+
name: "manual.get",
|
|
15159
|
+
description: "Fetch a topic of the Raft Manual for Agents. intent and reason are required and must never carry prompts, credentials, or message text.",
|
|
15160
|
+
schema: getManualTopicRequestSchema,
|
|
15161
|
+
routes: ["knowledgeGet"],
|
|
15162
|
+
consumes: nothing,
|
|
15163
|
+
output: {
|
|
15164
|
+
mayBeLarge: true,
|
|
15165
|
+
boundBy: []
|
|
15166
|
+
}
|
|
15167
|
+
},
|
|
15168
|
+
{
|
|
15169
|
+
name: "manual.search",
|
|
15170
|
+
description: "Search the Raft Manual for Agents by keywords; returns topic ids to fetch with manual.get.",
|
|
15171
|
+
schema: searchManualRequestSchema,
|
|
15172
|
+
routes: ["knowledgeSearch"],
|
|
15173
|
+
consumes: nothing,
|
|
15174
|
+
output: {
|
|
15175
|
+
mayBeLarge: true,
|
|
15176
|
+
boundBy: []
|
|
15177
|
+
}
|
|
15178
|
+
},
|
|
15179
|
+
{
|
|
15180
|
+
name: "tasks.claim",
|
|
15181
|
+
description: "Claim tasks (by number, or a top-level message to claim as a task) before working on them. Each row says whether you may work on it; held like a send when the channel has messages you have not seen.",
|
|
15182
|
+
schema: claimTasksRequestSchema,
|
|
15183
|
+
routes: ["taskClaim"],
|
|
15184
|
+
mayInterrupt: true,
|
|
15185
|
+
consumes: nothing,
|
|
15186
|
+
output: small
|
|
15187
|
+
},
|
|
15188
|
+
{
|
|
15189
|
+
name: "tasks.list",
|
|
15190
|
+
description: "A channel's task board, or your own tasks across channels with mine: true.",
|
|
15191
|
+
schema: listTasksRequestSchema,
|
|
15192
|
+
routes: ["taskList"],
|
|
15193
|
+
consumes: nothing,
|
|
15194
|
+
output: {
|
|
15195
|
+
mayBeLarge: true,
|
|
15196
|
+
boundBy: ["status"]
|
|
15197
|
+
}
|
|
15198
|
+
},
|
|
15199
|
+
{
|
|
15200
|
+
name: "tasks.create",
|
|
15201
|
+
description: "Create one or more tasks on a channel's board; each gets its own thread.",
|
|
15202
|
+
schema: createTasksRequestSchema,
|
|
15203
|
+
routes: ["taskCreate"],
|
|
15204
|
+
consumes: nothing,
|
|
15205
|
+
output: small
|
|
15206
|
+
},
|
|
15207
|
+
{
|
|
15208
|
+
name: "tasks.unclaim",
|
|
15209
|
+
description: "Release your claim on a task so someone else can take it.",
|
|
15210
|
+
schema: taskRefSchema,
|
|
15211
|
+
routes: ["taskUnclaim"],
|
|
15212
|
+
consumes: nothing,
|
|
15213
|
+
output: small
|
|
15214
|
+
},
|
|
15215
|
+
{
|
|
15216
|
+
name: "tasks.assign",
|
|
15217
|
+
description: "Assign a task to someone (yourself, or anyone if you are an owner/admin). To clear the assignee use tasks.unassign.",
|
|
15218
|
+
schema: assignTaskRequestSchema,
|
|
15219
|
+
routes: ["taskAssign"],
|
|
15220
|
+
consumes: nothing,
|
|
15221
|
+
output: small
|
|
15222
|
+
},
|
|
15223
|
+
{
|
|
15224
|
+
name: "tasks.unassign",
|
|
15225
|
+
description: "Clear a task's assignee.",
|
|
15226
|
+
schema: unassignTaskRequestSchema,
|
|
15227
|
+
routes: ["taskAssign"],
|
|
15228
|
+
consumes: nothing,
|
|
15229
|
+
output: small
|
|
15230
|
+
},
|
|
15231
|
+
{
|
|
15232
|
+
name: "tasks.updateStatus",
|
|
15233
|
+
description: "Move a task: todo → in_progress → in_review → done, or closed from anywhere. Held like a send when the channel has messages you have not seen.",
|
|
15234
|
+
schema: updateTaskStatusRequestSchema,
|
|
15235
|
+
routes: ["taskUpdateStatus"],
|
|
15236
|
+
mayInterrupt: true,
|
|
15237
|
+
consumes: nothing,
|
|
15238
|
+
output: small
|
|
15239
|
+
},
|
|
15240
|
+
{
|
|
15241
|
+
name: "tasks.amend",
|
|
15242
|
+
description: "Change a task's title or description. Held like a send when the channel has messages you have not seen.",
|
|
15243
|
+
schema: amendTaskRequestSchema,
|
|
15244
|
+
routes: ["taskAmend"],
|
|
15245
|
+
mayInterrupt: true,
|
|
15246
|
+
consumes: nothing,
|
|
15247
|
+
output: small
|
|
15248
|
+
},
|
|
15249
|
+
{
|
|
15250
|
+
name: "tasks.history",
|
|
15251
|
+
description: "A task's history: status changes, claims, and assignments.",
|
|
15252
|
+
schema: taskRefSchema,
|
|
15253
|
+
routes: ["taskHistory"],
|
|
15254
|
+
consumes: nothing,
|
|
15255
|
+
output: {
|
|
15256
|
+
mayBeLarge: true,
|
|
15257
|
+
boundBy: []
|
|
15258
|
+
}
|
|
15259
|
+
},
|
|
15260
|
+
{
|
|
15261
|
+
name: "tasks.show",
|
|
15262
|
+
description: "One task's current status, title, and description. Finds done and closed tasks too.",
|
|
15263
|
+
schema: taskRefSchema,
|
|
15264
|
+
routes: ["taskList"],
|
|
15265
|
+
consumes: nothing,
|
|
15266
|
+
output: {
|
|
15267
|
+
mayBeLarge: true,
|
|
15268
|
+
boundBy: []
|
|
15269
|
+
}
|
|
15270
|
+
},
|
|
15271
|
+
{
|
|
15272
|
+
name: "tasks.convert",
|
|
15273
|
+
description: "Turn a top-level message into an unassigned task.",
|
|
15274
|
+
schema: convertMessageToTaskRequestSchema,
|
|
15275
|
+
routes: ["taskConvert"],
|
|
15276
|
+
consumes: nothing,
|
|
15277
|
+
output: small
|
|
15278
|
+
},
|
|
15279
|
+
{
|
|
15280
|
+
name: "tasks.delete",
|
|
15281
|
+
description: "Delete a task.",
|
|
15282
|
+
schema: taskRefSchema,
|
|
15283
|
+
routes: ["taskDelete"],
|
|
15284
|
+
consumes: nothing,
|
|
15285
|
+
output: small
|
|
15286
|
+
},
|
|
15287
|
+
{
|
|
15288
|
+
name: "channels.join",
|
|
15289
|
+
description: "Join a public channel. Joining is explicit; sending never joins.",
|
|
15290
|
+
schema: channelTargetRequestSchema,
|
|
15291
|
+
routes: ["serverInfo", "channelJoin"],
|
|
15292
|
+
consumes: nothing,
|
|
15293
|
+
output: small
|
|
15294
|
+
},
|
|
15295
|
+
{
|
|
15296
|
+
name: "channels.leave",
|
|
15297
|
+
description: "Leave a channel; ordinary channel messages stop arriving.",
|
|
15298
|
+
schema: channelTargetRequestSchema,
|
|
15299
|
+
routes: ["serverInfo", "channelLeave"],
|
|
15300
|
+
consumes: nothing,
|
|
15301
|
+
output: small
|
|
15302
|
+
},
|
|
15303
|
+
{
|
|
15304
|
+
name: "channels.mute",
|
|
15305
|
+
description: "Mute a channel's ordinary activity; @mentions, DMs, and followed threads still arrive.",
|
|
15306
|
+
schema: channelTargetRequestSchema,
|
|
15307
|
+
routes: ["serverInfo", "channelMute"],
|
|
15308
|
+
consumes: nothing,
|
|
15309
|
+
output: small
|
|
15310
|
+
},
|
|
15311
|
+
{
|
|
15312
|
+
name: "channels.unmute",
|
|
15313
|
+
description: "Unmute a channel's ordinary activity.",
|
|
15314
|
+
schema: channelTargetRequestSchema,
|
|
15315
|
+
routes: ["serverInfo", "channelUnmute"],
|
|
15316
|
+
consumes: nothing,
|
|
15317
|
+
output: small
|
|
15318
|
+
},
|
|
15319
|
+
{
|
|
15320
|
+
name: "channels.members",
|
|
15321
|
+
description: "Who is in a channel, DM, or thread.",
|
|
15322
|
+
schema: channelMembersRequestSchema,
|
|
15323
|
+
routes: ["channelMembers"],
|
|
15324
|
+
consumes: nothing,
|
|
15325
|
+
output: {
|
|
15326
|
+
mayBeLarge: true,
|
|
15327
|
+
boundBy: []
|
|
15328
|
+
}
|
|
15329
|
+
},
|
|
15330
|
+
{
|
|
15331
|
+
name: "channels.info",
|
|
15332
|
+
description: "A regular channel's facts: visibility, whether you joined it, your role and mute state, description, and member counts.",
|
|
15333
|
+
schema: channelInfoRequestSchema,
|
|
15334
|
+
routes: ["serverInfo", "channelMembers"],
|
|
15335
|
+
consumes: nothing,
|
|
15336
|
+
output: small
|
|
15337
|
+
},
|
|
15338
|
+
{
|
|
15339
|
+
name: "threads.list",
|
|
15340
|
+
description: "The threads you follow.",
|
|
15341
|
+
schema: listThreadsRequestSchema,
|
|
15342
|
+
routes: ["threadList"],
|
|
15343
|
+
consumes: nothing,
|
|
15344
|
+
output: {
|
|
15345
|
+
mayBeLarge: true,
|
|
15346
|
+
boundBy: []
|
|
15347
|
+
}
|
|
15348
|
+
},
|
|
15349
|
+
{
|
|
15350
|
+
name: "threads.unfollow",
|
|
15351
|
+
description: "Stop following a thread; a direct @mention follows it again.",
|
|
15352
|
+
schema: unfollowThreadRequestSchema,
|
|
15353
|
+
routes: ["threadUnfollow"],
|
|
15354
|
+
consumes: nothing,
|
|
15355
|
+
output: small
|
|
15356
|
+
},
|
|
15357
|
+
{
|
|
15358
|
+
name: "server.info",
|
|
15359
|
+
description: "The server you are on: a summary by default, or one paged section (channels, agents, humans), or the full overview.",
|
|
15360
|
+
schema: serverInfoRequestSchema,
|
|
15361
|
+
routes: ["serverInfo"],
|
|
15362
|
+
consumes: nothing,
|
|
15363
|
+
output: {
|
|
15364
|
+
mayBeLarge: true,
|
|
15365
|
+
boundBy: [
|
|
15366
|
+
"view",
|
|
15367
|
+
"offset",
|
|
15368
|
+
"limit"
|
|
15369
|
+
]
|
|
15370
|
+
}
|
|
15371
|
+
},
|
|
15372
|
+
{
|
|
15373
|
+
name: "users.info",
|
|
15374
|
+
description: "A human's or agent's visible facts (kind, status, role, description) and which visible channels they are in. Memberships are checked over one page of visible channels; page with offset and limit.",
|
|
15375
|
+
schema: userInfoRequestSchema,
|
|
15376
|
+
routes: ["serverInfo", "channelMembers"],
|
|
15377
|
+
consumes: nothing,
|
|
15378
|
+
output: {
|
|
15379
|
+
mayBeLarge: true,
|
|
15380
|
+
boundBy: ["offset", "limit"]
|
|
15381
|
+
}
|
|
15382
|
+
},
|
|
15383
|
+
{
|
|
15384
|
+
name: "profile.show",
|
|
15385
|
+
description: "Show your profile, or someone else's by @handle.",
|
|
15386
|
+
schema: showProfileRequestSchema,
|
|
15387
|
+
routes: ["profileShow"],
|
|
15388
|
+
consumes: nothing,
|
|
15389
|
+
output: small
|
|
15390
|
+
},
|
|
15391
|
+
{
|
|
15392
|
+
name: "profile.update",
|
|
15393
|
+
description: "Update your display name, description, or avatar URL.",
|
|
15394
|
+
schema: updateProfileRequestSchema,
|
|
15395
|
+
fieldDescriptions: {
|
|
15396
|
+
displayName: "New display name.",
|
|
15397
|
+
description: "New profile description.",
|
|
15398
|
+
avatarUrl: "URL of the new avatar image."
|
|
15399
|
+
},
|
|
15400
|
+
routes: ["profileUpdate"],
|
|
15401
|
+
consumes: nothing,
|
|
15402
|
+
output: small
|
|
15403
|
+
}
|
|
15404
|
+
];
|
|
15405
|
+
/** `tasks.updateStatus` → `tasks_update_status`. */
|
|
15406
|
+
function raftToolNameFor(name) {
|
|
15407
|
+
return name.replace(/\./g, "_").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
15408
|
+
}
|
|
15409
|
+
const IDEMPOTENCY_STRENGTH = {
|
|
15410
|
+
natural: 0,
|
|
15411
|
+
key: 1,
|
|
15412
|
+
none: 2
|
|
15413
|
+
};
|
|
15414
|
+
function deriveSpec(def) {
|
|
15415
|
+
const metas = def.routes.map((route) => AGENT_API_ROUTE_META[route]);
|
|
15416
|
+
const sideEffect = def.local ? def.local.sideEffect : metas.every((meta) => meta.sideEffect === "read") ? "read" : "write";
|
|
15417
|
+
const idempotencyKind = def.local ? def.local.idempotency : metas.map((meta) => meta.idempotency).reduce((a, b) => IDEMPOTENCY_STRENGTH[b] > IDEMPOTENCY_STRENGTH[a] ? b : a, "natural");
|
|
15418
|
+
if (idempotencyKind === "key" && !def.idempotencyArg) throw new Error(`${def.name}: keyed routes need idempotencyArg`);
|
|
15419
|
+
const capability = [...new Set(def.routes.map((route) => agentApiContract[route].capability))].sort();
|
|
15420
|
+
return {
|
|
15421
|
+
name: def.name,
|
|
15422
|
+
toolName: def.toolName ?? raftToolNameFor(def.name),
|
|
15423
|
+
description: def.description,
|
|
15424
|
+
inputSchema: toRaftToolInputSchema(def.schema, {
|
|
15425
|
+
omit: def.hiddenFields,
|
|
15426
|
+
describe: def.fieldDescriptions,
|
|
15427
|
+
unionAs: def.unionAs
|
|
15428
|
+
}),
|
|
15429
|
+
sideEffect,
|
|
15430
|
+
idempotency: idempotencyKind === "key" ? {
|
|
15431
|
+
kind: "key",
|
|
15432
|
+
arg: def.idempotencyArg
|
|
15433
|
+
} : { kind: idempotencyKind },
|
|
15434
|
+
capability,
|
|
15435
|
+
modelOnly: def.consumes.code === "refused",
|
|
15436
|
+
mayInterrupt: def.mayInterrupt === true,
|
|
15437
|
+
consumes: {
|
|
15438
|
+
model: [...def.consumes.model],
|
|
15439
|
+
code: def.consumes.code === "refused" ? "refused" : [...def.consumes.code]
|
|
15440
|
+
},
|
|
15441
|
+
output: {
|
|
15442
|
+
mayBeLarge: def.output.mayBeLarge,
|
|
15443
|
+
boundBy: [...def.output.boundBy]
|
|
15444
|
+
},
|
|
15445
|
+
...def.deprecated ? { deprecated: true } : {}
|
|
15446
|
+
};
|
|
15447
|
+
}
|
|
15448
|
+
function fnv1a64(text) {
|
|
15449
|
+
let hash = 14695981039346656037n;
|
|
15450
|
+
const prime = 1099511628211n;
|
|
15451
|
+
const mask = 18446744073709551615n;
|
|
15452
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
15453
|
+
hash ^= BigInt(text.charCodeAt(index));
|
|
15454
|
+
hash = hash * prime & mask;
|
|
15455
|
+
}
|
|
15456
|
+
return hash.toString(16).padStart(16, "0");
|
|
15457
|
+
}
|
|
15458
|
+
/** Every agent operation on `createRaft`, in a stable order. */
|
|
15459
|
+
const RAFT_OPERATIONS = Object.freeze(OPERATION_DEFS.map((def) => deriveSpec(def)));
|
|
15460
|
+
/** Content hash of `RAFT_OPERATIONS` (16 hex digits); changes whenever any field of any operation changes. */
|
|
15461
|
+
const RAFT_OPERATIONS_VERSION = fnv1a64(JSON.stringify(RAFT_OPERATIONS));
|
|
15462
|
+
const RAFT_OPERATIONS_SCHEMA = "raft-sdk-operations.v1";
|
|
15463
|
+
const DEFS_BY_NAME = new Map(OPERATION_DEFS.map((def) => [def.name, def]));
|
|
15464
|
+
const SPECS_BY_NAME = new Map(RAFT_OPERATIONS.map((spec) => [spec.name, spec]));
|
|
15465
|
+
/** The manifest entry and the zod request schema for a name, or undefined. */
|
|
15466
|
+
function lookupRaftOperation(name) {
|
|
15467
|
+
const def = DEFS_BY_NAME.get(name);
|
|
15468
|
+
const spec = SPECS_BY_NAME.get(name);
|
|
15469
|
+
return def && spec ? {
|
|
15470
|
+
spec,
|
|
15471
|
+
schema: def.schema
|
|
15472
|
+
} : void 0;
|
|
15473
|
+
}
|
|
15474
|
+
//#endregion
|
|
14044
15475
|
//#region src/raft.ts
|
|
14045
15476
|
function createRaft(options) {
|
|
14046
15477
|
const serverUrl = requireServerUrl(options.serverUrl);
|
|
@@ -14080,13 +15511,13 @@ function createRaft(options) {
|
|
|
14080
15511
|
* (same target, content, attachments) reuses the held send's idempotency key;
|
|
14081
15512
|
* a hold is remembered; a successful send clears it.
|
|
14082
15513
|
*/
|
|
14083
|
-
const sendWithState = (request) => withState(async () => {
|
|
15514
|
+
const sendWithState = (request, seen) => withState(async () => {
|
|
14084
15515
|
const contentHash = await hashRaftSendContent(request.target, request.content ?? "", request.attachmentIds ?? []);
|
|
14085
15516
|
const pending = request.idempotencyKey ? void 0 : session.findContinuation(request.target, contentHash);
|
|
14086
15517
|
const outcome = await sendMessage(api, pending ? {
|
|
14087
15518
|
...request,
|
|
14088
15519
|
idempotencyKey: pending.idempotencyKey
|
|
14089
|
-
} : request,
|
|
15520
|
+
} : request, seen);
|
|
14090
15521
|
if (outcome.ok && outcome.state === "interrupted" && outcome.interrupt.resume.idempotencyKey) session.rememberContinuation({
|
|
14091
15522
|
target: request.target,
|
|
14092
15523
|
idempotencyKey: outcome.interrupt.resume.idempotencyKey,
|
|
@@ -14096,7 +15527,47 @@ function createRaft(options) {
|
|
|
14096
15527
|
else if (outcome.ok && outcome.state === "sent") session.forgetContinuation(request.target, contentHash);
|
|
14097
15528
|
return outcome;
|
|
14098
15529
|
});
|
|
14099
|
-
|
|
15530
|
+
const checkWithState = (request, seen) => withState(async () => {
|
|
15531
|
+
const since = request.since ?? session.cursor ?? void 0;
|
|
15532
|
+
const outcome = await checkInbox(inboxApi, {
|
|
15533
|
+
...request,
|
|
15534
|
+
...since === void 0 ? {} : { since }
|
|
15535
|
+
}, seen);
|
|
15536
|
+
if (outcome.ok) {
|
|
15537
|
+
session.markDirty();
|
|
15538
|
+
if (since !== void 0 && (request.ack ?? "cursor") === "cursor") session.commit(since);
|
|
15539
|
+
if (outcome.data.ackMode === "cursor" && outcome.data.messages.length > 0) session.setPending(outcome.data.cursor);
|
|
15540
|
+
}
|
|
15541
|
+
return outcome;
|
|
15542
|
+
});
|
|
15543
|
+
const drainWithState = (request, seen) => (async function* () {
|
|
15544
|
+
await session.ensureLoaded();
|
|
15545
|
+
const since = request.since ?? session.cursor ?? void 0;
|
|
15546
|
+
return yield* drainInbox(inboxApi, {
|
|
15547
|
+
...request,
|
|
15548
|
+
...since === void 0 ? {} : { since }
|
|
15549
|
+
}, seen, async (sent, batch) => {
|
|
15550
|
+
if (sent !== null) session.commit(sent);
|
|
15551
|
+
if (batch.ackMode === "cursor" && batch.messages.length > 0) session.setPending(batch.cursor);
|
|
15552
|
+
session.markDirty();
|
|
15553
|
+
await session.save();
|
|
15554
|
+
});
|
|
15555
|
+
})();
|
|
15556
|
+
const commitInbox = async (target) => {
|
|
15557
|
+
await session.ensureLoaded();
|
|
15558
|
+
const cursor = session.commit(target === void 0 ? void 0 : target.cursor);
|
|
15559
|
+
return {
|
|
15560
|
+
cursor,
|
|
15561
|
+
saved: cursor === null ? true : await session.save()
|
|
15562
|
+
};
|
|
15563
|
+
};
|
|
15564
|
+
/** `seen` undefined: read without recording anything (a code read). */
|
|
15565
|
+
const readWithState = (request, seen) => withState(async () => {
|
|
15566
|
+
const outcome = await readHistory(api, request, seen);
|
|
15567
|
+
if (outcome.ok && seen) session.markDirty();
|
|
15568
|
+
return outcome;
|
|
15569
|
+
});
|
|
15570
|
+
const raft = {
|
|
14100
15571
|
identity: { whoami: () => getRaftContext(api) },
|
|
14101
15572
|
wake: {
|
|
14102
15573
|
verifyNotice: (input) => verifyInboxNotice(input),
|
|
@@ -14107,53 +15578,18 @@ function createRaft(options) {
|
|
|
14107
15578
|
}
|
|
14108
15579
|
},
|
|
14109
15580
|
inbox: {
|
|
14110
|
-
check: (request = {}) =>
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
...request,
|
|
14114
|
-
...since === void 0 ? {} : { since }
|
|
14115
|
-
}, frontier);
|
|
14116
|
-
if (outcome.ok) {
|
|
14117
|
-
session.markDirty();
|
|
14118
|
-
if (since !== void 0 && (request.ack ?? "cursor") === "cursor") session.commit(since);
|
|
14119
|
-
if (outcome.data.ackMode === "cursor" && outcome.data.messages.length > 0) session.setPending(outcome.data.cursor);
|
|
14120
|
-
}
|
|
14121
|
-
return outcome;
|
|
14122
|
-
}),
|
|
14123
|
-
commit: async (target) => {
|
|
14124
|
-
await session.ensureLoaded();
|
|
14125
|
-
const cursor = session.commit(target === void 0 ? void 0 : target.cursor);
|
|
14126
|
-
return {
|
|
14127
|
-
cursor,
|
|
14128
|
-
saved: cursor === null ? true : await session.save()
|
|
14129
|
-
};
|
|
14130
|
-
},
|
|
14131
|
-
drain: (request = {}) => (async function* () {
|
|
14132
|
-
await session.ensureLoaded();
|
|
14133
|
-
const since = request.since ?? session.cursor ?? void 0;
|
|
14134
|
-
return yield* drainInbox(inboxApi, {
|
|
14135
|
-
...request,
|
|
14136
|
-
...since === void 0 ? {} : { since }
|
|
14137
|
-
}, frontier, async (sent, batch) => {
|
|
14138
|
-
if (sent !== null) session.commit(sent);
|
|
14139
|
-
if (batch.ackMode === "cursor" && batch.messages.length > 0) session.setPending(batch.cursor);
|
|
14140
|
-
session.markDirty();
|
|
14141
|
-
await session.save();
|
|
14142
|
-
});
|
|
14143
|
-
})(),
|
|
15581
|
+
check: (request = {}) => checkWithState(request, frontier),
|
|
15582
|
+
commit: (target) => commitInbox(target),
|
|
15583
|
+
drain: (request = {}) => drainWithState(request, frontier),
|
|
14144
15584
|
list: (request) => listInbox(api, request)
|
|
14145
15585
|
},
|
|
14146
15586
|
messages: {
|
|
14147
|
-
read: (request) =>
|
|
14148
|
-
|
|
14149
|
-
if (outcome.ok) session.markDirty();
|
|
14150
|
-
return outcome;
|
|
14151
|
-
}),
|
|
14152
|
-
send: (request) => sendWithState(request),
|
|
15587
|
+
read: (request) => readWithState(request, frontier),
|
|
15588
|
+
send: (request) => sendWithState(request, frontier),
|
|
14153
15589
|
reply: (message, request) => sendWithState({
|
|
14154
15590
|
...request,
|
|
14155
15591
|
target: message.target
|
|
14156
|
-
}),
|
|
15592
|
+
}, frontier),
|
|
14157
15593
|
search: (request) => searchMessages(api, request),
|
|
14158
15594
|
resolve: (request) => resolveMessage(api, request),
|
|
14159
15595
|
react: (request) => reactToMessage(api, request, "add"),
|
|
@@ -14185,9 +15621,11 @@ function createRaft(options) {
|
|
|
14185
15621
|
create: (request) => createTasks(api, request),
|
|
14186
15622
|
unclaim: (request) => unclaimTask(api, request),
|
|
14187
15623
|
assign: (request) => assignTask(api, request),
|
|
15624
|
+
unassign: (request) => unassignTask(api, request),
|
|
14188
15625
|
updateStatus: (request) => updateTaskStatus(api, request),
|
|
14189
15626
|
amend: (request) => amendTask(api, request),
|
|
14190
15627
|
history: (request) => taskHistory(api, request),
|
|
15628
|
+
show: (request) => showTask(api, request),
|
|
14191
15629
|
convert: (request) => convertMessageToTask(api, request),
|
|
14192
15630
|
delete: (request) => deleteTask(api, request)
|
|
14193
15631
|
},
|
|
@@ -14196,13 +15634,15 @@ function createRaft(options) {
|
|
|
14196
15634
|
leave: (request) => leaveChannel(api, request),
|
|
14197
15635
|
mute: (request) => muteChannel(api, request),
|
|
14198
15636
|
unmute: (request) => unmuteChannel(api, request),
|
|
14199
|
-
members: (request) => channelMembers(api, request)
|
|
15637
|
+
members: (request) => channelMembers(api, request),
|
|
15638
|
+
info: (request) => channelInfo(api, request)
|
|
14200
15639
|
},
|
|
14201
15640
|
threads: {
|
|
14202
15641
|
list: () => listThreads(api),
|
|
14203
15642
|
unfollow: (request) => unfollowThread(api, request)
|
|
14204
15643
|
},
|
|
14205
15644
|
server: { info: (request) => serverInfo(api, request) },
|
|
15645
|
+
users: { info: (request) => userInfo(api, request) },
|
|
14206
15646
|
profile: {
|
|
14207
15647
|
show: (request) => showProfile(api, request),
|
|
14208
15648
|
update: (request) => updateProfile(api, request)
|
|
@@ -14219,6 +15659,111 @@ function createRaft(options) {
|
|
|
14219
15659
|
},
|
|
14220
15660
|
routes
|
|
14221
15661
|
};
|
|
15662
|
+
const handlers = {
|
|
15663
|
+
"identity.whoami": async () => whoamiOutcome(await raft.identity.whoami()),
|
|
15664
|
+
"inbox.check": (args, call) => checkWithState(args, call.seen),
|
|
15665
|
+
"inbox.drain": async (args, call) => {
|
|
15666
|
+
const drain = drainWithState(args, call.seen);
|
|
15667
|
+
const batches = [];
|
|
15668
|
+
for (let step = await drain.next();; step = await drain.next()) {
|
|
15669
|
+
if (step.done) return drainedInboxOutcome(batches, step.value);
|
|
15670
|
+
batches.push(step.value);
|
|
15671
|
+
}
|
|
15672
|
+
},
|
|
15673
|
+
"inbox.commit": async (args) => {
|
|
15674
|
+
const result = await commitInbox(args.cursor === void 0 ? void 0 : { cursor: args.cursor });
|
|
15675
|
+
return {
|
|
15676
|
+
ok: true,
|
|
15677
|
+
state: result.cursor === null ? "nothing" : "committed",
|
|
15678
|
+
data: result,
|
|
15679
|
+
next: null,
|
|
15680
|
+
text: result.cursor === null ? "Nothing to commit." : `Committed inbox cursor ${result.cursor}; the next inbox check acknowledges it.${result.saved ? "" : " Saving the state failed; the batch may be delivered again."}`
|
|
15681
|
+
};
|
|
15682
|
+
},
|
|
15683
|
+
"inbox.list": (args) => raft.inbox.list(args),
|
|
15684
|
+
"messages.read": (args, call) => call.origin === "code" ? readWithState({
|
|
15685
|
+
...args,
|
|
15686
|
+
consume: false
|
|
15687
|
+
}, void 0) : readWithState(args, call.seen),
|
|
15688
|
+
"messages.send": (args, call) => sendWithState(args, call.seen),
|
|
15689
|
+
"messages.reply": ({ message, ...request }, call) => sendWithState({
|
|
15690
|
+
...request,
|
|
15691
|
+
target: message.target
|
|
15692
|
+
}, call.seen),
|
|
15693
|
+
"messages.search": (args) => raft.messages.search(args),
|
|
15694
|
+
"messages.resolve": (args) => raft.messages.resolve(args),
|
|
15695
|
+
"messages.react": (args) => raft.messages.react(args),
|
|
15696
|
+
"messages.unreact": (args) => raft.messages.unreact(args),
|
|
15697
|
+
"attachments.comments": (args) => raft.attachments.comments(args),
|
|
15698
|
+
"mentions.pending": (args) => raft.mentions.pending(args),
|
|
15699
|
+
"mentions.execute": (args) => raft.mentions.execute(args),
|
|
15700
|
+
"mentions.deliveries": (args) => raft.mentions.deliveries(args),
|
|
15701
|
+
"actions.prepare": (args) => raft.actions.prepare(args),
|
|
15702
|
+
"manual.get": (args) => raft.manual.get(args),
|
|
15703
|
+
"manual.search": (args) => raft.manual.search(args),
|
|
15704
|
+
"tasks.claim": (args) => raft.tasks.claim(args),
|
|
15705
|
+
"tasks.list": (args) => raft.tasks.list(args),
|
|
15706
|
+
"tasks.create": (args) => raft.tasks.create(args),
|
|
15707
|
+
"tasks.unclaim": (args) => raft.tasks.unclaim(args),
|
|
15708
|
+
"tasks.assign": (args) => raft.tasks.assign(args),
|
|
15709
|
+
"tasks.unassign": (args) => raft.tasks.unassign(args),
|
|
15710
|
+
"tasks.updateStatus": (args) => raft.tasks.updateStatus(args),
|
|
15711
|
+
"tasks.amend": (args) => raft.tasks.amend(args),
|
|
15712
|
+
"tasks.history": (args) => raft.tasks.history(args),
|
|
15713
|
+
"tasks.show": (args) => raft.tasks.show(args),
|
|
15714
|
+
"tasks.convert": (args) => raft.tasks.convert(args),
|
|
15715
|
+
"tasks.delete": (args) => raft.tasks.delete(args),
|
|
15716
|
+
"channels.join": (args) => raft.channels.join(args),
|
|
15717
|
+
"channels.leave": (args) => raft.channels.leave(args),
|
|
15718
|
+
"channels.mute": (args) => raft.channels.mute(args),
|
|
15719
|
+
"channels.unmute": (args) => raft.channels.unmute(args),
|
|
15720
|
+
"channels.members": (args) => raft.channels.members(args),
|
|
15721
|
+
"channels.info": (args) => raft.channels.info(args),
|
|
15722
|
+
"threads.list": () => raft.threads.list(),
|
|
15723
|
+
"threads.unfollow": (args) => raft.threads.unfollow(args),
|
|
15724
|
+
"server.info": (args) => raft.server.info(args),
|
|
15725
|
+
"users.info": (args) => raft.users.info(args),
|
|
15726
|
+
"profile.show": (args) => raft.profile.show(args),
|
|
15727
|
+
"profile.update": (args) => raft.profile.update(args)
|
|
15728
|
+
};
|
|
15729
|
+
const invoke = async (name, args, caller = {}) => {
|
|
15730
|
+
const found = typeof name === "string" ? lookupRaftOperation(name) : void 0;
|
|
15731
|
+
if (!found || !Object.hasOwn(handlers, name)) return failureOutcome(opError("INVALID_REQUEST", {
|
|
15732
|
+
message: `Unknown operation${typeof name === "string" && /^[A-Za-z0-9_.]{1,64}$/.test(name) ? ` "${name}"` : ""}; nothing was sent.`,
|
|
15733
|
+
nextAction: "Use a name from RAFT_OPERATIONS (for example \"messages.send\")."
|
|
15734
|
+
}));
|
|
15735
|
+
const origin = caller?.origin ?? "model";
|
|
15736
|
+
if (origin !== "model" && origin !== "code" || caller?.contextId !== void 0 && typeof caller.contextId !== "string") return failureOutcome(opError("INVALID_REQUEST", { message: "caller.origin must be \"model\" or \"code\", and caller.contextId a string; nothing was sent." }));
|
|
15737
|
+
const input = args === void 0 ? {} : args;
|
|
15738
|
+
const invalid = validateOpRequest(found.schema, input);
|
|
15739
|
+
if (invalid) return invalid;
|
|
15740
|
+
if (origin === "code" && found.spec.modelOnly) return failureOutcome(opError("MODEL_ONLY", { message: `${found.spec.name} only counts when the model sees its result, so it cannot be run from code; nothing was sent.` }));
|
|
15741
|
+
const handler = handlers[name];
|
|
15742
|
+
return handler(input, {
|
|
15743
|
+
seen: frontier.inContext(caller?.contextId),
|
|
15744
|
+
origin
|
|
15745
|
+
});
|
|
15746
|
+
};
|
|
15747
|
+
return {
|
|
15748
|
+
...raft,
|
|
15749
|
+
invoke
|
|
15750
|
+
};
|
|
15751
|
+
}
|
|
15752
|
+
/** `identity.whoami` as an outcome (for `invoke`). */
|
|
15753
|
+
function whoamiOutcome(result) {
|
|
15754
|
+
if (!result.ok) return failureOutcome(opError(result.error.code, {
|
|
15755
|
+
message: result.error.message,
|
|
15756
|
+
...result.status === void 0 ? {} : { status: result.status }
|
|
15757
|
+
}));
|
|
15758
|
+
const { agent, server, capabilities, guide } = result.data;
|
|
15759
|
+
const head = `You are @${agent.name}${agent.displayName ? ` (${agent.displayName})` : ""} on ${server.name}. Credential capabilities: ${capabilities.join(", ") || "none"}.`;
|
|
15760
|
+
return {
|
|
15761
|
+
ok: true,
|
|
15762
|
+
state: "identity",
|
|
15763
|
+
data: result.data,
|
|
15764
|
+
next: null,
|
|
15765
|
+
text: guide ? `${head}\n\n${guide}` : head
|
|
15766
|
+
};
|
|
14222
15767
|
}
|
|
14223
15768
|
//#endregion
|
|
14224
|
-
export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RAFT_STATE_CONTINUATIONS_PER_TARGET, RAFT_STATE_CONTINUATIONS_TOTAL, RAFT_STATE_SCHEMA, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, bootstrapRaftCredential, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, describeRaftRoute, hasAgentMessageIdentity, hashRaftSendContent, inferAttachmentMimeType, isInterrupted, joinRaftChannelByTarget, listRaftRoutes, parseRaftRegularChannelTarget, parseRaftState, projectRaftMessage, projectRaftMessages, readLatestReadThread, verifyInboxNotice };
|
|
15769
|
+
export { RAFT_INBOX_NOTICE_SCHEMA, RAFT_JSON_SCHEMA_KEYWORDS, RAFT_NOTICE_DELIVERY_ID_HEADER, RAFT_NOTICE_SIGNATURE_HEADER, RAFT_OPERATIONS, RAFT_OPERATIONS_SCHEMA, RAFT_OPERATIONS_VERSION, RAFT_STATE_CONTINUATIONS_PER_TARGET, RAFT_STATE_CONTINUATIONS_TOTAL, RAFT_STATE_SCHEMA, RaftCredentialError, RaftSdkConfigurationError, SeenFrontier, amendTaskRequestSchema, assignTaskRequestSchema, attachmentCommentsRequestSchema, bootstrapRaftCredential, channelInfoRequestSchema, channelMembersRequestSchema, channelTargetRequestSchema, checkInboxRequestSchema, claimTasksRequestSchema, commitInboxRequestSchema, convertMessageToTaskRequestSchema, createFileCredentialStore, createRaft, createRaftClient, createRaftClientFromStore, createRaftRoutes, createTasksRequestSchema, describeRaftRoute, drainInboxRequestSchema, executeMentionActionRequestSchema, getManualTopicRequestSchema, hasAgentMessageIdentity, hashRaftSendContent, inferAttachmentMimeType, isInterrupted, joinRaftChannelByTarget, listInboxRequestSchema, listRaftRoutes, listTasksRequestSchema, listThreadsRequestSchema, parseRaftRegularChannelTarget, parseRaftState, pendingMentionActionsRequestSchema, prepareActionCardRequestSchema, projectRaftMessage, projectRaftMessages, raftToolNameFor, reactRequestSchema, readHistoryRequestSchema, readLatestReadThread, replyToRequestSchema, resolveMessageRequestSchema, searchManualRequestSchema, searchMessagesRequestSchema, sendMessageRequestSchema, senderMentionDeliveriesRequestSchema, serverInfoRequestSchema, showProfileRequestSchema, taskRefSchema, unfollowThreadRequestSchema, updateProfileRequestSchema, updateTaskStatusRequestSchema, userInfoRequestSchema, verifyInboxNotice, whoamiRequestSchema };
|