@cardor/agent-harness-kit 1.5.2 → 1.6.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/reviewer.md +1 -0
- package/dist/cli.js +139 -25
- package/dist/cli.js.map +1 -1
- package/dist/dashboard-dist/assets/{index-Tnz4xXel.js → index-CoqlHfTu.js} +4 -4
- package/dist/dashboard-dist/index.html +9 -6
- package/dist/dashboard-dist/logo-512.png +0 -0
- package/dist/dashboard-dist/logo.png +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/{mysql-NXLYFD2H.js → mysql-THKQOXIS.js} +8 -2
- package/dist/mysql-THKQOXIS.js.map +1 -0
- package/dist/{postgres-6BXN7ZH4.js → postgres-IOQE32DM.js} +8 -2
- package/dist/postgres-IOQE32DM.js.map +1 -0
- package/dist/{sqlite-M65L55DA.js → sqlite-XBEJJ5T2.js} +8 -2
- package/dist/sqlite-XBEJJ5T2.js.map +1 -0
- package/package.json +1 -1
- package/dist/mysql-NXLYFD2H.js.map +0 -1
- package/dist/postgres-6BXN7ZH4.js.map +0 -1
- package/dist/sqlite-M65L55DA.js.map +0 -1
|
@@ -119,6 +119,7 @@ Then notify lead so the builder can be re-assigned.
|
|
|
119
119
|
|
|
120
120
|
- **Run health.sh before approving.** No exceptions.
|
|
121
121
|
- **Check every acceptance criterion.** Not just the obvious ones.
|
|
122
|
+
- **Use `tasks.acceptance.get(taskId)` to retrieve criterion ids.** Call this before `tasks.acceptance.update()` when you do not already have criterion ids from `tasks.get`.
|
|
122
123
|
- **Call `tasks.acceptance.update()` for each criterion.** Never skip this step.
|
|
123
124
|
- **Never self-approve partial work.** All criteria must be met, not most.
|
|
124
125
|
- **Be specific when blocking.** The builder must know exactly what to fix.
|
package/dist/cli.js
CHANGED
|
@@ -67,7 +67,7 @@ function mergeClaudeSettingsJson(filePath) {
|
|
|
67
67
|
};
|
|
68
68
|
writeFileSync2(filePath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
69
69
|
}
|
|
70
|
-
var
|
|
70
|
+
var MCP_CLAUDE_PERMISSIONS_LEAD = [
|
|
71
71
|
"mcp__agent-harness-kit__actions_start",
|
|
72
72
|
"mcp__agent-harness-kit__actions_write",
|
|
73
73
|
"mcp__agent-harness-kit__actions_complete",
|
|
@@ -76,14 +76,69 @@ var MCP_CLAUDE_PERMISSIONS = [
|
|
|
76
76
|
"mcp__agent-harness-kit__actions_record_tool",
|
|
77
77
|
"mcp__agent-harness-kit__tasks_get",
|
|
78
78
|
"mcp__agent-harness-kit__tasks_claim",
|
|
79
|
+
"mcp__agent-harness-kit__tasks_add",
|
|
79
80
|
"mcp__agent-harness-kit__tasks_update",
|
|
81
|
+
"mcp__agent-harness-kit__tasks_edit",
|
|
82
|
+
"mcp__agent-harness-kit__tasks_archive",
|
|
83
|
+
"mcp__agent-harness-kit__tasks_unarchive",
|
|
84
|
+
"mcp__agent-harness-kit__tasks_acceptance_get",
|
|
85
|
+
"mcp__agent-harness-kit__docs_search"
|
|
86
|
+
];
|
|
87
|
+
var MCP_CLAUDE_PERMISSIONS_EXPLORER = [
|
|
88
|
+
"mcp__agent-harness-kit__actions_start",
|
|
89
|
+
"mcp__agent-harness-kit__actions_write",
|
|
90
|
+
"mcp__agent-harness-kit__actions_complete",
|
|
91
|
+
"mcp__agent-harness-kit__actions_get",
|
|
92
|
+
"mcp__agent-harness-kit__actions_record_file",
|
|
93
|
+
"mcp__agent-harness-kit__actions_record_tool",
|
|
94
|
+
"mcp__agent-harness-kit__tasks_get",
|
|
95
|
+
"mcp__agent-harness-kit__tasks_claim",
|
|
96
|
+
"mcp__agent-harness-kit__tasks_acceptance_get",
|
|
97
|
+
"mcp__agent-harness-kit__docs_search"
|
|
98
|
+
];
|
|
99
|
+
var MCP_CLAUDE_PERMISSIONS_BUILDER = [
|
|
100
|
+
"mcp__agent-harness-kit__actions_start",
|
|
101
|
+
"mcp__agent-harness-kit__actions_write",
|
|
102
|
+
"mcp__agent-harness-kit__actions_complete",
|
|
103
|
+
"mcp__agent-harness-kit__actions_get",
|
|
104
|
+
"mcp__agent-harness-kit__actions_record_file",
|
|
105
|
+
"mcp__agent-harness-kit__actions_record_tool",
|
|
106
|
+
"mcp__agent-harness-kit__tasks_get",
|
|
107
|
+
"mcp__agent-harness-kit__tasks_claim",
|
|
80
108
|
"mcp__agent-harness-kit__tasks_add",
|
|
81
|
-
"mcp__agent-harness-
|
|
109
|
+
"mcp__agent-harness-kit__tasks_update",
|
|
110
|
+
"mcp__agent-harness-kit__tasks_edit",
|
|
111
|
+
"mcp__agent-harness-kit__tasks_archive",
|
|
112
|
+
"mcp__agent-harness-kit__tasks_unarchive",
|
|
113
|
+
"mcp__agent-harness-kit__tasks_acceptance_get",
|
|
114
|
+
"mcp__agent-harness-kit__docs_search"
|
|
115
|
+
];
|
|
116
|
+
var MCP_CLAUDE_PERMISSIONS_REVIEWER = [
|
|
117
|
+
"mcp__agent-harness-kit__actions_start",
|
|
118
|
+
"mcp__agent-harness-kit__actions_write",
|
|
119
|
+
"mcp__agent-harness-kit__actions_complete",
|
|
120
|
+
"mcp__agent-harness-kit__actions_get",
|
|
121
|
+
"mcp__agent-harness-kit__actions_record_file",
|
|
122
|
+
"mcp__agent-harness-kit__actions_record_tool",
|
|
123
|
+
"mcp__agent-harness-kit__tasks_get",
|
|
124
|
+
"mcp__agent-harness-kit__tasks_claim",
|
|
125
|
+
"mcp__agent-harness-kit__tasks_add",
|
|
126
|
+
"mcp__agent-harness-kit__tasks_update",
|
|
82
127
|
"mcp__agent-harness-kit__tasks_edit",
|
|
83
128
|
"mcp__agent-harness-kit__tasks_archive",
|
|
84
129
|
"mcp__agent-harness-kit__tasks_unarchive",
|
|
130
|
+
"mcp__agent-harness-kit__tasks_acceptance_update",
|
|
131
|
+
"mcp__agent-harness-kit__tasks_acceptance_get",
|
|
85
132
|
"mcp__agent-harness-kit__docs_search"
|
|
86
133
|
];
|
|
134
|
+
var MCP_CLAUDE_PERMISSIONS = [
|
|
135
|
+
.../* @__PURE__ */ new Set([
|
|
136
|
+
...MCP_CLAUDE_PERMISSIONS_LEAD,
|
|
137
|
+
...MCP_CLAUDE_PERMISSIONS_EXPLORER,
|
|
138
|
+
...MCP_CLAUDE_PERMISSIONS_BUILDER,
|
|
139
|
+
...MCP_CLAUDE_PERMISSIONS_REVIEWER
|
|
140
|
+
])
|
|
141
|
+
];
|
|
87
142
|
function mergeClaudeSettingsLocalJson(filePath) {
|
|
88
143
|
mkdirSync2(dirname(filePath), { recursive: true });
|
|
89
144
|
let existing = {};
|
|
@@ -491,8 +546,15 @@ function agentReviewerToml(vars) {
|
|
|
491
546
|
const { description, body } = stripFrontmatter(loadAgentTemplate("reviewer", vars));
|
|
492
547
|
return toCodexToml("reviewer", description, body, "read-only");
|
|
493
548
|
}
|
|
494
|
-
function translateFrontmatterForClaudeCode(md,
|
|
495
|
-
const
|
|
549
|
+
function translateFrontmatterForClaudeCode(md, agentName) {
|
|
550
|
+
const permissionsMap = {
|
|
551
|
+
lead: MCP_CLAUDE_PERMISSIONS_LEAD,
|
|
552
|
+
explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
|
|
553
|
+
builder: MCP_CLAUDE_PERMISSIONS_BUILDER,
|
|
554
|
+
reviewer: MCP_CLAUDE_PERMISSIONS_REVIEWER
|
|
555
|
+
};
|
|
556
|
+
const permissions = permissionsMap[agentName] ?? MCP_CLAUDE_PERMISSIONS;
|
|
557
|
+
const mcpLines = permissions.map((t) => ` - ${t}`).join("\n");
|
|
496
558
|
return md.replace(/(tools:\n(?: - (?!mcp__)[^\n]+\n)+)/, (match) => {
|
|
497
559
|
const trimmed = match.trimEnd();
|
|
498
560
|
return `${trimmed}
|
|
@@ -770,6 +832,30 @@ import { extname, join as join7 } from "path";
|
|
|
770
832
|
import { serve } from "@hono/node-server";
|
|
771
833
|
import { Hono } from "hono";
|
|
772
834
|
import { WebSocketServer } from "ws";
|
|
835
|
+
|
|
836
|
+
// src/core/port-utils.ts
|
|
837
|
+
import { createServer } from "net";
|
|
838
|
+
function isPortFree(port) {
|
|
839
|
+
return new Promise((resolve13) => {
|
|
840
|
+
const server = createServer();
|
|
841
|
+
server.once("error", () => resolve13(false));
|
|
842
|
+
server.once("listening", () => {
|
|
843
|
+
server.close(() => resolve13(true));
|
|
844
|
+
});
|
|
845
|
+
server.listen(port, "127.0.0.1");
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
async function findFreePort(start, maxAttempts = 10) {
|
|
849
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
850
|
+
const port = start + i;
|
|
851
|
+
if (await isPortFree(port)) return port;
|
|
852
|
+
}
|
|
853
|
+
throw new Error(
|
|
854
|
+
`Could not find a free port after ${maxAttempts} attempts (tried ${start}-${start + maxAttempts - 1}). Please free a port and try again.`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/core/dashboard-server.ts
|
|
773
859
|
var MIME = {
|
|
774
860
|
".html": "text/html; charset=utf-8",
|
|
775
861
|
".js": "application/javascript; charset=utf-8",
|
|
@@ -790,7 +876,7 @@ function fileResponse(filePath) {
|
|
|
790
876
|
headers: { "Content-Type": mime, "Cache-Control": "no-cache" }
|
|
791
877
|
});
|
|
792
878
|
}
|
|
793
|
-
function startDashboardServer(db, dbPath, staticPath, port) {
|
|
879
|
+
async function startDashboardServer(db, dbPath, staticPath, port) {
|
|
794
880
|
const app = new Hono();
|
|
795
881
|
const { tasks, actions, stats } = db;
|
|
796
882
|
app.use("/api/*", async (c, next) => {
|
|
@@ -832,7 +918,10 @@ function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
832
918
|
if (body.description !== void 0) updateParams.description = body.description;
|
|
833
919
|
await db.updateTask(id, updateParams);
|
|
834
920
|
if (body.acceptance !== void 0 && Array.isArray(body.acceptance)) {
|
|
835
|
-
await db.updateTaskAcceptance(
|
|
921
|
+
await db.updateTaskAcceptance(
|
|
922
|
+
id,
|
|
923
|
+
body.acceptance.map((a) => a.trim()).filter(Boolean)
|
|
924
|
+
);
|
|
836
925
|
}
|
|
837
926
|
const updated = await tasks.getById(id);
|
|
838
927
|
const acceptance = await tasks.getAcceptance(id);
|
|
@@ -897,7 +986,11 @@ function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
897
986
|
}
|
|
898
987
|
return fileResponse(join7(staticPath, "index.html"));
|
|
899
988
|
});
|
|
900
|
-
const
|
|
989
|
+
const resolvedPort = await findFreePort(port);
|
|
990
|
+
if (resolvedPort !== port) {
|
|
991
|
+
console.log(`Port ${port} in use, using ${resolvedPort}`);
|
|
992
|
+
}
|
|
993
|
+
const httpServer = serve({ fetch: app.fetch, port: resolvedPort });
|
|
901
994
|
const wss = new WebSocketServer({ noServer: true });
|
|
902
995
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
903
996
|
if (req.url === "/ws") {
|
|
@@ -926,7 +1019,7 @@ function startDashboardServer(db, dbPath, staticPath, port) {
|
|
|
926
1019
|
watcher = watch2(watchTarget, broadcast);
|
|
927
1020
|
}
|
|
928
1021
|
return {
|
|
929
|
-
url: `http://localhost:${
|
|
1022
|
+
url: `http://localhost:${resolvedPort}`,
|
|
930
1023
|
close: () => {
|
|
931
1024
|
clearTimeout(debounce);
|
|
932
1025
|
watcher?.close();
|
|
@@ -1149,8 +1242,8 @@ var TaskRepository = class {
|
|
|
1149
1242
|
async add(params) {
|
|
1150
1243
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1151
1244
|
return this.driver.insert(
|
|
1152
|
-
`INSERT INTO tasks (slug, title, description, status, created_at) VALUES (?, ?, ?, ?, ?)`,
|
|
1153
|
-
[params.slug, params.title, params.description ?? null, params.status ?? "pending", now]
|
|
1245
|
+
`INSERT INTO tasks (slug, title, description, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
1246
|
+
[params.slug, params.title, params.description ?? null, params.status ?? "pending", now, now]
|
|
1154
1247
|
);
|
|
1155
1248
|
}
|
|
1156
1249
|
async addAcceptance(taskId, criteria) {
|
|
@@ -1175,7 +1268,7 @@ var TaskRepository = class {
|
|
|
1175
1268
|
if (conditions.length > 0) {
|
|
1176
1269
|
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
1177
1270
|
}
|
|
1178
|
-
sql += ` ORDER BY
|
|
1271
|
+
sql += ` ORDER BY CASE status WHEN 'pending' THEN 1 WHEN 'in_progress' THEN 2 WHEN 'blocked' THEN 3 WHEN 'done' THEN 4 ELSE 5 END, updated_at DESC`;
|
|
1179
1272
|
return this.driver.query(sql, params);
|
|
1180
1273
|
}
|
|
1181
1274
|
async getAllWithAcceptanceCounts(includeArchived = false) {
|
|
@@ -1189,7 +1282,7 @@ var TaskRepository = class {
|
|
|
1189
1282
|
if (!includeArchived) {
|
|
1190
1283
|
sql += ` WHERE t.archived_at IS NULL`;
|
|
1191
1284
|
}
|
|
1192
|
-
sql += ` GROUP BY t.id ORDER BY t.
|
|
1285
|
+
sql += ` GROUP BY t.id ORDER BY CASE t.status WHEN 'pending' THEN 1 WHEN 'in_progress' THEN 2 WHEN 'blocked' THEN 3 WHEN 'done' THEN 4 ELSE 5 END, t.updated_at DESC`;
|
|
1193
1286
|
return this.driver.query(sql);
|
|
1194
1287
|
}
|
|
1195
1288
|
async getById(id) {
|
|
@@ -1205,23 +1298,25 @@ var TaskRepository = class {
|
|
|
1205
1298
|
);
|
|
1206
1299
|
}
|
|
1207
1300
|
async setStatus(id, status, extra) {
|
|
1301
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1208
1302
|
if (extra?.started_at) {
|
|
1209
1303
|
await this.driver.exec(
|
|
1210
|
-
`UPDATE tasks SET status = ?, started_at = ? WHERE id = ?`,
|
|
1211
|
-
[status, extra.started_at, id]
|
|
1304
|
+
`UPDATE tasks SET status = ?, started_at = ?, updated_at = ? WHERE id = ?`,
|
|
1305
|
+
[status, extra.started_at, now, id]
|
|
1212
1306
|
);
|
|
1213
1307
|
} else if (extra?.completed_at) {
|
|
1214
1308
|
await this.driver.exec(
|
|
1215
|
-
`UPDATE tasks SET status = ?, completed_at = ? WHERE id = ?`,
|
|
1216
|
-
[status, extra.completed_at, id]
|
|
1309
|
+
`UPDATE tasks SET status = ?, completed_at = ?, updated_at = ? WHERE id = ?`,
|
|
1310
|
+
[status, extra.completed_at, now, id]
|
|
1217
1311
|
);
|
|
1218
1312
|
} else {
|
|
1219
|
-
await this.driver.exec(`UPDATE tasks SET status = ? WHERE id = ?`, [status, id]);
|
|
1313
|
+
await this.driver.exec(`UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?`, [status, now, id]);
|
|
1220
1314
|
}
|
|
1221
1315
|
}
|
|
1222
1316
|
async update(id, params) {
|
|
1223
1317
|
const sets = [];
|
|
1224
1318
|
const vals = [];
|
|
1319
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1225
1320
|
if (params.title !== void 0) {
|
|
1226
1321
|
sets.push("title = ?");
|
|
1227
1322
|
vals.push(params.title);
|
|
@@ -1235,6 +1330,8 @@ var TaskRepository = class {
|
|
|
1235
1330
|
vals.push(params.slug);
|
|
1236
1331
|
}
|
|
1237
1332
|
if (sets.length === 0) return;
|
|
1333
|
+
sets.push("updated_at = ?");
|
|
1334
|
+
vals.push(now);
|
|
1238
1335
|
vals.push(id);
|
|
1239
1336
|
await this.driver.exec(`UPDATE tasks SET ${sets.join(", ")} WHERE id = ?`, vals);
|
|
1240
1337
|
}
|
|
@@ -1249,10 +1346,11 @@ var TaskRepository = class {
|
|
|
1249
1346
|
}
|
|
1250
1347
|
async archive(id) {
|
|
1251
1348
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1252
|
-
await this.driver.exec(`UPDATE tasks SET archived_at = ? WHERE id = ?`, [now, id]);
|
|
1349
|
+
await this.driver.exec(`UPDATE tasks SET archived_at = ?, updated_at = ? WHERE id = ?`, [now, now, id]);
|
|
1253
1350
|
}
|
|
1254
1351
|
async unarchive(id) {
|
|
1255
|
-
|
|
1352
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1353
|
+
await this.driver.exec(`UPDATE tasks SET archived_at = NULL, updated_at = ? WHERE id = ?`, [now, id]);
|
|
1256
1354
|
}
|
|
1257
1355
|
async getArchived() {
|
|
1258
1356
|
return this.driver.query(
|
|
@@ -1261,8 +1359,8 @@ var TaskRepository = class {
|
|
|
1261
1359
|
}
|
|
1262
1360
|
async claim(id, agent, now) {
|
|
1263
1361
|
return this.driver.exec(
|
|
1264
|
-
`UPDATE tasks SET status = 'in_progress', assigned_to = ?, started_at = ? WHERE id = ? AND status = 'pending'`,
|
|
1265
|
-
[agent, now, id]
|
|
1362
|
+
`UPDATE tasks SET status = 'in_progress', assigned_to = ?, started_at = ?, updated_at = ? WHERE id = ? AND status = 'pending'`,
|
|
1363
|
+
[agent, now, now, id]
|
|
1266
1364
|
);
|
|
1267
1365
|
}
|
|
1268
1366
|
async markAcceptanceMet(criterionId) {
|
|
@@ -1538,13 +1636,13 @@ async function openDB(config, cwd2) {
|
|
|
1538
1636
|
const dbConfig = config.database;
|
|
1539
1637
|
let driver;
|
|
1540
1638
|
if (dbConfig.type === "postgres") {
|
|
1541
|
-
const { PostgresDriver } = await import("./postgres-
|
|
1639
|
+
const { PostgresDriver } = await import("./postgres-IOQE32DM.js");
|
|
1542
1640
|
driver = new PostgresDriver(dbConfig);
|
|
1543
1641
|
} else if (dbConfig.type === "mysql") {
|
|
1544
|
-
const { MySQLDriver } = await import("./mysql-
|
|
1642
|
+
const { MySQLDriver } = await import("./mysql-THKQOXIS.js");
|
|
1545
1643
|
driver = new MySQLDriver(dbConfig);
|
|
1546
1644
|
} else {
|
|
1547
|
-
const { SQLiteDriver } = await import("./sqlite-
|
|
1645
|
+
const { SQLiteDriver } = await import("./sqlite-XBEJJ5T2.js");
|
|
1548
1646
|
if (dbConfig.type !== "sqlite") {
|
|
1549
1647
|
throw new Error("Invalid database type");
|
|
1550
1648
|
}
|
|
@@ -1561,7 +1659,7 @@ async function runDashboard(cwd2, opts) {
|
|
|
1561
1659
|
const db = await openDB(config, cwd2);
|
|
1562
1660
|
const dbPath = config.database.type === "sqlite" ? resolve7(cwd2, config.database.path) : null;
|
|
1563
1661
|
const staticPath = join9(__dirname2, "dashboard-dist");
|
|
1564
|
-
const { url } = startDashboardServer(db, dbPath, staticPath, opts.port);
|
|
1662
|
+
const { url } = await startDashboardServer(db, dbPath, staticPath, opts.port);
|
|
1565
1663
|
console.log(pc2.green(`\u2713`) + ` Dashboard running at ${pc2.bold(pc2.cyan(url))}`);
|
|
1566
1664
|
console.log(pc2.dim(` WebSocket live updates enabled`));
|
|
1567
1665
|
console.log(pc2.dim(` Press Ctrl+C to stop`));
|
|
@@ -2375,6 +2473,17 @@ var TOOLS = [
|
|
|
2375
2473
|
required: ["criterionId"]
|
|
2376
2474
|
}
|
|
2377
2475
|
},
|
|
2476
|
+
{
|
|
2477
|
+
name: "tasks.acceptance.get",
|
|
2478
|
+
description: "Given a taskId, returns all acceptance criteria for that task with their id, task_id, criterion text, and met status. Use the returned id values to call tasks.acceptance_update(criterionId).",
|
|
2479
|
+
inputSchema: {
|
|
2480
|
+
type: "object",
|
|
2481
|
+
properties: {
|
|
2482
|
+
taskId: { type: "number", description: "Task ID" }
|
|
2483
|
+
},
|
|
2484
|
+
required: ["taskId"]
|
|
2485
|
+
}
|
|
2486
|
+
},
|
|
2378
2487
|
{
|
|
2379
2488
|
name: "tasks.add",
|
|
2380
2489
|
description: "Create a new task in the harness. Use this when the user describes work in natural language. Infer slug, title, description, and acceptance criteria from the conversation. Ask for missing critical info before calling.",
|
|
@@ -2551,6 +2660,11 @@ async function dispatch(name, args, db, docsPath) {
|
|
|
2551
2660
|
await db.markAcceptanceMet(criterionId);
|
|
2552
2661
|
return ok(JSON.stringify({ criterionId, met: true }));
|
|
2553
2662
|
}
|
|
2663
|
+
case "tasks.acceptance.get": {
|
|
2664
|
+
const taskId = num(args, "taskId");
|
|
2665
|
+
const criteria = await db.getTaskAcceptance(taskId);
|
|
2666
|
+
return ok(JSON.stringify(criteria, null, 2));
|
|
2667
|
+
}
|
|
2554
2668
|
case "actions.record_tool": {
|
|
2555
2669
|
const actionId = str(args, "actionId");
|
|
2556
2670
|
const toolName = str(args, "toolName");
|