@cardor/agent-harness-kit 2.3.0 → 2.4.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/dist/agent-templates/builder.md +4 -4
- package/dist/agent-templates/consultant.md +6 -3
- package/dist/agent-templates/explorer.md +3 -1
- package/dist/agent-templates/lead.md +4 -4
- package/dist/agent-templates/reviewer.md +5 -3
- package/dist/{chunk-URMVLD2S.js → chunk-LURM3C4M.js} +2 -2
- package/dist/{chunk-JTACLEGM.js → chunk-TWG3KVZQ.js} +73 -4
- package/dist/chunk-TWG3KVZQ.js.map +1 -0
- package/dist/cli.js +225 -10
- package/dist/cli.js.map +1 -1
- package/dist/{db-L3AADJF5.js → db-3B2W2EBA.js} +2 -2
- package/dist/{mysql-AUPKARWA.js → mysql-ZU3T7DWV.js} +3 -3
- package/dist/{postgres-BB4GY4PN.js → postgres-H5TLXDTW.js} +4 -4
- package/dist/postgres-H5TLXDTW.js.map +1 -0
- package/dist/{sqlite-5OWKTUUZ.js → sqlite-UDXCFIEN.js} +4 -4
- package/dist/sqlite-UDXCFIEN.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-JTACLEGM.js.map +0 -1
- package/dist/postgres-BB4GY4PN.js.map +0 -1
- package/dist/sqlite-5OWKTUUZ.js.map +0 -1
- /package/dist/{chunk-URMVLD2S.js.map → chunk-LURM3C4M.js.map} +0 -0
- /package/dist/{db-L3AADJF5.js.map → db-3B2W2EBA.js.map} +0 -0
- /package/dist/{mysql-AUPKARWA.js.map → mysql-ZU3T7DWV.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
resolveGlobalStorageDir,
|
|
13
13
|
resolveSqlitePath,
|
|
14
14
|
resolveSqlitePathForScope
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-TWG3KVZQ.js";
|
|
16
16
|
|
|
17
17
|
// src/cli.ts
|
|
18
18
|
import { Command, InvalidArgumentError } from "commander";
|
|
@@ -398,6 +398,7 @@ function mergeCodexConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
|
398
398
|
].join("\n");
|
|
399
399
|
content = ensureTomlTopLevelKey(content, "model", "gpt-5.6-terra");
|
|
400
400
|
content = ensureTomlTopLevelKey(content, "model_reasoning_effort", "medium");
|
|
401
|
+
content = ensureTomlTopLevelKey(content, "sandbox_mode", "danger-full-access");
|
|
401
402
|
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
402
403
|
writeFileSync2(filePath, content, "utf8");
|
|
403
404
|
}
|
|
@@ -408,7 +409,10 @@ function mergeGrokConfigToml(filePath, port, cwd2, pm = "npm") {
|
|
|
408
409
|
content = readFileSync3(filePath, "utf8");
|
|
409
410
|
}
|
|
410
411
|
const [command, ...args] = getMcpCommandParts(pm, port, cwd2);
|
|
411
|
-
const sectionBody = [
|
|
412
|
+
const sectionBody = [
|
|
413
|
+
`command = ${JSON.stringify(command)}`,
|
|
414
|
+
`args = ${JSON.stringify(args)}`
|
|
415
|
+
].join("\n");
|
|
412
416
|
content = mergeTomlSection(content, "mcp_servers.agent-harness-kit", sectionBody);
|
|
413
417
|
writeFileSync2(filePath, content, "utf8");
|
|
414
418
|
}
|
|
@@ -2731,7 +2735,7 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2731
2735
|
}
|
|
2732
2736
|
async function probeTaskCount(dbPath) {
|
|
2733
2737
|
if (!existsSync15(dbPath)) return 0;
|
|
2734
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2738
|
+
const { SQLiteDriver } = await import("./sqlite-UDXCFIEN.js");
|
|
2735
2739
|
const driver = new SQLiteDriver(dbPath);
|
|
2736
2740
|
try {
|
|
2737
2741
|
await driver.ensureSchema();
|
|
@@ -2752,7 +2756,7 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2752
2756
|
}
|
|
2753
2757
|
const destExists = existsSync15(destDb);
|
|
2754
2758
|
if (destExists) {
|
|
2755
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2759
|
+
const { SQLiteDriver } = await import("./sqlite-UDXCFIEN.js");
|
|
2756
2760
|
const destDriver = new SQLiteDriver(destDb);
|
|
2757
2761
|
let destEmpty;
|
|
2758
2762
|
try {
|
|
@@ -2767,12 +2771,12 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2767
2771
|
);
|
|
2768
2772
|
}
|
|
2769
2773
|
if (!destEmpty && opts.force) {
|
|
2770
|
-
const { SQLiteDriver: Driver } = await import("./sqlite-
|
|
2774
|
+
const { SQLiteDriver: Driver } = await import("./sqlite-UDXCFIEN.js");
|
|
2771
2775
|
const backupDriver = new Driver(destDb);
|
|
2772
2776
|
let data;
|
|
2773
2777
|
try {
|
|
2774
2778
|
await backupDriver.ensureSchema();
|
|
2775
|
-
const { HarnessDB } = await import("./db-
|
|
2779
|
+
const { HarnessDB } = await import("./db-3B2W2EBA.js");
|
|
2776
2780
|
const tmpDb = new HarnessDB(backupDriver, config, homeDir);
|
|
2777
2781
|
data = await tmpDb.exportJson();
|
|
2778
2782
|
} finally {
|
|
@@ -2811,14 +2815,14 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2811
2815
|
if (!existsSync15(srcPath)) {
|
|
2812
2816
|
fail(`Source sqlite database not found at ${srcPath} (expected ${sourceScope} scope) \u2014 nothing to migrate.`);
|
|
2813
2817
|
}
|
|
2814
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2818
|
+
const { SQLiteDriver } = await import("./sqlite-UDXCFIEN.js");
|
|
2815
2819
|
const srcDriver = new SQLiteDriver(srcPath);
|
|
2816
2820
|
let sourceData;
|
|
2817
2821
|
let sourceCounts;
|
|
2818
2822
|
try {
|
|
2819
2823
|
await srcDriver.ensureSchema();
|
|
2820
2824
|
sourceCounts = await getRowCounts(srcDriver);
|
|
2821
|
-
const { HarnessDB } = await import("./db-
|
|
2825
|
+
const { HarnessDB } = await import("./db-3B2W2EBA.js");
|
|
2822
2826
|
const srcDb = new HarnessDB(srcDriver, config, homeDir);
|
|
2823
2827
|
sourceData = await srcDb.exportJson();
|
|
2824
2828
|
} finally {
|
|
@@ -3113,7 +3117,7 @@ var TOOLS = [
|
|
|
3113
3117
|
taskId: { type: "number", description: "The task ID from tasks.get" },
|
|
3114
3118
|
agent: {
|
|
3115
3119
|
type: "string",
|
|
3116
|
-
description: "Agent name: lead | explorer | builder | reviewer | custom:<name>"
|
|
3120
|
+
description: "Agent name: lead | explorer | consultant | builder | reviewer | custom:<name>"
|
|
3117
3121
|
}
|
|
3118
3122
|
},
|
|
3119
3123
|
required: ["taskId", "agent"]
|
|
@@ -3152,7 +3156,7 @@ var TOOLS = [
|
|
|
3152
3156
|
},
|
|
3153
3157
|
{
|
|
3154
3158
|
name: "actions.get",
|
|
3155
|
-
description: "
|
|
3159
|
+
description: "Full task action history, including every action and section. Potentially large; use only for audit or diagnosis.",
|
|
3156
3160
|
inputSchema: {
|
|
3157
3161
|
type: "object",
|
|
3158
3162
|
properties: {
|
|
@@ -3161,6 +3165,84 @@ var TOOLS = [
|
|
|
3161
3165
|
required: ["taskId"]
|
|
3162
3166
|
}
|
|
3163
3167
|
},
|
|
3168
|
+
{
|
|
3169
|
+
name: "actions.list",
|
|
3170
|
+
description: "Compact newest-first action index. Use to discover actions before reading a specific action or section.",
|
|
3171
|
+
inputSchema: {
|
|
3172
|
+
type: "object",
|
|
3173
|
+
properties: {
|
|
3174
|
+
taskId: { type: "number", description: "Task ID" },
|
|
3175
|
+
agent: { type: "string", description: "Optional agent filter" },
|
|
3176
|
+
status: { type: "string", enum: ["in_progress", "completed", "blocked"], description: "Optional action status filter" },
|
|
3177
|
+
cursor: { type: "string", description: "Opaque cursor returned as nextCursor" },
|
|
3178
|
+
limit: { type: "number", description: "Maximum items (1-100; default 20)" }
|
|
3179
|
+
},
|
|
3180
|
+
required: ["taskId"]
|
|
3181
|
+
}
|
|
3182
|
+
},
|
|
3183
|
+
{
|
|
3184
|
+
name: "actions.get_by_id",
|
|
3185
|
+
description: "Get one action and a compact index of its sections; section contents are not included.",
|
|
3186
|
+
inputSchema: { type: "object", properties: { actionId: { type: "number", description: "Action ID" } }, required: ["actionId"] }
|
|
3187
|
+
},
|
|
3188
|
+
{
|
|
3189
|
+
name: "actions.sections.list",
|
|
3190
|
+
description: "Compact newest-first section index for one action. Filter by section types without loading content.",
|
|
3191
|
+
inputSchema: {
|
|
3192
|
+
type: "object",
|
|
3193
|
+
properties: {
|
|
3194
|
+
actionId: { type: "number", description: "Action ID" },
|
|
3195
|
+
types: { type: "array", items: { type: "string" }, description: "Optional section-type filter" },
|
|
3196
|
+
cursor: { type: "string", description: "Opaque cursor returned as nextCursor" },
|
|
3197
|
+
limit: { type: "number", description: "Maximum items (1-100; default 20)" }
|
|
3198
|
+
},
|
|
3199
|
+
required: ["actionId"]
|
|
3200
|
+
}
|
|
3201
|
+
},
|
|
3202
|
+
{
|
|
3203
|
+
name: "actions.sections.get",
|
|
3204
|
+
description: "Read one section content with an explicit character range. This is the only generic action reader that returns long text.",
|
|
3205
|
+
inputSchema: {
|
|
3206
|
+
type: "object",
|
|
3207
|
+
properties: {
|
|
3208
|
+
sectionId: { type: "number", description: "Section ID" },
|
|
3209
|
+
offset: { type: "number", description: "Zero-based character offset (default 0)" },
|
|
3210
|
+
length: { type: "number", description: "Characters to return (1-12000; default 8000)" }
|
|
3211
|
+
},
|
|
3212
|
+
required: ["sectionId"]
|
|
3213
|
+
}
|
|
3214
|
+
},
|
|
3215
|
+
{
|
|
3216
|
+
name: "actions.handoff.write",
|
|
3217
|
+
description: "Write a validated, recipient-directed, bounded handoff for a completed action to resume work without loading the full history.",
|
|
3218
|
+
inputSchema: {
|
|
3219
|
+
type: "object",
|
|
3220
|
+
properties: {
|
|
3221
|
+
actionId: { type: "number", description: "Producer action ID" },
|
|
3222
|
+
recipient: { type: "string", enum: ["lead", "explorer", "consultant", "builder", "reviewer"] },
|
|
3223
|
+
goal: { type: "string" },
|
|
3224
|
+
completed: { type: "array", items: { type: "string" } },
|
|
3225
|
+
decisions: { type: "array", items: { type: "string" } },
|
|
3226
|
+
files: { type: "array", items: { type: "string" } },
|
|
3227
|
+
verification: { type: "array", items: { type: "string" } },
|
|
3228
|
+
blockers: { type: "array", items: { type: "string" } },
|
|
3229
|
+
nextStep: { type: "string" }
|
|
3230
|
+
},
|
|
3231
|
+
required: ["actionId", "recipient", "goal", "completed", "decisions", "files", "verification", "blockers", "nextStep"]
|
|
3232
|
+
}
|
|
3233
|
+
},
|
|
3234
|
+
{
|
|
3235
|
+
name: "actions.handoff.get",
|
|
3236
|
+
description: "Get the newest completed canonical handoff addressed to a recipient. Returns HANDOFF_NOT_FOUND instead of falling back to history.",
|
|
3237
|
+
inputSchema: {
|
|
3238
|
+
type: "object",
|
|
3239
|
+
properties: {
|
|
3240
|
+
taskId: { type: "number", description: "Task ID" },
|
|
3241
|
+
recipient: { type: "string", enum: ["lead", "explorer", "consultant", "builder", "reviewer"], description: "Recipient role; defaults to builder" }
|
|
3242
|
+
},
|
|
3243
|
+
required: ["taskId"]
|
|
3244
|
+
}
|
|
3245
|
+
},
|
|
3164
3246
|
{
|
|
3165
3247
|
name: "tasks.get",
|
|
3166
3248
|
description: "List tasks, optionally filtered by status. Excludes archived tasks by default.",
|
|
@@ -3422,6 +3504,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3422
3504
|
case "actions.write": {
|
|
3423
3505
|
const actionId = num(args, "actionId");
|
|
3424
3506
|
const sectionType = str(args, "sectionType");
|
|
3507
|
+
if (sectionType === "handoff") throw new Error("handoff sections must be written with actions.handoff.write");
|
|
3425
3508
|
const content = str(args, "content");
|
|
3426
3509
|
await db.writeSection(actionId, sectionType, content);
|
|
3427
3510
|
return ok2(JSON.stringify({ recorded: true }));
|
|
@@ -3456,6 +3539,56 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3456
3539
|
);
|
|
3457
3540
|
return ok2(JSON.stringify(full));
|
|
3458
3541
|
}
|
|
3542
|
+
case "actions.list": {
|
|
3543
|
+
const taskId = num(args, "taskId");
|
|
3544
|
+
const limit = boundedInt(args, "limit", 20, 1, 100);
|
|
3545
|
+
const rows = await db.listActionsForTask(taskId, { agent: optionalAgent(args, "agent"), status: optionalActionStatus(args, "status"), cursor: decodeActionCursor(optionalStr(args, "cursor")), limit });
|
|
3546
|
+
const last = rows.at(-1);
|
|
3547
|
+
return ok2(JSON.stringify({ items: rows.map((row) => ({ id: row.id, agent: row.agent, status: row.status, createdAt: row.created_at, completedAt: row.completed_at, summaryPreview: preview(row.summary), sectionCount: Number(row.section_count) })), nextCursor: rows.length === limit && last ? encodeActionCursor(last.created_at, last.id) : null }));
|
|
3548
|
+
}
|
|
3549
|
+
case "actions.get_by_id": {
|
|
3550
|
+
const actionId = num(args, "actionId");
|
|
3551
|
+
const action = await db.getAction(actionId);
|
|
3552
|
+
if (!action) return ok2(JSON.stringify({ error: "ACTION_NOT_FOUND", actionId }), true);
|
|
3553
|
+
const sections = await db.listActionSections(actionId, { limit: 100 });
|
|
3554
|
+
return ok2(JSON.stringify({ id: action.id, taskId: action.task_id, agent: action.agent, status: action.status, summary: action.summary, createdAt: action.created_at, completedAt: action.completed_at, sections: sections.map(sectionIndex) }));
|
|
3555
|
+
}
|
|
3556
|
+
case "actions.sections.list": {
|
|
3557
|
+
const actionId = num(args, "actionId");
|
|
3558
|
+
const limit = boundedInt(args, "limit", 20, 1, 100);
|
|
3559
|
+
const rows = await db.listActionSections(actionId, { types: optionalStringArray(args, "types"), cursor: decodeSectionCursor(optionalStr(args, "cursor")), limit });
|
|
3560
|
+
const last = rows.at(-1);
|
|
3561
|
+
return ok2(JSON.stringify({ items: rows.map(sectionIndex), nextCursor: rows.length === limit && last ? encodeSectionCursor(last.id) : null }));
|
|
3562
|
+
}
|
|
3563
|
+
case "actions.sections.get": {
|
|
3564
|
+
const sectionId = num(args, "sectionId");
|
|
3565
|
+
const offset = boundedInt(args, "offset", 0, 0, Number.MAX_SAFE_INTEGER);
|
|
3566
|
+
const length = boundedInt(args, "length", 8e3, 1, 12e3);
|
|
3567
|
+
const section = await db.getActionSection(sectionId);
|
|
3568
|
+
if (!section) return ok2(JSON.stringify({ error: "SECTION_NOT_FOUND", sectionId }), true);
|
|
3569
|
+
const content = section.content.slice(offset, offset + length);
|
|
3570
|
+
const nextOffset = offset + content.length;
|
|
3571
|
+
return ok2(JSON.stringify({ sectionId: section.id, type: section.section_type, content, truncated: nextOffset < section.content.length, nextOffset: nextOffset < section.content.length ? nextOffset : null }));
|
|
3572
|
+
}
|
|
3573
|
+
case "actions.handoff.write": {
|
|
3574
|
+
const actionId = num(args, "actionId");
|
|
3575
|
+
const recipient = recipientRole(args["recipient"]);
|
|
3576
|
+
const handoff = handoffFromArgs(args);
|
|
3577
|
+
const serialized = JSON.stringify({ version: 1, recipient, handoff });
|
|
3578
|
+
if (Buffer.byteLength(serialized, "utf8") > 12e3) throw new Error("handoff must not exceed 12000 UTF-8 bytes");
|
|
3579
|
+
await db.writeSection(actionId, "handoff", serialized);
|
|
3580
|
+
return ok2(JSON.stringify({ recorded: true, recipient }));
|
|
3581
|
+
}
|
|
3582
|
+
case "actions.handoff.get": {
|
|
3583
|
+
const taskId = num(args, "taskId");
|
|
3584
|
+
const recipient = recipientRole(args["recipient"] ?? "builder");
|
|
3585
|
+
for (const candidate of await db.getCompletedHandoffSections(taskId)) {
|
|
3586
|
+
const decoded = decodeHandoff(candidate.content);
|
|
3587
|
+
if (decoded?.recipient !== recipient) continue;
|
|
3588
|
+
return ok2(JSON.stringify({ found: true, taskId, sourceActionId: candidate.action_id, sourceAgent: candidate.agent, recipient, createdAt: candidate.created_at, handoff: decoded.handoff }));
|
|
3589
|
+
}
|
|
3590
|
+
return ok2(JSON.stringify({ found: false, reason: "HANDOFF_NOT_FOUND" }));
|
|
3591
|
+
}
|
|
3459
3592
|
case "tasks.get": {
|
|
3460
3593
|
const status = args["status"];
|
|
3461
3594
|
const includeArchived = args["includeArchived"];
|
|
@@ -3708,6 +3841,88 @@ function num(args, key) {
|
|
|
3708
3841
|
if (typeof v4 !== "number") throw new Error(`${key} must be a number`);
|
|
3709
3842
|
return v4;
|
|
3710
3843
|
}
|
|
3844
|
+
function optionalStr(args, key) {
|
|
3845
|
+
const value = args[key];
|
|
3846
|
+
if (value === void 0) return void 0;
|
|
3847
|
+
if (typeof value !== "string") throw new Error(`${key} must be a string`);
|
|
3848
|
+
return value;
|
|
3849
|
+
}
|
|
3850
|
+
function boundedInt(args, key, fallback, min, max) {
|
|
3851
|
+
const value = args[key];
|
|
3852
|
+
if (value === void 0) return fallback;
|
|
3853
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) throw new Error(`${key} must be an integer between ${min} and ${max}`);
|
|
3854
|
+
return value;
|
|
3855
|
+
}
|
|
3856
|
+
function optionalStringArray(args, key) {
|
|
3857
|
+
const value = args[key];
|
|
3858
|
+
if (value === void 0) return void 0;
|
|
3859
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.length === 0)) throw new Error(`${key} must be an array of non-empty strings`);
|
|
3860
|
+
return value;
|
|
3861
|
+
}
|
|
3862
|
+
var RECIPIENTS = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
3863
|
+
function recipientRole(value) {
|
|
3864
|
+
if (typeof value !== "string" || !RECIPIENTS.includes(value)) throw new Error(`recipient must be one of: ${RECIPIENTS.join(", ")}`);
|
|
3865
|
+
return value;
|
|
3866
|
+
}
|
|
3867
|
+
function requiredStringArray(args, key) {
|
|
3868
|
+
if (!(key in args)) throw new Error(`${key} is required`);
|
|
3869
|
+
return optionalStringArray(args, key) ?? [];
|
|
3870
|
+
}
|
|
3871
|
+
function handoffFromArgs(args) {
|
|
3872
|
+
const goal = str(args, "goal");
|
|
3873
|
+
const nextStep = str(args, "nextStep");
|
|
3874
|
+
if (!goal.trim() || !nextStep.trim()) throw new Error("goal and nextStep must be non-empty strings");
|
|
3875
|
+
return { goal, completed: requiredStringArray(args, "completed"), decisions: requiredStringArray(args, "decisions"), files: requiredStringArray(args, "files"), verification: requiredStringArray(args, "verification"), blockers: requiredStringArray(args, "blockers"), nextStep };
|
|
3876
|
+
}
|
|
3877
|
+
function decodeHandoff(content) {
|
|
3878
|
+
try {
|
|
3879
|
+
const value = JSON.parse(content);
|
|
3880
|
+
if (value.version !== 1 || !value.handoff) return null;
|
|
3881
|
+
return { recipient: recipientRole(value.recipient), handoff: handoffFromArgs(value.handoff) };
|
|
3882
|
+
} catch {
|
|
3883
|
+
return null;
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
function optionalAgent(args, key) {
|
|
3887
|
+
const value = optionalStr(args, key);
|
|
3888
|
+
if (value === void 0) return void 0;
|
|
3889
|
+
if (!["lead", "explorer", "consultant", "builder", "reviewer"].includes(value) && !value.startsWith("custom:")) throw new Error("agent must be a known role or custom:<name>");
|
|
3890
|
+
return value;
|
|
3891
|
+
}
|
|
3892
|
+
function optionalActionStatus(args, key) {
|
|
3893
|
+
const value = optionalStr(args, key);
|
|
3894
|
+
if (value === void 0) return void 0;
|
|
3895
|
+
if (!["in_progress", "completed", "blocked"].includes(value)) throw new Error("status must be in_progress, completed, or blocked");
|
|
3896
|
+
return value;
|
|
3897
|
+
}
|
|
3898
|
+
function encodeActionCursor(createdAt, id) {
|
|
3899
|
+
return Buffer.from(JSON.stringify({ createdAt, id })).toString("base64url");
|
|
3900
|
+
}
|
|
3901
|
+
function decodeActionCursor(cursor) {
|
|
3902
|
+
if (!cursor) return void 0;
|
|
3903
|
+
try {
|
|
3904
|
+
const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
3905
|
+
if (typeof value.createdAt !== "string" || typeof value.id !== "number" || !Number.isInteger(value.id)) throw new Error();
|
|
3906
|
+
return { createdAt: value.createdAt, id: value.id };
|
|
3907
|
+
} catch {
|
|
3908
|
+
throw new Error("cursor is invalid");
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
function encodeSectionCursor(id) {
|
|
3912
|
+
return Buffer.from(String(id)).toString("base64url");
|
|
3913
|
+
}
|
|
3914
|
+
function decodeSectionCursor(cursor) {
|
|
3915
|
+
if (!cursor) return void 0;
|
|
3916
|
+
const id = Number(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
3917
|
+
if (!Number.isInteger(id) || id < 1) throw new Error("cursor is invalid");
|
|
3918
|
+
return id;
|
|
3919
|
+
}
|
|
3920
|
+
function preview(summary) {
|
|
3921
|
+
return summary ? summary.slice(0, 240) : null;
|
|
3922
|
+
}
|
|
3923
|
+
function sectionIndex(section) {
|
|
3924
|
+
return { id: section.id, type: section.section_type, chars: Number(section.chars), createdAt: section.created_at };
|
|
3925
|
+
}
|
|
3711
3926
|
function nonEmptyArray(args, key) {
|
|
3712
3927
|
const v4 = args[key];
|
|
3713
3928
|
if (!Array.isArray(v4) || v4.length === 0) {
|