@botiverse/raft-sdk 0.3.0 → 0.3.1
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 +50 -0
- package/dist/cjs/index.cjs +129 -2
- package/dist/esm/index.js +130 -4
- package/dist/index.d.ts +47 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -363,6 +363,56 @@ record through `options.store`. It returns only non-secret identity metadata.
|
|
|
363
363
|
Creates the explicit Node.js file store described above. Relative paths and
|
|
364
364
|
unsafe stored-file permissions fail closed.
|
|
365
365
|
|
|
366
|
+
### `readLatestReadThread(options?)`
|
|
367
|
+
|
|
368
|
+
Node.js only. Returns the thread this agent read most recently with
|
|
369
|
+
`raft message read` on this machine. It reads the record the Raft CLI keeps
|
|
370
|
+
locally: no credential, no login, and no request to the Server.
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
import { readLatestReadThread } from "@botiverse/raft-sdk";
|
|
374
|
+
|
|
375
|
+
const latest = await readLatestReadThread();
|
|
376
|
+
if (latest.state === "thread") {
|
|
377
|
+
console.log(latest.target); // "#general:1a2b3c4d"
|
|
378
|
+
console.log(latest.parentTarget); // "#general"
|
|
379
|
+
} else {
|
|
380
|
+
console.log(latest.reason);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Options, all optional:
|
|
385
|
+
|
|
386
|
+
- `agentId`: whose reads to look at. Defaults to `SLOCK_AGENT_ID`, which the
|
|
387
|
+
Raft daemon sets for the agents it runs.
|
|
388
|
+
- `home`: the Raft home directory. Defaults to what the CLI uses: `RAFT_HOME`,
|
|
389
|
+
then `SLOCK_HOME`, then `~/.slock`.
|
|
390
|
+
- `env`: the environment to take those defaults from. Defaults to
|
|
391
|
+
`process.env`.
|
|
392
|
+
|
|
393
|
+
When there is no thread to report, `state` is `"none"` and `reason` is one of:
|
|
394
|
+
|
|
395
|
+
| `reason` | Meaning |
|
|
396
|
+
| --- | --- |
|
|
397
|
+
| `no_agent_id` | No `agentId` was given and `SLOCK_AGENT_ID` is not set. |
|
|
398
|
+
| `no_record` | The CLI has kept no read record for this agent on this machine. |
|
|
399
|
+
| `unreadable` | A record exists but is not a private file of this user, or is not valid. |
|
|
400
|
+
| `no_reads` | The record holds no read. |
|
|
401
|
+
| `latest_read_is_not_a_thread` | The latest read was a channel or a DM, not a thread. |
|
|
402
|
+
|
|
403
|
+
What to keep in mind:
|
|
404
|
+
|
|
405
|
+
- It reports what was read last, not what the work belongs to. An agent that
|
|
406
|
+
read an unrelated thread afterwards gets that thread. Use the answer as a
|
|
407
|
+
default to confirm.
|
|
408
|
+
- An older thread is never substituted when the latest read was a channel or
|
|
409
|
+
a DM.
|
|
410
|
+
- Only `raft message read` counts. `raft message check` and reads made
|
|
411
|
+
through this SDK's `messages.read()` are not in the CLI's record.
|
|
412
|
+
- A thread target contains the channel name. Do not publish the target of a
|
|
413
|
+
private channel's thread.
|
|
414
|
+
- The function only reads. It never creates or changes the CLI's record.
|
|
415
|
+
|
|
366
416
|
### `createRaftClientFromStore(options)`
|
|
367
417
|
|
|
368
418
|
Loads and validates one `RaftCredentialStore` record, then creates the same
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ let node_crypto = require("node:crypto");
|
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
4
|
let node_fs_promises = require("node:fs/promises");
|
|
5
5
|
let node_path = require("node:path");
|
|
6
|
+
let node_os = require("node:os");
|
|
6
7
|
Object.freeze({ status: "aborted" });
|
|
7
8
|
function $constructor(name, initializer, params) {
|
|
8
9
|
function init(inst, def) {
|
|
@@ -5443,6 +5444,7 @@ const agentApiIntegrationAppUpdateBodySchema = passthroughObject({
|
|
|
5443
5444
|
clientKey: string().trim().min(1),
|
|
5444
5445
|
name: optionalStringSchema,
|
|
5445
5446
|
description: optionalStringSchema,
|
|
5447
|
+
whenToUse: optionalStringSchema,
|
|
5446
5448
|
category: string().trim().min(1).optional(),
|
|
5447
5449
|
homepageUrl: optionalStringSchema,
|
|
5448
5450
|
returnUrl: optionalStringSchema,
|
|
@@ -5623,6 +5625,7 @@ const agentApiIntegrationServiceSchema = passthroughObject({
|
|
|
5623
5625
|
]).optional(),
|
|
5624
5626
|
name: string(),
|
|
5625
5627
|
description: nullableStringSchema,
|
|
5628
|
+
whenToUse: nullableStringSchema.optional(),
|
|
5626
5629
|
homepageUrl: nullableStringSchema,
|
|
5627
5630
|
returnUrl: nullableStringSchema,
|
|
5628
5631
|
agentManifestUrl: nullableStringSchema,
|
|
@@ -11192,6 +11195,73 @@ var SeenFrontier = class SeenFrontier {
|
|
|
11192
11195
|
}
|
|
11193
11196
|
};
|
|
11194
11197
|
//#endregion
|
|
11198
|
+
//#region ../shared/src/agentOps/latestReadThread.ts
|
|
11199
|
+
const CLI_READ_STATE_NAMESPACE = "slock-cli-consumed-seq";
|
|
11200
|
+
const CLI_READ_STATE_FILENAME = "consumed-seqs.json";
|
|
11201
|
+
const RAFT_HOME_ENVS = ["RAFT_HOME", "SLOCK_HOME"];
|
|
11202
|
+
/** The Raft home the environment configures, or undefined when the default (`~/.slock`) applies. */
|
|
11203
|
+
function configuredRaftHome(env) {
|
|
11204
|
+
for (const name of RAFT_HOME_ENVS) {
|
|
11205
|
+
const value = env[name]?.trim();
|
|
11206
|
+
if (value) return value;
|
|
11207
|
+
}
|
|
11208
|
+
}
|
|
11209
|
+
/** The base directory the environment configures for the read record, or undefined when the default applies. */
|
|
11210
|
+
function configuredCliReadStateBase(env) {
|
|
11211
|
+
return env["SLOCK_CLI_CONSUMED_SEQ_STATE_DIR"]?.trim() || configuredRaftHome(env);
|
|
11212
|
+
}
|
|
11213
|
+
/** An agent id is a path segment of the record's location, so it must be a plain one. */
|
|
11214
|
+
function isLocalStateAgentId(agentId) {
|
|
11215
|
+
return /^[A-Za-z0-9_-]+$/.test(agentId);
|
|
11216
|
+
}
|
|
11217
|
+
/** The record's location under the base directory, as path segments. */
|
|
11218
|
+
function cliReadStatePathSegments(agentId) {
|
|
11219
|
+
if (!isLocalStateAgentId(agentId)) throw new Error("Invalid local state agent identity");
|
|
11220
|
+
return [
|
|
11221
|
+
CLI_READ_STATE_NAMESPACE,
|
|
11222
|
+
agentId,
|
|
11223
|
+
CLI_READ_STATE_FILENAME
|
|
11224
|
+
];
|
|
11225
|
+
}
|
|
11226
|
+
function positiveFinite(value) {
|
|
11227
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
11228
|
+
}
|
|
11229
|
+
/** `#channel:thread` → `#channel`, `dm:@peer:thread` → `dm:@peer`; null for a channel or DM root. */
|
|
11230
|
+
function parentTargetOfThread(target) {
|
|
11231
|
+
const from = target.startsWith("dm:@") ? 4 : target.startsWith("#") ? 1 : -1;
|
|
11232
|
+
if (from < 0) return null;
|
|
11233
|
+
const separator = target.indexOf(":", from);
|
|
11234
|
+
return separator > 0 && separator < target.length - 1 ? target.slice(0, separator) : null;
|
|
11235
|
+
}
|
|
11236
|
+
function latestReadThreadFromCliReadState(raw) {
|
|
11237
|
+
const targets = raw && typeof raw === "object" ? raw.targets : void 0;
|
|
11238
|
+
let latestTarget;
|
|
11239
|
+
let latestOrder = 0;
|
|
11240
|
+
if (targets && typeof targets === "object" && !Array.isArray(targets)) for (const [target, value] of Object.entries(targets)) {
|
|
11241
|
+
if (target.length === 0) continue;
|
|
11242
|
+
const record = typeof value === "number" ? { seq: value } : value && typeof value === "object" ? value : {};
|
|
11243
|
+
const order = positiveFinite(record.readOrder) ?? positiveFinite(record.seq);
|
|
11244
|
+
if (order !== void 0 && order > latestOrder) {
|
|
11245
|
+
latestOrder = order;
|
|
11246
|
+
latestTarget = target;
|
|
11247
|
+
}
|
|
11248
|
+
}
|
|
11249
|
+
if (latestTarget === void 0) return {
|
|
11250
|
+
state: "none",
|
|
11251
|
+
reason: "no_reads"
|
|
11252
|
+
};
|
|
11253
|
+
const parentTarget = parentTargetOfThread(latestTarget);
|
|
11254
|
+
if (parentTarget === null) return {
|
|
11255
|
+
state: "none",
|
|
11256
|
+
reason: "latest_read_is_not_a_thread"
|
|
11257
|
+
};
|
|
11258
|
+
return {
|
|
11259
|
+
state: "thread",
|
|
11260
|
+
target: latestTarget,
|
|
11261
|
+
parentTarget
|
|
11262
|
+
};
|
|
11263
|
+
}
|
|
11264
|
+
//#endregion
|
|
11195
11265
|
//#region ../shared/src/agentOps/inbox.ts
|
|
11196
11266
|
const MAX_DRAIN_ROUNDS = 50;
|
|
11197
11267
|
function batchNext(batch) {
|
|
@@ -12308,7 +12378,7 @@ function formatAgentServerInfo(data) {
|
|
|
12308
12378
|
text += "Server-profile changes still use raft server update and remain server-role gated.\n";
|
|
12309
12379
|
text += "Mute state is shown when the server provides it; otherwise it is omitted.\n";
|
|
12310
12380
|
if (channels.length > 0) for (const t of channels) {
|
|
12311
|
-
const statusParts = [t
|
|
12381
|
+
const statusParts = [channelVisibility(t), t.joined ? "joined" : "not joined"];
|
|
12312
12382
|
if (t.channelRole) statusParts.push(`channel role=${t.channelRole}`);
|
|
12313
12383
|
if (t.channelAdminBasis) statusParts.push(`admin via=${t.channelAdminBasis}`);
|
|
12314
12384
|
const muted = channelMuted(t);
|
|
@@ -12343,6 +12413,7 @@ function agentChannelRef(name, type) {
|
|
|
12343
12413
|
}
|
|
12344
12414
|
function channelVisibility(channel) {
|
|
12345
12415
|
const type = channel.type?.trim();
|
|
12416
|
+
if (type === "joint") return "joint";
|
|
12346
12417
|
return type === "private" || type === "dm" ? "private" : "public";
|
|
12347
12418
|
}
|
|
12348
12419
|
function channelMuted(channel) {
|
|
@@ -13923,12 +13994,67 @@ async function prepareActionCard(client, request) {
|
|
|
13923
13994
|
target: request.target,
|
|
13924
13995
|
messageId: card.messageId
|
|
13925
13996
|
},
|
|
13926
|
-
why: "A human must click the card to commit it; its
|
|
13997
|
+
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."
|
|
13927
13998
|
},
|
|
13928
13999
|
text: formatAgentActionCardPosted(request.target, card.messageId)
|
|
13929
14000
|
};
|
|
13930
14001
|
}
|
|
13931
14002
|
//#endregion
|
|
14003
|
+
//#region src/latestReadThread.ts
|
|
14004
|
+
const MAX_READ_STATE_BYTES = 33554432;
|
|
14005
|
+
function firstNonEmpty(...values) {
|
|
14006
|
+
for (const value of values) {
|
|
14007
|
+
const trimmed = value?.trim();
|
|
14008
|
+
if (trimmed) return trimmed;
|
|
14009
|
+
}
|
|
14010
|
+
}
|
|
14011
|
+
/**
|
|
14012
|
+
* The thread this agent read most recently with `raft message read`, when
|
|
14013
|
+
* that read is still the latest read of any conversation.
|
|
14014
|
+
*
|
|
14015
|
+
* It is a fact about what was read, not about what the work belongs to: an
|
|
14016
|
+
* agent that read an unrelated thread afterwards gets that thread. Treat the
|
|
14017
|
+
* answer as a default to confirm, and keep private channel names out of
|
|
14018
|
+
* public places.
|
|
14019
|
+
*/
|
|
14020
|
+
async function readLatestReadThread(options = {}) {
|
|
14021
|
+
const env = options.env ?? process.env;
|
|
14022
|
+
const agentId = firstNonEmpty(options.agentId, env.SLOCK_AGENT_ID);
|
|
14023
|
+
if (!agentId || !isLocalStateAgentId(agentId)) return {
|
|
14024
|
+
state: "none",
|
|
14025
|
+
reason: "no_agent_id"
|
|
14026
|
+
};
|
|
14027
|
+
const base = firstNonEmpty(options.home, configuredCliReadStateBase(env)) ?? (0, node_path.join)((0, node_os.homedir)(), ".slock");
|
|
14028
|
+
const filePath = (0, node_path.join)(base, ...cliReadStatePathSegments(agentId));
|
|
14029
|
+
let handle;
|
|
14030
|
+
try {
|
|
14031
|
+
handle = await (0, node_fs_promises.open)(filePath, node_fs.constants.O_RDONLY | node_fs.constants.O_NOFOLLOW);
|
|
14032
|
+
} catch (error) {
|
|
14033
|
+
return {
|
|
14034
|
+
state: "none",
|
|
14035
|
+
reason: error.code === "ENOENT" ? "no_record" : "unreadable"
|
|
14036
|
+
};
|
|
14037
|
+
}
|
|
14038
|
+
let raw;
|
|
14039
|
+
try {
|
|
14040
|
+
const stat = await handle.stat();
|
|
14041
|
+
const ownedByUser = typeof process.getuid !== "function" || stat.uid === process.getuid() && (stat.mode & 63) === 0;
|
|
14042
|
+
if (!stat.isFile() || !ownedByUser || stat.size > MAX_READ_STATE_BYTES) return {
|
|
14043
|
+
state: "none",
|
|
14044
|
+
reason: "unreadable"
|
|
14045
|
+
};
|
|
14046
|
+
raw = JSON.parse(await handle.readFile("utf8"));
|
|
14047
|
+
} catch {
|
|
14048
|
+
return {
|
|
14049
|
+
state: "none",
|
|
14050
|
+
reason: "unreadable"
|
|
14051
|
+
};
|
|
14052
|
+
} finally {
|
|
14053
|
+
await handle.close();
|
|
14054
|
+
}
|
|
14055
|
+
return latestReadThreadFromCliReadState(raw);
|
|
14056
|
+
}
|
|
14057
|
+
//#endregion
|
|
13932
14058
|
//#region src/raft.ts
|
|
13933
14059
|
function createRaft(options) {
|
|
13934
14060
|
const serverUrl = requireServerUrl(options.serverUrl);
|
|
@@ -14134,4 +14260,5 @@ exports.parseRaftRegularChannelTarget = parseRaftRegularChannelTarget;
|
|
|
14134
14260
|
exports.parseRaftState = parseRaftState;
|
|
14135
14261
|
exports.projectRaftMessage = projectRaftMessage;
|
|
14136
14262
|
exports.projectRaftMessages = projectRaftMessages;
|
|
14263
|
+
exports.readLatestReadThread = readLatestReadThread;
|
|
14137
14264
|
exports.verifyInboxNotice = verifyInboxNotice;
|
package/dist/esm/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { chmod, link, lstat, mkdir, open, unlink } from "node:fs/promises";
|
|
4
|
-
import { dirname, isAbsolute } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
5
6
|
Object.freeze({ status: "aborted" });
|
|
6
7
|
function $constructor(name, initializer, params) {
|
|
7
8
|
function init(inst, def) {
|
|
@@ -5442,6 +5443,7 @@ const agentApiIntegrationAppUpdateBodySchema = passthroughObject({
|
|
|
5442
5443
|
clientKey: string().trim().min(1),
|
|
5443
5444
|
name: optionalStringSchema,
|
|
5444
5445
|
description: optionalStringSchema,
|
|
5446
|
+
whenToUse: optionalStringSchema,
|
|
5445
5447
|
category: string().trim().min(1).optional(),
|
|
5446
5448
|
homepageUrl: optionalStringSchema,
|
|
5447
5449
|
returnUrl: optionalStringSchema,
|
|
@@ -5622,6 +5624,7 @@ const agentApiIntegrationServiceSchema = passthroughObject({
|
|
|
5622
5624
|
]).optional(),
|
|
5623
5625
|
name: string(),
|
|
5624
5626
|
description: nullableStringSchema,
|
|
5627
|
+
whenToUse: nullableStringSchema.optional(),
|
|
5625
5628
|
homepageUrl: nullableStringSchema,
|
|
5626
5629
|
returnUrl: nullableStringSchema,
|
|
5627
5630
|
agentManifestUrl: nullableStringSchema,
|
|
@@ -11191,6 +11194,73 @@ var SeenFrontier = class SeenFrontier {
|
|
|
11191
11194
|
}
|
|
11192
11195
|
};
|
|
11193
11196
|
//#endregion
|
|
11197
|
+
//#region ../shared/src/agentOps/latestReadThread.ts
|
|
11198
|
+
const CLI_READ_STATE_NAMESPACE = "slock-cli-consumed-seq";
|
|
11199
|
+
const CLI_READ_STATE_FILENAME = "consumed-seqs.json";
|
|
11200
|
+
const RAFT_HOME_ENVS = ["RAFT_HOME", "SLOCK_HOME"];
|
|
11201
|
+
/** The Raft home the environment configures, or undefined when the default (`~/.slock`) applies. */
|
|
11202
|
+
function configuredRaftHome(env) {
|
|
11203
|
+
for (const name of RAFT_HOME_ENVS) {
|
|
11204
|
+
const value = env[name]?.trim();
|
|
11205
|
+
if (value) return value;
|
|
11206
|
+
}
|
|
11207
|
+
}
|
|
11208
|
+
/** The base directory the environment configures for the read record, or undefined when the default applies. */
|
|
11209
|
+
function configuredCliReadStateBase(env) {
|
|
11210
|
+
return env["SLOCK_CLI_CONSUMED_SEQ_STATE_DIR"]?.trim() || configuredRaftHome(env);
|
|
11211
|
+
}
|
|
11212
|
+
/** An agent id is a path segment of the record's location, so it must be a plain one. */
|
|
11213
|
+
function isLocalStateAgentId(agentId) {
|
|
11214
|
+
return /^[A-Za-z0-9_-]+$/.test(agentId);
|
|
11215
|
+
}
|
|
11216
|
+
/** The record's location under the base directory, as path segments. */
|
|
11217
|
+
function cliReadStatePathSegments(agentId) {
|
|
11218
|
+
if (!isLocalStateAgentId(agentId)) throw new Error("Invalid local state agent identity");
|
|
11219
|
+
return [
|
|
11220
|
+
CLI_READ_STATE_NAMESPACE,
|
|
11221
|
+
agentId,
|
|
11222
|
+
CLI_READ_STATE_FILENAME
|
|
11223
|
+
];
|
|
11224
|
+
}
|
|
11225
|
+
function positiveFinite(value) {
|
|
11226
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
11227
|
+
}
|
|
11228
|
+
/** `#channel:thread` → `#channel`, `dm:@peer:thread` → `dm:@peer`; null for a channel or DM root. */
|
|
11229
|
+
function parentTargetOfThread(target) {
|
|
11230
|
+
const from = target.startsWith("dm:@") ? 4 : target.startsWith("#") ? 1 : -1;
|
|
11231
|
+
if (from < 0) return null;
|
|
11232
|
+
const separator = target.indexOf(":", from);
|
|
11233
|
+
return separator > 0 && separator < target.length - 1 ? target.slice(0, separator) : null;
|
|
11234
|
+
}
|
|
11235
|
+
function latestReadThreadFromCliReadState(raw) {
|
|
11236
|
+
const targets = raw && typeof raw === "object" ? raw.targets : void 0;
|
|
11237
|
+
let latestTarget;
|
|
11238
|
+
let latestOrder = 0;
|
|
11239
|
+
if (targets && typeof targets === "object" && !Array.isArray(targets)) for (const [target, value] of Object.entries(targets)) {
|
|
11240
|
+
if (target.length === 0) continue;
|
|
11241
|
+
const record = typeof value === "number" ? { seq: value } : value && typeof value === "object" ? value : {};
|
|
11242
|
+
const order = positiveFinite(record.readOrder) ?? positiveFinite(record.seq);
|
|
11243
|
+
if (order !== void 0 && order > latestOrder) {
|
|
11244
|
+
latestOrder = order;
|
|
11245
|
+
latestTarget = target;
|
|
11246
|
+
}
|
|
11247
|
+
}
|
|
11248
|
+
if (latestTarget === void 0) return {
|
|
11249
|
+
state: "none",
|
|
11250
|
+
reason: "no_reads"
|
|
11251
|
+
};
|
|
11252
|
+
const parentTarget = parentTargetOfThread(latestTarget);
|
|
11253
|
+
if (parentTarget === null) return {
|
|
11254
|
+
state: "none",
|
|
11255
|
+
reason: "latest_read_is_not_a_thread"
|
|
11256
|
+
};
|
|
11257
|
+
return {
|
|
11258
|
+
state: "thread",
|
|
11259
|
+
target: latestTarget,
|
|
11260
|
+
parentTarget
|
|
11261
|
+
};
|
|
11262
|
+
}
|
|
11263
|
+
//#endregion
|
|
11194
11264
|
//#region ../shared/src/agentOps/inbox.ts
|
|
11195
11265
|
const MAX_DRAIN_ROUNDS = 50;
|
|
11196
11266
|
function batchNext(batch) {
|
|
@@ -12307,7 +12377,7 @@ function formatAgentServerInfo(data) {
|
|
|
12307
12377
|
text += "Server-profile changes still use raft server update and remain server-role gated.\n";
|
|
12308
12378
|
text += "Mute state is shown when the server provides it; otherwise it is omitted.\n";
|
|
12309
12379
|
if (channels.length > 0) for (const t of channels) {
|
|
12310
|
-
const statusParts = [t
|
|
12380
|
+
const statusParts = [channelVisibility(t), t.joined ? "joined" : "not joined"];
|
|
12311
12381
|
if (t.channelRole) statusParts.push(`channel role=${t.channelRole}`);
|
|
12312
12382
|
if (t.channelAdminBasis) statusParts.push(`admin via=${t.channelAdminBasis}`);
|
|
12313
12383
|
const muted = channelMuted(t);
|
|
@@ -12342,6 +12412,7 @@ function agentChannelRef(name, type) {
|
|
|
12342
12412
|
}
|
|
12343
12413
|
function channelVisibility(channel) {
|
|
12344
12414
|
const type = channel.type?.trim();
|
|
12415
|
+
if (type === "joint") return "joint";
|
|
12345
12416
|
return type === "private" || type === "dm" ? "private" : "public";
|
|
12346
12417
|
}
|
|
12347
12418
|
function channelMuted(channel) {
|
|
@@ -13922,12 +13993,67 @@ async function prepareActionCard(client, request) {
|
|
|
13922
13993
|
target: request.target,
|
|
13923
13994
|
messageId: card.messageId
|
|
13924
13995
|
},
|
|
13925
|
-
why: "A human must click the card to commit it; its
|
|
13996
|
+
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."
|
|
13926
13997
|
},
|
|
13927
13998
|
text: formatAgentActionCardPosted(request.target, card.messageId)
|
|
13928
13999
|
};
|
|
13929
14000
|
}
|
|
13930
14001
|
//#endregion
|
|
14002
|
+
//#region src/latestReadThread.ts
|
|
14003
|
+
const MAX_READ_STATE_BYTES = 33554432;
|
|
14004
|
+
function firstNonEmpty(...values) {
|
|
14005
|
+
for (const value of values) {
|
|
14006
|
+
const trimmed = value?.trim();
|
|
14007
|
+
if (trimmed) return trimmed;
|
|
14008
|
+
}
|
|
14009
|
+
}
|
|
14010
|
+
/**
|
|
14011
|
+
* The thread this agent read most recently with `raft message read`, when
|
|
14012
|
+
* that read is still the latest read of any conversation.
|
|
14013
|
+
*
|
|
14014
|
+
* It is a fact about what was read, not about what the work belongs to: an
|
|
14015
|
+
* agent that read an unrelated thread afterwards gets that thread. Treat the
|
|
14016
|
+
* answer as a default to confirm, and keep private channel names out of
|
|
14017
|
+
* public places.
|
|
14018
|
+
*/
|
|
14019
|
+
async function readLatestReadThread(options = {}) {
|
|
14020
|
+
const env = options.env ?? process.env;
|
|
14021
|
+
const agentId = firstNonEmpty(options.agentId, env.SLOCK_AGENT_ID);
|
|
14022
|
+
if (!agentId || !isLocalStateAgentId(agentId)) return {
|
|
14023
|
+
state: "none",
|
|
14024
|
+
reason: "no_agent_id"
|
|
14025
|
+
};
|
|
14026
|
+
const base = firstNonEmpty(options.home, configuredCliReadStateBase(env)) ?? join(homedir(), ".slock");
|
|
14027
|
+
const filePath = join(base, ...cliReadStatePathSegments(agentId));
|
|
14028
|
+
let handle;
|
|
14029
|
+
try {
|
|
14030
|
+
handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
14031
|
+
} catch (error) {
|
|
14032
|
+
return {
|
|
14033
|
+
state: "none",
|
|
14034
|
+
reason: error.code === "ENOENT" ? "no_record" : "unreadable"
|
|
14035
|
+
};
|
|
14036
|
+
}
|
|
14037
|
+
let raw;
|
|
14038
|
+
try {
|
|
14039
|
+
const stat = await handle.stat();
|
|
14040
|
+
const ownedByUser = typeof process.getuid !== "function" || stat.uid === process.getuid() && (stat.mode & 63) === 0;
|
|
14041
|
+
if (!stat.isFile() || !ownedByUser || stat.size > MAX_READ_STATE_BYTES) return {
|
|
14042
|
+
state: "none",
|
|
14043
|
+
reason: "unreadable"
|
|
14044
|
+
};
|
|
14045
|
+
raw = JSON.parse(await handle.readFile("utf8"));
|
|
14046
|
+
} catch {
|
|
14047
|
+
return {
|
|
14048
|
+
state: "none",
|
|
14049
|
+
reason: "unreadable"
|
|
14050
|
+
};
|
|
14051
|
+
} finally {
|
|
14052
|
+
await handle.close();
|
|
14053
|
+
}
|
|
14054
|
+
return latestReadThreadFromCliReadState(raw);
|
|
14055
|
+
}
|
|
14056
|
+
//#endregion
|
|
13931
14057
|
//#region src/raft.ts
|
|
13932
14058
|
function createRaft(options) {
|
|
13933
14059
|
const serverUrl = requireServerUrl(options.serverUrl);
|
|
@@ -14108,4 +14234,4 @@ function createRaft(options) {
|
|
|
14108
14234
|
};
|
|
14109
14235
|
}
|
|
14110
14236
|
//#endregion
|
|
14111
|
-
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, joinRaftChannelByTarget, listRaftRoutes, parseRaftRegularChannelTarget, parseRaftState, projectRaftMessage, projectRaftMessages, verifyInboxNotice };
|
|
14237
|
+
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, joinRaftChannelByTarget, listRaftRoutes, parseRaftRegularChannelTarget, parseRaftState, projectRaftMessage, projectRaftMessages, readLatestReadThread, verifyInboxNotice };
|
package/dist/index.d.ts
CHANGED
|
@@ -1857,6 +1857,7 @@ declare const agentApiIntegrationAppUpdateBodySchema: z.ZodObject<{
|
|
|
1857
1857
|
clientKey: z.ZodString;
|
|
1858
1858
|
name: z.ZodOptional<z.ZodString>;
|
|
1859
1859
|
description: z.ZodOptional<z.ZodString>;
|
|
1860
|
+
whenToUse: z.ZodOptional<z.ZodString>;
|
|
1860
1861
|
category: z.ZodOptional<z.ZodString>;
|
|
1861
1862
|
homepageUrl: z.ZodOptional<z.ZodString>;
|
|
1862
1863
|
returnUrl: z.ZodOptional<z.ZodString>;
|
|
@@ -2120,6 +2121,7 @@ declare const agentApiIntegrationListResponseSchema: z.ZodObject<{
|
|
|
2120
2121
|
}>>;
|
|
2121
2122
|
name: z.ZodString;
|
|
2122
2123
|
description: z.ZodNullable<z.ZodString>;
|
|
2124
|
+
whenToUse: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2123
2125
|
homepageUrl: z.ZodNullable<z.ZodString>;
|
|
2124
2126
|
returnUrl: z.ZodNullable<z.ZodString>;
|
|
2125
2127
|
agentManifestUrl: z.ZodNullable<z.ZodString>;
|
|
@@ -2198,6 +2200,7 @@ declare const agentApiIntegrationLoginResponseSchema: z.ZodObject<{
|
|
|
2198
2200
|
}>>;
|
|
2199
2201
|
name: z.ZodString;
|
|
2200
2202
|
description: z.ZodNullable<z.ZodString>;
|
|
2203
|
+
whenToUse: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
2201
2204
|
homepageUrl: z.ZodNullable<z.ZodString>;
|
|
2202
2205
|
returnUrl: z.ZodNullable<z.ZodString>;
|
|
2203
2206
|
agentManifestUrl: z.ZodNullable<z.ZodString>;
|
|
@@ -7291,6 +7294,7 @@ declare const agentApiContract: {
|
|
|
7291
7294
|
}>>;
|
|
7292
7295
|
name: z.ZodString;
|
|
7293
7296
|
description: z.ZodNullable<z.ZodString>;
|
|
7297
|
+
whenToUse: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
7294
7298
|
homepageUrl: z.ZodNullable<z.ZodString>;
|
|
7295
7299
|
returnUrl: z.ZodNullable<z.ZodString>;
|
|
7296
7300
|
agentManifestUrl: z.ZodNullable<z.ZodString>;
|
|
@@ -7412,6 +7416,7 @@ declare const agentApiContract: {
|
|
|
7412
7416
|
}>>;
|
|
7413
7417
|
name: z.ZodString;
|
|
7414
7418
|
description: z.ZodNullable<z.ZodString>;
|
|
7419
|
+
whenToUse: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
7415
7420
|
homepageUrl: z.ZodNullable<z.ZodString>;
|
|
7416
7421
|
returnUrl: z.ZodNullable<z.ZodString>;
|
|
7417
7422
|
agentManifestUrl: z.ZodNullable<z.ZodString>;
|
|
@@ -7603,6 +7608,7 @@ declare const agentApiContract: {
|
|
|
7603
7608
|
clientKey: z.ZodString;
|
|
7604
7609
|
name: z.ZodOptional<z.ZodString>;
|
|
7605
7610
|
description: z.ZodOptional<z.ZodString>;
|
|
7611
|
+
whenToUse: z.ZodOptional<z.ZodString>;
|
|
7606
7612
|
category: z.ZodOptional<z.ZodString>;
|
|
7607
7613
|
homepageUrl: z.ZodOptional<z.ZodString>;
|
|
7608
7614
|
returnUrl: z.ZodOptional<z.ZodString>;
|
|
@@ -9305,6 +9311,46 @@ export declare function createRaftClientFromStore(options: CreateRaftClientFromS
|
|
|
9305
9311
|
*/
|
|
9306
9312
|
export declare function createFileCredentialStore(filePath: string): RaftCredentialStore;
|
|
9307
9313
|
//#endregion
|
|
9314
|
+
//#region src/latestReadThread.d.ts
|
|
9315
|
+
interface ReadLatestReadThreadOptions {
|
|
9316
|
+
/** The agent whose reads to look at. Defaults to `SLOCK_AGENT_ID` from `env`. */
|
|
9317
|
+
agentId?: string;
|
|
9318
|
+
/**
|
|
9319
|
+
* The Raft home directory the CLI uses. Defaults to what the CLI resolves
|
|
9320
|
+
* from `env`: `RAFT_HOME`, then `SLOCK_HOME`, then `~/.slock`.
|
|
9321
|
+
*/
|
|
9322
|
+
home?: string;
|
|
9323
|
+
/** Environment to resolve the defaults from. Defaults to `process.env`. */
|
|
9324
|
+
env?: Record<string, string | undefined>;
|
|
9325
|
+
}
|
|
9326
|
+
type RaftLatestReadThread = {
|
|
9327
|
+
state: "thread";
|
|
9328
|
+
/** The thread target, as `raft message read --target` takes it, for example `#general:1a2b3c4d`. */
|
|
9329
|
+
target: string;
|
|
9330
|
+
/** The channel or DM the thread belongs to, for example `#general`. */
|
|
9331
|
+
parentTarget: string;
|
|
9332
|
+
} | {
|
|
9333
|
+
state: "none";
|
|
9334
|
+
/**
|
|
9335
|
+
* - `no_agent_id`: no `agentId` was given and `SLOCK_AGENT_ID` is not set (or is not a valid id).
|
|
9336
|
+
* - `no_record`: the CLI has kept no read record for this agent here.
|
|
9337
|
+
* - `unreadable`: a record exists but is not a private regular file of this user, is too large, or is not JSON.
|
|
9338
|
+
* - `no_reads`: the record holds no read.
|
|
9339
|
+
* - `latest_read_is_not_a_thread`: the latest read was a channel or DM root. An older thread is never substituted.
|
|
9340
|
+
*/
|
|
9341
|
+
reason: "no_agent_id" | "no_record" | "unreadable" | "no_reads" | "latest_read_is_not_a_thread";
|
|
9342
|
+
};
|
|
9343
|
+
/**
|
|
9344
|
+
* The thread this agent read most recently with `raft message read`, when
|
|
9345
|
+
* that read is still the latest read of any conversation.
|
|
9346
|
+
*
|
|
9347
|
+
* It is a fact about what was read, not about what the work belongs to: an
|
|
9348
|
+
* agent that read an unrelated thread afterwards gets that thread. Treat the
|
|
9349
|
+
* answer as a default to confirm, and keep private channel names out of
|
|
9350
|
+
* public places.
|
|
9351
|
+
*/
|
|
9352
|
+
export declare function readLatestReadThread(options?: ReadLatestReadThreadOptions): Promise<RaftLatestReadThread>;
|
|
9353
|
+
//#endregion
|
|
9308
9354
|
//#region src/raft.d.ts
|
|
9309
9355
|
interface CreateRaftOptions {
|
|
9310
9356
|
/** Raft Server origin, for example `https://api.raft.build`. */
|
|
@@ -9534,4 +9580,4 @@ interface Raft {
|
|
|
9534
9580
|
}
|
|
9535
9581
|
export declare function createRaft(options: CreateRaftOptions): Raft;
|
|
9536
9582
|
//#endregion
|
|
9537
|
-
export type { AmendTaskOutcome, AmendTaskRequest, AssignTaskRequest, BootstrapRaftCredentialOptions, CheckInboxOutcome, CheckInboxRequest, ClaimTasksOutcome, ClaimTasksRequest, CreateRaftClientFromStoreOptions, CreateRaftClientOptions, CreateRaftOptions, CreateRaftRoutesOptions, CreateTasksRequest, DrainInboxRequest, ExecuteMentionActionRequest, ListInboxRequest, ListTasksRequest, ManualContext, PrepareActionCardRequest, Raft, RaftAckMode, RaftActionPrepareRequest, RaftActionPrepared, RaftApiError, RaftApiResult, RaftAppConfig, RaftAppConfigPatch, RaftAttachmentBytes, RaftAttachmentUploaded, RaftAvatarUpload, RaftChannelJoinClient, RaftChannelJoinClientResult, RaftChannelJoinError, RaftChannelJoinFailure, RaftChannelJoinOperation, RaftChannelJoinRequest, RaftChannelJoinResult, RaftChannelJoinSuccess, RaftChannelJoinTransportError, RaftChannelMuteState, RaftChannelRef, RaftClaimHeld, RaftClaimHeldLike, RaftClaimResult, RaftClaimRow, RaftClaimRowState, RaftClient, RaftClientError, RaftClientFailure, RaftClientResult, RaftClientSuccess, RaftClientThrottleOptions, RaftClientTransportRequest, RaftContextAgent, RaftContextData, RaftContextError, RaftContextResult, RaftContextServer, RaftCredentialErrorCode, RaftCredentialIdentity, RaftCredentialStore, RaftEvent, RaftEventAttachment, RaftEventExternalMessage, RaftEventsReceiveData, RaftEventsReceiveError, RaftEventsReceiveRequest, RaftEventsReceiveResult, RaftFailure, RaftHeld, RaftHeldBase, RaftHistoryPage, RaftInboxBatch, RaftInboxCommitResult, RaftInboxConversation, RaftInboxDrainSummary, RaftInboxListing, RaftInboxNotice, RaftManageClient, RaftMessage, RaftMessageAttachment, RaftMessageTask, RaftNextStep, RaftNoticeFlag, RaftNoticeTarget, RaftOpError, RaftOpErrorCode, RaftOutcome, RaftPendingMentions, RaftPreparedCard, RaftProfile, RaftProfileUpdate, RaftRouteAnnotations, RaftRouteInfo, RaftRouteKey, RaftRouteMeta, RaftRouteResult, RaftRouteRetryPolicy, RaftRoutes, RaftSdkConfigurationErrorCode, RaftSearchPage, RaftSendContinuation, RaftSenderType, RaftSent, RaftServerInfo, RaftServerProfile, RaftServerUpdate, RaftState, RaftStateContinuation, RaftStateSaveErrorHandler, RaftStateStore, RaftTaskBoard, RaftTaskStatus, RaftTasksCreated, RaftWebhookStatus, ReactRequest, ReadHistoryRequest, SearchMessagesRequest, SeenAttestation, SeenFrontierSnapshot, SendMessageOutcome, SendMessageRequest, ServerInfoRequest, ServerInfoSection, StoredRaftCredential, TaskRef, UpdateTaskStatusOutcome, UpdateTaskStatusRequest, UploadAttachmentRequest, VerifyNoticeInput, VerifyNoticeRejection, VerifyNoticeResult };
|
|
9583
|
+
export type { AmendTaskOutcome, AmendTaskRequest, AssignTaskRequest, BootstrapRaftCredentialOptions, CheckInboxOutcome, CheckInboxRequest, ClaimTasksOutcome, ClaimTasksRequest, CreateRaftClientFromStoreOptions, CreateRaftClientOptions, CreateRaftOptions, CreateRaftRoutesOptions, CreateTasksRequest, DrainInboxRequest, ExecuteMentionActionRequest, ListInboxRequest, ListTasksRequest, ManualContext, PrepareActionCardRequest, Raft, RaftAckMode, RaftActionPrepareRequest, RaftActionPrepared, RaftApiError, RaftApiResult, RaftAppConfig, RaftAppConfigPatch, RaftAttachmentBytes, RaftAttachmentUploaded, RaftAvatarUpload, RaftChannelJoinClient, RaftChannelJoinClientResult, RaftChannelJoinError, RaftChannelJoinFailure, RaftChannelJoinOperation, RaftChannelJoinRequest, RaftChannelJoinResult, RaftChannelJoinSuccess, RaftChannelJoinTransportError, RaftChannelMuteState, RaftChannelRef, RaftClaimHeld, RaftClaimHeldLike, RaftClaimResult, RaftClaimRow, RaftClaimRowState, RaftClient, RaftClientError, RaftClientFailure, RaftClientResult, RaftClientSuccess, RaftClientThrottleOptions, RaftClientTransportRequest, RaftContextAgent, RaftContextData, RaftContextError, RaftContextResult, RaftContextServer, RaftCredentialErrorCode, RaftCredentialIdentity, RaftCredentialStore, RaftEvent, RaftEventAttachment, RaftEventExternalMessage, RaftEventsReceiveData, RaftEventsReceiveError, RaftEventsReceiveRequest, RaftEventsReceiveResult, RaftFailure, RaftHeld, RaftHeldBase, RaftHistoryPage, RaftInboxBatch, RaftInboxCommitResult, RaftInboxConversation, RaftInboxDrainSummary, RaftInboxListing, RaftInboxNotice, RaftLatestReadThread, RaftManageClient, RaftMessage, RaftMessageAttachment, RaftMessageTask, RaftNextStep, RaftNoticeFlag, RaftNoticeTarget, RaftOpError, RaftOpErrorCode, RaftOutcome, RaftPendingMentions, RaftPreparedCard, RaftProfile, RaftProfileUpdate, RaftRouteAnnotations, RaftRouteInfo, RaftRouteKey, RaftRouteMeta, RaftRouteResult, RaftRouteRetryPolicy, RaftRoutes, RaftSdkConfigurationErrorCode, RaftSearchPage, RaftSendContinuation, RaftSenderType, RaftSent, RaftServerInfo, RaftServerProfile, RaftServerUpdate, RaftState, RaftStateContinuation, RaftStateSaveErrorHandler, RaftStateStore, RaftTaskBoard, RaftTaskStatus, RaftTasksCreated, RaftWebhookStatus, ReactRequest, ReadHistoryRequest, ReadLatestReadThreadOptions, SearchMessagesRequest, SeenAttestation, SeenFrontierSnapshot, SendMessageOutcome, SendMessageRequest, ServerInfoRequest, ServerInfoSection, StoredRaftCredential, TaskRef, UpdateTaskStatusOutcome, UpdateTaskStatusRequest, UploadAttachmentRequest, VerifyNoticeInput, VerifyNoticeRejection, VerifyNoticeResult };
|