@cardor/agent-harness-kit 2.3.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- 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 +223 -12
- 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/skills/ahk-test/SKILL.md +61 -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";
|
|
@@ -1057,7 +1057,7 @@ function slugify(title) {
|
|
|
1057
1057
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
1058
1058
|
}
|
|
1059
1059
|
function writeSkills(cwd2, skillsDir) {
|
|
1060
|
-
const skillNames = ["ahk-ask", "ahk-consultant", "ahk-triage", "ahk-review"];
|
|
1060
|
+
const skillNames = ["ahk-ask", "ahk-consultant", "ahk-triage", "ahk-review", "ahk-test"];
|
|
1061
1061
|
for (const skillName of skillNames) {
|
|
1062
1062
|
const src = join6(__dirname2, "skills", skillName, "SKILL.md");
|
|
1063
1063
|
const destDir = join6(cwd2, skillsDir, skillName);
|
|
@@ -1833,7 +1833,7 @@ var REGISTRY_URL = `https://registry.npmjs.org/${pkg.name}/latest`;
|
|
|
1833
1833
|
var TIMEOUT_MS = 2e3;
|
|
1834
1834
|
var LIB_VERSION_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1835
1835
|
var AGENT_NAMES = ["lead", "explorer", "consultant", "builder", "reviewer"];
|
|
1836
|
-
var SKILL_NAMES = ["ahk-ask", "ahk-consultant", "ahk-triage", "ahk-review"];
|
|
1836
|
+
var SKILL_NAMES = ["ahk-ask", "ahk-consultant", "ahk-triage", "ahk-review", "ahk-test"];
|
|
1837
1837
|
var __dirname4 = dirname6(fileURLToPath5(import.meta.url));
|
|
1838
1838
|
var libVersionCache = null;
|
|
1839
1839
|
async function checkLibVersion() {
|
|
@@ -1907,7 +1907,7 @@ function getProviderSkillsDir(provider) {
|
|
|
1907
1907
|
}
|
|
1908
1908
|
}
|
|
1909
1909
|
function checkSkillsAtRoot(skillsRoot) {
|
|
1910
|
-
const skillSourceBase = join13(__dirname4, "skills");
|
|
1910
|
+
const skillSourceBase = join13(__dirname4, "materializer", "skills");
|
|
1911
1911
|
return SKILL_NAMES.map((name) => {
|
|
1912
1912
|
const livePath = join13(skillsRoot, name, "SKILL.md");
|
|
1913
1913
|
const sourcePath = join13(skillSourceBase, name, "SKILL.md");
|
|
@@ -2735,7 +2735,7 @@ async function runMigrateStorage(cwd2, opts, homeDir = homedir3()) {
|
|
|
2735
2735
|
}
|
|
2736
2736
|
async function probeTaskCount(dbPath) {
|
|
2737
2737
|
if (!existsSync15(dbPath)) return 0;
|
|
2738
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2738
|
+
const { SQLiteDriver } = await import("./sqlite-UDXCFIEN.js");
|
|
2739
2739
|
const driver = new SQLiteDriver(dbPath);
|
|
2740
2740
|
try {
|
|
2741
2741
|
await driver.ensureSchema();
|
|
@@ -2756,7 +2756,7 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2756
2756
|
}
|
|
2757
2757
|
const destExists = existsSync15(destDb);
|
|
2758
2758
|
if (destExists) {
|
|
2759
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2759
|
+
const { SQLiteDriver } = await import("./sqlite-UDXCFIEN.js");
|
|
2760
2760
|
const destDriver = new SQLiteDriver(destDb);
|
|
2761
2761
|
let destEmpty;
|
|
2762
2762
|
try {
|
|
@@ -2771,12 +2771,12 @@ async function migrateScopeOnly(cwd2, config, homeDir, fromScope, toScope, opts)
|
|
|
2771
2771
|
);
|
|
2772
2772
|
}
|
|
2773
2773
|
if (!destEmpty && opts.force) {
|
|
2774
|
-
const { SQLiteDriver: Driver } = await import("./sqlite-
|
|
2774
|
+
const { SQLiteDriver: Driver } = await import("./sqlite-UDXCFIEN.js");
|
|
2775
2775
|
const backupDriver = new Driver(destDb);
|
|
2776
2776
|
let data;
|
|
2777
2777
|
try {
|
|
2778
2778
|
await backupDriver.ensureSchema();
|
|
2779
|
-
const { HarnessDB } = await import("./db-
|
|
2779
|
+
const { HarnessDB } = await import("./db-3B2W2EBA.js");
|
|
2780
2780
|
const tmpDb = new HarnessDB(backupDriver, config, homeDir);
|
|
2781
2781
|
data = await tmpDb.exportJson();
|
|
2782
2782
|
} finally {
|
|
@@ -2815,14 +2815,14 @@ async function migrateAcrossDbType(cwd2, config, homeDir, sourceScope, opts) {
|
|
|
2815
2815
|
if (!existsSync15(srcPath)) {
|
|
2816
2816
|
fail(`Source sqlite database not found at ${srcPath} (expected ${sourceScope} scope) \u2014 nothing to migrate.`);
|
|
2817
2817
|
}
|
|
2818
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
2818
|
+
const { SQLiteDriver } = await import("./sqlite-UDXCFIEN.js");
|
|
2819
2819
|
const srcDriver = new SQLiteDriver(srcPath);
|
|
2820
2820
|
let sourceData;
|
|
2821
2821
|
let sourceCounts;
|
|
2822
2822
|
try {
|
|
2823
2823
|
await srcDriver.ensureSchema();
|
|
2824
2824
|
sourceCounts = await getRowCounts(srcDriver);
|
|
2825
|
-
const { HarnessDB } = await import("./db-
|
|
2825
|
+
const { HarnessDB } = await import("./db-3B2W2EBA.js");
|
|
2826
2826
|
const srcDb = new HarnessDB(srcDriver, config, homeDir);
|
|
2827
2827
|
sourceData = await srcDb.exportJson();
|
|
2828
2828
|
} finally {
|
|
@@ -3117,7 +3117,7 @@ var TOOLS = [
|
|
|
3117
3117
|
taskId: { type: "number", description: "The task ID from tasks.get" },
|
|
3118
3118
|
agent: {
|
|
3119
3119
|
type: "string",
|
|
3120
|
-
description: "Agent name: lead | explorer | builder | reviewer | custom:<name>"
|
|
3120
|
+
description: "Agent name: lead | explorer | consultant | builder | reviewer | custom:<name>"
|
|
3121
3121
|
}
|
|
3122
3122
|
},
|
|
3123
3123
|
required: ["taskId", "agent"]
|
|
@@ -3156,7 +3156,7 @@ var TOOLS = [
|
|
|
3156
3156
|
},
|
|
3157
3157
|
{
|
|
3158
3158
|
name: "actions.get",
|
|
3159
|
-
description: "
|
|
3159
|
+
description: "Full task action history, including every action and section. Potentially large; use only for audit or diagnosis.",
|
|
3160
3160
|
inputSchema: {
|
|
3161
3161
|
type: "object",
|
|
3162
3162
|
properties: {
|
|
@@ -3165,6 +3165,84 @@ var TOOLS = [
|
|
|
3165
3165
|
required: ["taskId"]
|
|
3166
3166
|
}
|
|
3167
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
|
+
},
|
|
3168
3246
|
{
|
|
3169
3247
|
name: "tasks.get",
|
|
3170
3248
|
description: "List tasks, optionally filtered by status. Excludes archived tasks by default.",
|
|
@@ -3426,6 +3504,7 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3426
3504
|
case "actions.write": {
|
|
3427
3505
|
const actionId = num(args, "actionId");
|
|
3428
3506
|
const sectionType = str(args, "sectionType");
|
|
3507
|
+
if (sectionType === "handoff") throw new Error("handoff sections must be written with actions.handoff.write");
|
|
3429
3508
|
const content = str(args, "content");
|
|
3430
3509
|
await db.writeSection(actionId, sectionType, content);
|
|
3431
3510
|
return ok2(JSON.stringify({ recorded: true }));
|
|
@@ -3460,6 +3539,56 @@ async function dispatch(name, args, db, docsPath, cwd2, config) {
|
|
|
3460
3539
|
);
|
|
3461
3540
|
return ok2(JSON.stringify(full));
|
|
3462
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
|
+
}
|
|
3463
3592
|
case "tasks.get": {
|
|
3464
3593
|
const status = args["status"];
|
|
3465
3594
|
const includeArchived = args["includeArchived"];
|
|
@@ -3712,6 +3841,88 @@ function num(args, key) {
|
|
|
3712
3841
|
if (typeof v4 !== "number") throw new Error(`${key} must be a number`);
|
|
3713
3842
|
return v4;
|
|
3714
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
|
+
}
|
|
3715
3926
|
function nonEmptyArray(args, key) {
|
|
3716
3927
|
const v4 = args[key];
|
|
3717
3928
|
if (!Array.isArray(v4) || v4.length === 0) {
|