@programinglive/dev-workflow-mcp-server 1.9.1 → 1.10.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 +311 -173
- package/factory-client.js +554 -0
- package/git-facade.js +35 -0
- package/index.js +4 -30
- package/package.json +14 -18
- package/scripts/setup-test-dbs.sh +61 -0
- package/scripts/test-db-adapters.sh +71 -0
- package/scripts/verify-bundle.mjs +92 -0
- package/tools/definitions.js +124 -0
- package/tools/handler-modules/factory-handlers.js +276 -0
- package/tools/handler-modules/index.js +11 -0
- package/tools/handler-modules/status-handlers.js +22 -2
- package/tools/handler-modules/workflow-handlers.js +32 -3
- package/vite.config.js +6 -0
- package/workflow-state.js +166 -2
- package/scripts/test-auth-flow.js +0 -75
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "MCP server to enforce development workflow discipline",
|
|
5
5
|
"homepage": "https://devworkflow.programinglive.com/",
|
|
6
6
|
"mcpName": "io.github.programinglive/dev-workflow-mcp-server",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "index.js",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20"
|
|
11
|
+
},
|
|
9
12
|
"bin": {
|
|
10
13
|
"dev-workflow-mcp": "index.js"
|
|
11
14
|
},
|
|
@@ -27,7 +30,8 @@
|
|
|
27
30
|
"release:minor": "node scripts/release.cjs minor",
|
|
28
31
|
"release:patch": "node scripts/release.cjs patch",
|
|
29
32
|
"web": "npm start --prefix web",
|
|
30
|
-
"release:first": "node scripts/release.cjs --first-release"
|
|
33
|
+
"release:first": "node scripts/release.cjs --first-release",
|
|
34
|
+
"verify:bundle": "node scripts/verify-bundle.mjs"
|
|
31
35
|
},
|
|
32
36
|
"keywords": [
|
|
33
37
|
"mcp",
|
|
@@ -43,19 +47,11 @@
|
|
|
43
47
|
},
|
|
44
48
|
"dependencies": {
|
|
45
49
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
46
|
-
"bcrypt": "^5.1.1",
|
|
47
50
|
"better-sqlite3": "^12.11.1",
|
|
48
|
-
"
|
|
49
|
-
"dotenv": "^17.2.3",
|
|
50
|
-
"esbuild": "^0.25.0",
|
|
51
|
-
"express": "^4.18.2",
|
|
52
|
-
"express-session": "^1.18.0",
|
|
53
|
-
"get-port": "^7.1.0",
|
|
54
|
-
"marked": "^12.0.2",
|
|
51
|
+
"dotenv": "^18.0.9",
|
|
55
52
|
"mysql2": "^3.15.3",
|
|
56
|
-
"pg": "^8.
|
|
57
|
-
"vite": "^7.1.12"
|
|
58
|
-
"ws": "^8.16.0"
|
|
53
|
+
"pg": "^8.23.1",
|
|
54
|
+
"vite": "^7.1.12"
|
|
59
55
|
},
|
|
60
56
|
"publishConfig": {
|
|
61
57
|
"access": "public"
|
|
@@ -64,17 +60,17 @@
|
|
|
64
60
|
"tar": "7.5.22"
|
|
65
61
|
},
|
|
66
62
|
"devDependencies": {
|
|
67
|
-
"@commitlint/cli": "^
|
|
68
|
-
"@commitlint/config-conventional": "^
|
|
63
|
+
"@commitlint/cli": "^21.2.4",
|
|
64
|
+
"@commitlint/config-conventional": "^21.2.4",
|
|
69
65
|
"@programinglive/commiter": "^1.2.17",
|
|
70
66
|
"@types/node": "^26.6.5",
|
|
71
|
-
"autoprefixer": "^10.
|
|
67
|
+
"autoprefixer": "^10.6.1",
|
|
72
68
|
"cross-env": "^10.1.0",
|
|
73
69
|
"husky": "^9.1.7",
|
|
74
70
|
"postcss": "^8.5.6",
|
|
75
71
|
"standard-version": "^9.5.0",
|
|
76
|
-
"supertest": "^7.1
|
|
77
|
-
"typescript": "^
|
|
72
|
+
"supertest": "^7.3.1",
|
|
73
|
+
"typescript": "^7.0.2"
|
|
78
74
|
},
|
|
79
75
|
"standard-version": {
|
|
80
76
|
"releaseCommitMessageFormat": "chore(release): {{currentTag}} 🚀",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Creates isolated test databases and users on the database host.
|
|
4
|
+
#
|
|
5
|
+
# Run this on bw-server (it uses sudo locally). It creates:
|
|
6
|
+
# - PostgreSQL database `devworkflow_test` and role `devworkflow_test`
|
|
7
|
+
# - MySQL database `devworkflow_test` and user `dwftest`
|
|
8
|
+
#
|
|
9
|
+
# It never touches an existing database, and the role is granted access only to
|
|
10
|
+
# its own database. The adapter tests INSERT and UPDATE real rows, so pointing
|
|
11
|
+
# them at a production database would corrupt real history data.
|
|
12
|
+
#
|
|
13
|
+
# Prints the two connection URLs at the end.
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
PG_DB=devworkflow_test
|
|
18
|
+
PG_USER=devworkflow_test
|
|
19
|
+
PG_PASS="$(openssl rand -hex 16)"
|
|
20
|
+
|
|
21
|
+
MY_DB=devworkflow_test
|
|
22
|
+
MY_USER=dwftest
|
|
23
|
+
MY_PASS="$(openssl rand -hex 16)"
|
|
24
|
+
|
|
25
|
+
echo "=== PostgreSQL ==="
|
|
26
|
+
sudo -u postgres psql -v ON_ERROR_STOP=1 <<SQL
|
|
27
|
+
DO \$\$
|
|
28
|
+
BEGIN
|
|
29
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='${PG_USER}') THEN
|
|
30
|
+
CREATE ROLE ${PG_USER} WITH LOGIN PASSWORD '${PG_PASS}';
|
|
31
|
+
ELSE
|
|
32
|
+
ALTER ROLE ${PG_USER} WITH LOGIN PASSWORD '${PG_PASS}';
|
|
33
|
+
END IF;
|
|
34
|
+
END
|
|
35
|
+
\$\$;
|
|
36
|
+
SQL
|
|
37
|
+
|
|
38
|
+
sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${PG_DB}'" | grep -q 1 \
|
|
39
|
+
|| sudo -u postgres createdb -O "${PG_USER}" "${PG_DB}"
|
|
40
|
+
|
|
41
|
+
sudo -u postgres psql -d "${PG_DB}" -c "GRANT ALL ON SCHEMA public TO ${PG_USER};" > /dev/null
|
|
42
|
+
sudo -u postgres psql -d "${PG_DB}" -c "ALTER SCHEMA public OWNER TO ${PG_USER};" > /dev/null
|
|
43
|
+
|
|
44
|
+
echo "=== MySQL ==="
|
|
45
|
+
sudo mysql <<SQL
|
|
46
|
+
CREATE DATABASE IF NOT EXISTS \`${MY_DB}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
47
|
+
CREATE USER IF NOT EXISTS '${MY_USER}'@'localhost' IDENTIFIED BY '${MY_PASS}';
|
|
48
|
+
ALTER USER '${MY_USER}'@'localhost' IDENTIFIED BY '${MY_PASS}';
|
|
49
|
+
GRANT ALL PRIVILEGES ON \`${MY_DB}\`.* TO '${MY_USER}'@'localhost';
|
|
50
|
+
FLUSH PRIVILEGES;
|
|
51
|
+
SQL
|
|
52
|
+
|
|
53
|
+
echo "=== verification ==="
|
|
54
|
+
mysql -h 127.0.0.1 -u "${MY_USER}" -p"${MY_PASS}" -N -B -e "SELECT 'mysql ok'" "${MY_DB}"
|
|
55
|
+
|
|
56
|
+
echo
|
|
57
|
+
echo "Add these to your shell before running scripts/test-db-adapters.sh"
|
|
58
|
+
echo "(ports are the local tunnel ports):"
|
|
59
|
+
echo
|
|
60
|
+
echo "TEST_POSTGRES_URL=postgresql://${PG_USER}:${PG_PASS}@127.0.0.1:15432/${PG_DB}"
|
|
61
|
+
echo "TEST_MYSQL_URL=mysql://${MY_USER}:${MY_PASS}@127.0.0.1:13306/${MY_DB}"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Runs the database adapter tests against real MySQL and PostgreSQL.
|
|
4
|
+
#
|
|
5
|
+
# The two adapters are skipped in the default suite because they need a live
|
|
6
|
+
# database. This script opens an SSH tunnel to bw-server (where both servers
|
|
7
|
+
# run on localhost), runs only the adapter tests, and closes the tunnel.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ./scripts/test-db-adapters.sh
|
|
11
|
+
#
|
|
12
|
+
# Requires:
|
|
13
|
+
# - an SSH host alias `bw-tunnel` that can reach 127.0.0.1:3306 and :5432
|
|
14
|
+
# (see ~/.ssh/config; the restricted dbtunnel account is enough)
|
|
15
|
+
# - TEST_MYSQL_URL / TEST_POSTGRES_URL pointing at the forwarded ports,
|
|
16
|
+
# or the defaults below
|
|
17
|
+
#
|
|
18
|
+
# The test databases are created by scripts/setup-test-dbs.sh on the server.
|
|
19
|
+
# Tests write real rows, so never point these at a production database.
|
|
20
|
+
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
LOCAL_PG_PORT="${LOCAL_PG_PORT:-15432}"
|
|
24
|
+
LOCAL_MY_PORT="${LOCAL_MY_PORT:-13306}"
|
|
25
|
+
SSH_HOST="${SSH_HOST:-bw-tunnel}"
|
|
26
|
+
|
|
27
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
28
|
+
|
|
29
|
+
if ! command -v ssh > /dev/null 2>&1; then
|
|
30
|
+
echo "ssh not found; this script needs an SSH tunnel to the database host." >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
cleanup() {
|
|
35
|
+
if [ -n "${TUNNEL_PID:-}" ] && kill -0 "$TUNNEL_PID" 2> /dev/null; then
|
|
36
|
+
kill "$TUNNEL_PID" 2> /dev/null || true
|
|
37
|
+
wait "$TUNNEL_PID" 2> /dev/null || true
|
|
38
|
+
echo "tunnel closed"
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
trap cleanup EXIT
|
|
42
|
+
|
|
43
|
+
echo "opening tunnel to $SSH_HOST ($LOCAL_PG_PORT -> 5432, $LOCAL_MY_PORT -> 3306)"
|
|
44
|
+
ssh -o BatchMode=yes -o ExitOnForwardFailure=yes -N \
|
|
45
|
+
-L "${LOCAL_PG_PORT}:127.0.0.1:5432" \
|
|
46
|
+
-L "${LOCAL_MY_PORT}:127.0.0.1:3306" \
|
|
47
|
+
"$SSH_HOST" &
|
|
48
|
+
TUNNEL_PID=$!
|
|
49
|
+
|
|
50
|
+
# Wait for the forwards to accept connections.
|
|
51
|
+
for port in "$LOCAL_PG_PORT" "$LOCAL_MY_PORT"; do
|
|
52
|
+
ready=0
|
|
53
|
+
for _ in $(seq 1 20); do
|
|
54
|
+
if (exec 3<>"/dev/tcp/127.0.0.1/$port") 2> /dev/null; then
|
|
55
|
+
exec 3>&- 3<&-
|
|
56
|
+
ready=1
|
|
57
|
+
break
|
|
58
|
+
fi
|
|
59
|
+
sleep 0.5
|
|
60
|
+
done
|
|
61
|
+
if [ "$ready" -ne 1 ]; then
|
|
62
|
+
echo "port $port did not open; is $SSH_HOST reachable?" >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
: "${TEST_POSTGRES_URL:?set TEST_POSTGRES_URL (see scripts/setup-test-dbs.sh)}"
|
|
68
|
+
: "${TEST_MYSQL_URL:?set TEST_MYSQL_URL (see scripts/setup-test-dbs.sh)}"
|
|
69
|
+
|
|
70
|
+
cd "$REPO_ROOT"
|
|
71
|
+
node --no-deprecation --test tests/db-adapters.test.js
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Verifies that the built bundle actually starts.
|
|
5
|
+
*
|
|
6
|
+
* `npm run build:server` succeeding only means the bundler emitted a file. It
|
|
7
|
+
* says nothing about whether that file runs. This bit us with vite 8: the build
|
|
8
|
+
* was green and smaller, but `node dist/index.mjs` threw
|
|
9
|
+
*
|
|
10
|
+
* Calling `require` for "fs" in an environment that doesn't expose the
|
|
11
|
+
* `require` function
|
|
12
|
+
*
|
|
13
|
+
* because rolldown's CJS shim assumed CommonJS. CI only ran the build, so a
|
|
14
|
+
* broken artefact would have shipped.
|
|
15
|
+
*
|
|
16
|
+
* The MCP server is a stdio server: it exits cleanly when stdin closes. So the
|
|
17
|
+
* pass condition is the startup banner, not the exit code - the same rule used
|
|
18
|
+
* by scripts/setup-stack.js and the release workflow.
|
|
19
|
+
*
|
|
20
|
+
* Usage: node scripts/verify-bundle.mjs [dist/index.mjs]
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { spawn } from 'node:child_process';
|
|
24
|
+
import { existsSync } from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
|
|
27
|
+
const entry = path.resolve(process.argv[2] || 'dist/index.mjs');
|
|
28
|
+
const STARTUP_PATTERN = /running on stdio|MCP Server starting/i;
|
|
29
|
+
const TIMEOUT_MS = 15000;
|
|
30
|
+
|
|
31
|
+
if (!existsSync(entry)) {
|
|
32
|
+
console.error(`✖ Bundle not found: ${entry}`);
|
|
33
|
+
console.error(' Run "npm run build:server" first.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(`Verifying ${path.relative(process.cwd(), entry)}`);
|
|
38
|
+
|
|
39
|
+
const child = spawn(process.execPath, [entry], {
|
|
40
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let stdout = '';
|
|
44
|
+
let stderr = '';
|
|
45
|
+
let settled = false;
|
|
46
|
+
|
|
47
|
+
child.stdout.on('data', (chunk) => {
|
|
48
|
+
stdout += chunk.toString();
|
|
49
|
+
});
|
|
50
|
+
child.stderr.on('data', (chunk) => {
|
|
51
|
+
stderr += chunk.toString();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function finish(ok, reason) {
|
|
55
|
+
if (settled) return;
|
|
56
|
+
settled = true;
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
try {
|
|
59
|
+
child.kill();
|
|
60
|
+
} catch {
|
|
61
|
+
// already gone
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const output = `${stdout}${stderr}`;
|
|
65
|
+
if (ok) {
|
|
66
|
+
console.log(`✓ ${reason}`);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.error(`✖ ${reason}`);
|
|
71
|
+
console.error('--- bundle output ---');
|
|
72
|
+
console.error(output.trim() || '(no output)');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
child.on('error', (error) => finish(false, `Failed to spawn: ${error.message}`));
|
|
77
|
+
|
|
78
|
+
child.on('exit', (code) => {
|
|
79
|
+
if (STARTUP_PATTERN.test(`${stdout}${stderr}`)) {
|
|
80
|
+
finish(true, 'Bundle started and exited cleanly (stdin closed).');
|
|
81
|
+
} else {
|
|
82
|
+
finish(false, `Bundle exited with code ${code} without reporting that it started.`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
if (STARTUP_PATTERN.test(`${stdout}${stderr}`)) {
|
|
88
|
+
finish(true, 'Bundle started and stayed alive.');
|
|
89
|
+
} else {
|
|
90
|
+
finish(false, `Bundle produced no startup output within ${TIMEOUT_MS}ms.`);
|
|
91
|
+
}
|
|
92
|
+
}, TIMEOUT_MS);
|
package/tools/definitions.js
CHANGED
|
@@ -362,5 +362,129 @@ export function getToolList() {
|
|
|
362
362
|
properties: {},
|
|
363
363
|
},
|
|
364
364
|
},
|
|
365
|
+
{
|
|
366
|
+
name: "list_factory_tasks",
|
|
367
|
+
description:
|
|
368
|
+
"List tasks from the Factory board (factory.programinglive.com), newest activity first. Defaults to work that still needs attention: running, queued, backlog, review and blocked. Requires FACTORY_URL plus FACTORY_TOKEN, or FACTORY_EMAIL and FACTORY_PASSWORD.",
|
|
369
|
+
inputSchema: {
|
|
370
|
+
type: "object",
|
|
371
|
+
properties: {
|
|
372
|
+
statuses: {
|
|
373
|
+
type: "array",
|
|
374
|
+
items: { type: "string", enum: ["backlog", "queued", "running", "review", "blocked", "done", "cancelled"] },
|
|
375
|
+
description: "Statuses to include. Defaults to running, queued, backlog, review and blocked.",
|
|
376
|
+
},
|
|
377
|
+
project: {
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "Optional project name or id to filter by (case-insensitive substring).",
|
|
380
|
+
},
|
|
381
|
+
limit: {
|
|
382
|
+
type: "number",
|
|
383
|
+
description: "Maximum number of tasks to return (1-200, default 50).",
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: "list_factory_projects",
|
|
390
|
+
description:
|
|
391
|
+
"List projects in the Factory workspace with their UUIDs. Run this before create_factory_task, because Factory requires a real projectId and cannot accept an invented one.",
|
|
392
|
+
inputSchema: {
|
|
393
|
+
type: "object",
|
|
394
|
+
properties: {},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: "create_factory_task",
|
|
399
|
+
description:
|
|
400
|
+
"Create a task on the Factory board through the official write path (POST /api/factory create_task). Defaults to status 'backlog', which records the work WITHOUT starting a worker run. Only pass status 'queued' when you really want the worker to execute it - Factory rejects a queued task (409) unless 'acceptance' contains a ```run block. Use list_factory_projects first to get a real projectId.",
|
|
401
|
+
inputSchema: {
|
|
402
|
+
type: "object",
|
|
403
|
+
properties: {
|
|
404
|
+
projectId: {
|
|
405
|
+
type: "string",
|
|
406
|
+
description: "Project UUID from list_factory_projects. Required.",
|
|
407
|
+
},
|
|
408
|
+
title: {
|
|
409
|
+
type: "string",
|
|
410
|
+
description: "Task title, 3-140 characters.",
|
|
411
|
+
},
|
|
412
|
+
description: {
|
|
413
|
+
type: "string",
|
|
414
|
+
description: "Optional context: current behaviour, what should change. Max 4000 characters.",
|
|
415
|
+
},
|
|
416
|
+
acceptance: {
|
|
417
|
+
type: "string",
|
|
418
|
+
description:
|
|
419
|
+
"What must be true for this task to be done. 3-3000 characters. Required by Factory.",
|
|
420
|
+
},
|
|
421
|
+
priority: {
|
|
422
|
+
type: "string",
|
|
423
|
+
enum: ["low", "normal", "high"],
|
|
424
|
+
description: "Defaults to normal.",
|
|
425
|
+
},
|
|
426
|
+
status: {
|
|
427
|
+
type: "string",
|
|
428
|
+
enum: ["backlog", "queued"],
|
|
429
|
+
description:
|
|
430
|
+
"Defaults to backlog. 'queued' also creates a worker run and requires a ```run block in acceptance.",
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
required: ["projectId", "title", "acceptance"],
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "claim_factory_task",
|
|
438
|
+
description:
|
|
439
|
+
"Claim a task by opening a Factory work session. This is what moves the task to 'running' on the board, because Factory has no plain status edit for it. Also starts the local workflow for that task. Use the task id from list_factory_tasks. If the task is held by another device, pass 'takeoverReason' to take it over; Factory records that reason on the board and locks the previous holder out.",
|
|
440
|
+
inputSchema: {
|
|
441
|
+
type: "object",
|
|
442
|
+
properties: {
|
|
443
|
+
recordId: {
|
|
444
|
+
type: "string",
|
|
445
|
+
description: "Factory task id (UUID) to claim.",
|
|
446
|
+
},
|
|
447
|
+
step: {
|
|
448
|
+
type: "string",
|
|
449
|
+
description: "Short description of what you are about to do, shown on the device row (max 240 chars).",
|
|
450
|
+
},
|
|
451
|
+
takeoverReason: {
|
|
452
|
+
type: "string",
|
|
453
|
+
description:
|
|
454
|
+
"Alasan mengambil alih task yang sedang dipegang perangkat lain (3-500 karakter). Wajib diisi hanya bila task itu dipegang perangkat lain. Alasannya dicatat di papan.",
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
required: ["recordId"],
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "sync_task_status",
|
|
462
|
+
description:
|
|
463
|
+
"Report a task status change to Factory using the endpoint that supports it: review, blocked and paused finish the work session; backlog, queued, cancelled and done move the task. 'done' is the approval step: Factory accepts it only when the task is currently in review, and it marks the work as finished. 'running' is set by claim_factory_task, not here.",
|
|
464
|
+
inputSchema: {
|
|
465
|
+
type: "object",
|
|
466
|
+
properties: {
|
|
467
|
+
recordId: {
|
|
468
|
+
type: "string",
|
|
469
|
+
description: "Factory task id (UUID).",
|
|
470
|
+
},
|
|
471
|
+
status: {
|
|
472
|
+
type: "string",
|
|
473
|
+
enum: ["backlog", "queued", "review", "blocked", "paused", "cancelled", "done"],
|
|
474
|
+
description:
|
|
475
|
+
"New status. 'running' is set by claim_factory_task, not here. 'done' is the approval step and is only valid when the task is currently in review.",
|
|
476
|
+
},
|
|
477
|
+
reason: {
|
|
478
|
+
type: "string",
|
|
479
|
+
description: "Required when status is 'blocked'; used as the session summary otherwise.",
|
|
480
|
+
},
|
|
481
|
+
nextAction: {
|
|
482
|
+
type: "string",
|
|
483
|
+
description: "What happens next. Appended to the session summary.",
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
required: ["recordId", "status"],
|
|
487
|
+
},
|
|
488
|
+
},
|
|
365
489
|
];
|
|
366
490
|
}
|