feat(M13): get_worker_settings + worker_heartbeat tools (v0.7.0)
T-519 — pre-flight quota-gate voor de worker-loop. Twee nieuwe MCP-tools: - get_worker_settings (read): retourneert User.min_quota_pct. Worker roept dit elke iteratie aan vóór de quota-probe. - worker_heartbeat (write): worker rapporteert last_quota_pct + last_quota_check_at na een probe. Update ClaudeWorker en emit pg_notify 'worker_heartbeat' op scrum4me_changes-channel zodat NavBar stand-by-badge real-time updatet. requireWriteAccess (demo-blok). Schema-resync: vendor/scrum4me bijgewerkt naar 555ed8f waarmee de M13-velden (User.min_quota_pct, ClaudeWorker.last_quota_pct + last_quota_check_at) beschikbaar zijn voor Prisma client. Bestaande achtergrond-heartbeat (presence/heartbeat.ts, 5s tick op last_seen_at) blijft ongewijzigd. Worker_heartbeat is een aparte expliciete call met quota-info. Versie naar 0.7.0 (minor — twee nieuwe tools). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b48f2a5c74
commit
d50075d960
6 changed files with 192 additions and 8 deletions
|
|
@ -28,6 +28,8 @@ import { registerGetIdeaContextTool } from './tools/get-idea-context.js'
|
|||
import { registerUpdateIdeaGrillMdTool } from './tools/update-idea-grill-md.js'
|
||||
import { registerUpdateIdeaPlanMdTool } from './tools/update-idea-plan-md.js'
|
||||
import { registerLogIdeaDecisionTool } from './tools/log-idea-decision.js'
|
||||
import { registerGetWorkerSettingsTool } from './tools/get-worker-settings.js'
|
||||
import { registerWorkerHeartbeatTool } from './tools/worker-heartbeat.js'
|
||||
import { registerImplementNextStoryPrompt } from './prompts/implement-next-story.js'
|
||||
import { getAuth } from './auth.js'
|
||||
import { registerWorker } from './presence/worker.js'
|
||||
|
|
@ -89,6 +91,9 @@ async function main() {
|
|||
registerUpdateIdeaGrillMdTool(server)
|
||||
registerUpdateIdeaPlanMdTool(server)
|
||||
registerLogIdeaDecisionTool(server)
|
||||
// M13: worker quota-gate tools
|
||||
registerGetWorkerSettingsTool(server)
|
||||
registerWorkerHeartbeatTool(server)
|
||||
registerImplementNextStoryPrompt(server)
|
||||
|
||||
// Presence bootstrap MUST run before server.connect — the stdio transport
|
||||
|
|
|
|||
33
src/tools/get-worker-settings.ts
Normal file
33
src/tools/get-worker-settings.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// MCP read-tool: lees de worker-instellingen van de geauthenticeerde user.
|
||||
//
|
||||
// Worker roept dit aan vóór elke wait_for_job iteratie zodat hij weet
|
||||
// wanneer hij stand-by moet (pre-flight quota-gate).
|
||||
//
|
||||
// Auth: api-token; user_id afgeleid uit token. Demo mag.
|
||||
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import { prisma } from '../prisma.js'
|
||||
import { getAuth } from '../auth.js'
|
||||
import { toolError, toolJson, withToolErrors } from '../errors.js'
|
||||
|
||||
export function registerGetWorkerSettingsTool(server: McpServer) {
|
||||
server.registerTool(
|
||||
'get_worker_settings',
|
||||
{
|
||||
title: 'Get worker settings',
|
||||
description:
|
||||
'Read the authenticated user\'s worker settings (min_quota_pct). Worker should call this each iteration before doing the pre-flight quota probe.',
|
||||
inputSchema: {},
|
||||
},
|
||||
async () =>
|
||||
withToolErrors(async () => {
|
||||
const auth = await getAuth()
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: auth.userId },
|
||||
select: { min_quota_pct: true },
|
||||
})
|
||||
if (!user) return toolError('User not found')
|
||||
return toolJson({ min_quota_pct: user.min_quota_pct })
|
||||
}),
|
||||
)
|
||||
}
|
||||
81
src/tools/worker-heartbeat.ts
Normal file
81
src/tools/worker-heartbeat.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// MCP write-tool: worker rapporteert quota-pct na pre-flight probe.
|
||||
//
|
||||
// Aanvulling op de bestaande achtergrond-heartbeat (die alleen last_seen_at
|
||||
// elke 5s tickt). Deze tool wordt expliciet aangeroepen door de worker
|
||||
// nadat scripts/worker-quota-probe.sh een quota-meting heeft gedaan.
|
||||
//
|
||||
// Updates ClaudeWorker.{last_quota_pct, last_quota_check_at, last_seen_at}
|
||||
// en emit een pg_notify-event op 'scrum4me_changes' zodat de UI de
|
||||
// stand-by-badge real-time kan tonen.
|
||||
//
|
||||
// Auth: api-token; demo mag niet (worker is geen demo-flow).
|
||||
|
||||
import { z } from 'zod'
|
||||
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||
import { Client } from 'pg'
|
||||
import { prisma } from '../prisma.js'
|
||||
import { requireWriteAccess } from '../auth.js'
|
||||
import { toolError, toolJson, withToolErrors } from '../errors.js'
|
||||
|
||||
const inputSchema = z.object({
|
||||
last_quota_pct: z.number().int().min(0).max(100),
|
||||
last_quota_check_at: z.string().datetime().optional(),
|
||||
})
|
||||
|
||||
export function registerWorkerHeartbeatTool(server: McpServer) {
|
||||
server.registerTool(
|
||||
'worker_heartbeat',
|
||||
{
|
||||
title: 'Worker heartbeat with quota',
|
||||
description:
|
||||
'Report the worker\'s most recent rate-limit quota percentage to the server. Updates ClaudeWorker.last_quota_pct + last_quota_check_at. Emits a SSE event so the UI can show stand-by status. Forbidden for demo accounts.',
|
||||
inputSchema,
|
||||
},
|
||||
async ({ last_quota_pct, last_quota_check_at }) =>
|
||||
withToolErrors(async () => {
|
||||
const auth = await requireWriteAccess()
|
||||
const checkAt = last_quota_check_at ? new Date(last_quota_check_at) : new Date()
|
||||
|
||||
const result = await prisma.claudeWorker.updateMany({
|
||||
where: { token_id: auth.tokenId },
|
||||
data: {
|
||||
last_seen_at: new Date(),
|
||||
last_quota_pct,
|
||||
last_quota_check_at: checkAt,
|
||||
},
|
||||
})
|
||||
|
||||
if (result.count === 0) {
|
||||
return toolError(
|
||||
'Worker record not found — call register_worker first or wait for the next heartbeat tick',
|
||||
)
|
||||
}
|
||||
|
||||
// pg_notify zodat NavBar realtime kan updaten. Failure is non-fatal:
|
||||
// de DB-write is al gebeurd, alleen de live-update mist dan.
|
||||
try {
|
||||
const pg = new Client({ connectionString: process.env.DATABASE_URL })
|
||||
await pg.connect()
|
||||
await pg.query('SELECT pg_notify($1, $2)', [
|
||||
'scrum4me_changes',
|
||||
JSON.stringify({
|
||||
type: 'worker_heartbeat',
|
||||
user_id: auth.userId,
|
||||
token_id: auth.tokenId,
|
||||
last_quota_pct,
|
||||
last_quota_check_at: checkAt.toISOString(),
|
||||
}),
|
||||
])
|
||||
await pg.end()
|
||||
} catch {
|
||||
// non-fatal
|
||||
}
|
||||
|
||||
return toolJson({
|
||||
ok: true,
|
||||
last_quota_pct,
|
||||
last_quota_check_at: checkAt.toISOString(),
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue