@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/cli.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  resolveGlobalStorageDir,
13
13
  resolveSqlitePath,
14
14
  resolveSqlitePathForScope
15
- } from "./chunk-JTACLEGM.js";
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 = [`command = ${JSON.stringify(command)}`, `args = ${JSON.stringify(args)}`].join("\n");
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-5OWKTUUZ.js");
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-5OWKTUUZ.js");
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-5OWKTUUZ.js");
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-L3AADJF5.js");
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-5OWKTUUZ.js");
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-L3AADJF5.js");
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: "Get the full action history for a task (all agents, all sections).",
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) {