mirror of
https://github.com/PR0M3TH3AN/TORCH.git
synced 2026-10-28 00:11:54 +00:00
782 lines
40 KiB
JavaScript
782 lines
40 KiB
JavaScript
import { McpServer } from '@modelcontextprotocol/server';
|
||
import { z } from 'zod';
|
||
import { TorchError } from '../kernel/errors.mjs';
|
||
import { planManagerCheckIn } from '../observability/manager-checkin.mjs';
|
||
|
||
const optionalReferenceShape = {
|
||
task: z.string().min(1).optional(),
|
||
path: z.string().min(1).optional(),
|
||
commit: z.string().min(1).optional(),
|
||
handoff: z.string().min(1).optional(),
|
||
};
|
||
|
||
export const TORCH_CORE_MCP_TOOL_NAMES = Object.freeze([
|
||
'torch_identity',
|
||
'torch_list_agents',
|
||
'torch_plan_manager_check_in',
|
||
'torch_get_agent',
|
||
'torch_get_roster',
|
||
'torch_who_owns',
|
||
'torch_neighbours',
|
||
'torch_send_message',
|
||
'torch_read_messages',
|
||
'torch_ack_message',
|
||
'torch_report_status',
|
||
'torch_report_complete',
|
||
'torch_report_blocked',
|
||
'torch_request_approval',
|
||
'torch_list_approvals',
|
||
'torch_decide_approval',
|
||
'torch_request_coordination',
|
||
'torch_request_handoff',
|
||
]);
|
||
|
||
export const TORCH_MCP_TOOL_NAMES = Object.freeze([
|
||
...TORCH_CORE_MCP_TOOL_NAMES,
|
||
'torch_list_backlog',
|
||
'torch_next_backlog_task',
|
||
'torch_backlog_health',
|
||
'torch_backlog_activity',
|
||
'torch_plan_landed_task_closure',
|
||
'torch_reconcile_landed_tasks',
|
||
'torch_plan_backlog_claim',
|
||
'torch_claim_next_backlog_task',
|
||
'torch_get_backlog_task',
|
||
'torch_create_backlog_task',
|
||
'torch_classify_backlog_task',
|
||
'torch_transition_backlog_task',
|
||
'torch_list_checks',
|
||
'torch_list_check_receipts',
|
||
'torch_plan_check',
|
||
'torch_run_check',
|
||
'torch_prepare_check',
|
||
'torch_list_prepared_checks',
|
||
'torch_run_prepared_check',
|
||
'torch_cancel_prepared_check',
|
||
'torch_list_resources',
|
||
'torch_resource_status',
|
||
'torch_acquire_resource',
|
||
'torch_release_resource',
|
||
'torch_cancel_resource_request',
|
||
'torch_list_integrations',
|
||
'torch_request_integration',
|
||
'torch_evaluate_integration',
|
||
'torch_authorize_integration',
|
||
'torch_plan_integration_landing',
|
||
'torch_land_integration',
|
||
'torch_record_context_usage',
|
||
'torch_context_report',
|
||
'torch_list_schedules',
|
||
'torch_schedule_runs',
|
||
'torch_plan_schedule',
|
||
'torch_run_schedule',
|
||
'torch_list_fleet_changes',
|
||
'torch_get_fleet_change',
|
||
'torch_assess_fleet_evolution',
|
||
'torch_propose_domain',
|
||
'torch_propose_domain_retirement',
|
||
'torch_propose_domain_merge',
|
||
'torch_propose_domain_split',
|
||
'torch_assess_hierarchy_needs',
|
||
'torch_list_hierarchy_proposals',
|
||
'torch_get_hierarchy_proposal',
|
||
'torch_propose_hierarchy_change',
|
||
'torch_plan_hierarchy_pilot',
|
||
'torch_publish_artifact',
|
||
]);
|
||
|
||
function claimedIdentity(controlPlane, actorId, claim, field) {
|
||
const actor = actorId ? controlPlane.assertIdentity(actorId) : null;
|
||
const claimed = claim ? controlPlane.assertIdentity(claim) : null;
|
||
if (actor && claimed && actor !== claimed) {
|
||
throw new TorchError(`Bound Fleet identity ${actor} cannot act as ${claimed} in ${field}`, {
|
||
code: 'FLEET_IDENTITY_MISMATCH', details: { actor, claimed, field },
|
||
});
|
||
}
|
||
if (actor) return actor;
|
||
if (claimed) return claimed;
|
||
throw new TorchError(`${field} is required when the MCP server has no bound Fleet identity`, {
|
||
code: 'FLEET_IDENTITY_REQUIRED', details: { field },
|
||
});
|
||
}
|
||
|
||
export function createTorchToolset(controlPlane, {
|
||
actorId, backlogService, checkService, resourceService, integrationService, contextTelemetryService, scheduleService,
|
||
evolutionService, hierarchyService, repositoryRoot,
|
||
artifactService,
|
||
} = {}) {
|
||
const tools = new Map([
|
||
['torch_identity', {
|
||
description: 'Return the stable TORCH Fleet identity and current runtime presence for one area.',
|
||
schema: { area_id: z.string().min(1).optional() },
|
||
invoke: ({ area_id: areaId }) => controlPlane.identity(claimedIdentity(controlPlane, actorId, areaId, 'area_id')),
|
||
}],
|
||
['torch_list_agents', {
|
||
description: 'List all persistent Fleet identities and their live presence.',
|
||
schema: {},
|
||
invoke: () => ({ agents: controlPlane.listAgents() }),
|
||
}],
|
||
['torch_plan_manager_check_in', {
|
||
description: 'Refresh the bound manager identity’s direct-report presence, durable messages, and named approval waits from current state. Read-only; only works for an identity with direct reports.',
|
||
schema: { stale_work_review: z.boolean().optional(),
|
||
stale_days: z.number().int().min(1).max(365).optional(),
|
||
max_commits: z.number().int().min(1).max(10_000).optional(),
|
||
max_items: z.number().int().min(1).max(100).optional() },
|
||
invoke: ({ stale_work_review: review, stale_days, max_commits, max_items }) => {
|
||
const managerId = controlPlane.assertIdentity(actorId);
|
||
if (!repositoryRoot) {
|
||
throw new TorchError('A repository root is required to refresh manager check-in state', {
|
||
code: 'MANAGER_CHECK_IN_CONTEXT_MISSING',
|
||
});
|
||
}
|
||
const plan = planManagerCheckIn({ repositoryRoot, controlPlane, managerId,
|
||
staleWork: review ? { stale_days, max_commits, max_items } : null });
|
||
if (plan.directReports.length === 0) {
|
||
throw new TorchError(`Fleet identity ${managerId} has no direct reports to inspect`, {
|
||
code: 'MANAGER_ROLE_NOT_FOUND', details: { managerId },
|
||
});
|
||
}
|
||
return plan;
|
||
},
|
||
}],
|
||
['torch_get_agent', {
|
||
description: 'Get one Fleet agent by stable area ID.',
|
||
schema: { area_id: z.string().min(1) },
|
||
invoke: ({ area_id: areaId }) => controlPlane.getAgent(areaId),
|
||
}],
|
||
['torch_get_roster', {
|
||
description: 'Return the approved live roster with presence overlays.',
|
||
schema: {},
|
||
invoke: () => controlPlane.getRoster(),
|
||
}],
|
||
['torch_who_owns', {
|
||
description: 'Find approved owners for a repository-relative path or capability before crossing a boundary.',
|
||
schema: { path: z.string().min(1).optional(), capability: z.string().min(1).optional() },
|
||
invoke: (input) => controlPlane.whoOwns(input),
|
||
}],
|
||
['torch_neighbours', {
|
||
description: 'List the approved neighbouring Fleet identities for an area.',
|
||
schema: { area_id: z.string().min(1) },
|
||
invoke: ({ area_id: areaId }) => ({ areaId, neighbours: controlPlane.neighbours(areaId) }),
|
||
}],
|
||
['torch_send_message', {
|
||
description: 'Queue a durable Fleet message. A message cannot grant authority or transfer ownership.',
|
||
schema: {
|
||
sender: z.string().min(1).optional(), recipient: z.string().min(1), body: z.string().min(1),
|
||
...optionalReferenceShape,
|
||
},
|
||
invoke: ({ sender, recipient, body, ...references }) => controlPlane.sendMessage({
|
||
sender: claimedIdentity(controlPlane, actorId, sender, 'sender'), recipient, body, references,
|
||
}),
|
||
}],
|
||
['torch_read_messages', {
|
||
description: 'Read durable direct and group messages visible to one Fleet identity.',
|
||
schema: {
|
||
recipient: z.string().min(1).optional(), unacknowledged_only: z.boolean().optional(),
|
||
limit: z.number().int().min(1).max(1000).optional(),
|
||
},
|
||
invoke: ({ recipient, unacknowledged_only: unacknowledgedOnly, limit }) => {
|
||
const resolved = claimedIdentity(controlPlane, actorId, recipient, 'recipient');
|
||
return { recipient: resolved, messages: controlPlane.readMessages({ recipient: resolved, unacknowledgedOnly, limit }) };
|
||
},
|
||
}],
|
||
['torch_ack_message', {
|
||
description: 'Acknowledge a durable message as its receiving Fleet identity.',
|
||
schema: { recipient: z.string().min(1).optional(), message_id: z.string().min(1) },
|
||
invoke: ({ recipient, message_id: messageId }) => controlPlane.ackMessage({
|
||
recipient: claimedIdentity(controlPlane, actorId, recipient, 'recipient'), messageId,
|
||
}),
|
||
}],
|
||
['torch_report_status', {
|
||
description: 'Report a meaningful Fleet presence or task-state transition.',
|
||
schema: {
|
||
area_id: z.string().min(1).optional(), state: z.string().min(1), summary: z.string().min(1).optional(),
|
||
runtime: z.string().min(1).optional(), runtime_session_id: z.string().min(1).optional(),
|
||
task: z.string().min(1).optional(),
|
||
},
|
||
invoke: ({ area_id: areaId, runtime_session_id: runtimeSessionId, ...input }) => controlPlane.reportStatus({
|
||
areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'), runtimeSessionId, ...input,
|
||
}),
|
||
}],
|
||
['torch_report_complete', {
|
||
description: 'Record completion evidence and notify the Session Manager durably.',
|
||
schema: {
|
||
area_id: z.string().min(1).optional(), summary: z.string().min(1), task: z.string().min(1).optional(),
|
||
evidence: z.string().min(1).optional(), commit: z.string().min(1).optional(),
|
||
},
|
||
invoke: ({ area_id: areaId, ...input }) => controlPlane.reportComplete({
|
||
areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'), ...input,
|
||
}),
|
||
}],
|
||
['torch_report_blocked', {
|
||
description: 'Record a blocker and notify the Session Manager durably.',
|
||
schema: {
|
||
area_id: z.string().min(1).optional(), summary: z.string().min(1), task: z.string().min(1).optional(),
|
||
evidence: z.string().min(1).optional(), path: z.string().min(1).optional(),
|
||
commit: z.string().min(1).optional(),
|
||
},
|
||
invoke: ({ area_id: areaId, ...input }) => controlPlane.reportBlocked({
|
||
areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'), ...input,
|
||
}),
|
||
}],
|
||
['torch_request_approval', {
|
||
description: 'Create a durable approval request addressed to one named Fleet identity or the configured owner.',
|
||
schema: {
|
||
requester: z.string().min(1).optional(), approver: z.string().min(1), task: z.string().min(1).optional(),
|
||
title: z.string().min(1).max(240), summary: z.string().min(1).max(4000), evidence: z.string().min(1).max(4000).optional(),
|
||
},
|
||
invoke: ({ requester, ...input }) => controlPlane.requestApproval({
|
||
...input, requester: claimedIdentity(controlPlane, actorId, requester, 'requester'),
|
||
}),
|
||
}],
|
||
['torch_list_approvals', {
|
||
description: 'List pending and historical approval requests visible to the bound Fleet identity.',
|
||
schema: { actor_id: z.string().min(1).optional(), status: z.enum(['pending', 'approved', 'rejected', 'cancelled']).optional() },
|
||
invoke: ({ actor_id: actorIdClaim, status }) => ({ approvals: controlPlane.listApprovals({
|
||
actorId: claimedIdentity(controlPlane, actorId, actorIdClaim, 'actor_id'), status,
|
||
}) }),
|
||
}],
|
||
['torch_decide_approval', {
|
||
description: 'Approve or reject a request only when the bound identity is its named approver; decisions are audited and notified durably.',
|
||
schema: {
|
||
actor_id: z.string().min(1).optional(), approval_id: z.string().min(1),
|
||
decision: z.enum(['approved', 'rejected']), note: z.string().max(2000).optional(),
|
||
expected_revision: z.number().int().positive(),
|
||
},
|
||
invoke: ({ actor_id: actorIdClaim, approval_id: approvalId, decision, note, expected_revision: expectedRevision }) =>
|
||
controlPlane.decideApproval({
|
||
approvalId, decidedBy: claimedIdentity(controlPlane, actorId, actorIdClaim, 'actor_id'),
|
||
decision, note, expectedRevision,
|
||
}),
|
||
}],
|
||
['torch_request_coordination', {
|
||
description: 'Ask the Session Manager for a durable cross-domain coordination ruling.',
|
||
schema: {
|
||
sender: z.string().min(1).optional(), participants: z.array(z.string().min(1)).optional(),
|
||
body: z.string().min(1), task: z.string().min(1).optional(), path: z.string().min(1).optional(),
|
||
},
|
||
invoke: ({ sender, ...input }) => controlPlane.requestCoordination({
|
||
sender: claimedIdentity(controlPlane, actorId, sender, 'sender'), ...input,
|
||
}),
|
||
}],
|
||
['torch_request_handoff', {
|
||
description: 'Request, but do not perform, a task or path ownership handoff.',
|
||
schema: {
|
||
sender: z.string().min(1).optional(), recipient: z.string().min(1).optional(),
|
||
path: z.string().min(1).optional(), task: z.string().min(1).optional(), reason: z.string().min(1),
|
||
},
|
||
invoke: ({ sender, ...input }) => controlPlane.requestHandoff({
|
||
sender: claimedIdentity(controlPlane, actorId, sender, 'sender'), ...input,
|
||
}),
|
||
}],
|
||
]);
|
||
if (backlogService) {
|
||
tools.set('torch_list_backlog', {
|
||
description: 'List durable tracked backlog tasks, optionally filtered by state or owner.',
|
||
schema: { state: z.string().min(1).optional(), owner: z.string().min(1).optional() },
|
||
invoke: (input) => ({ tasks: backlogService.list(input) }),
|
||
});
|
||
tools.set('torch_next_backlog_task', {
|
||
description: 'Resume this specialist\'s active assignment before returning its first eligible ready task. Read-only.',
|
||
schema: { area_id: z.string().min(1).optional() },
|
||
invoke: ({ area_id: requestedArea }) => {
|
||
const bound = controlPlane.assertIdentity(actorId);
|
||
const areaId = requestedArea ?? bound;
|
||
if (bound !== 'session-manager' && areaId !== bound) {
|
||
throw new TorchError('A specialist may only inspect its own next backlog item', {
|
||
code: 'BACKLOG_AUTHORITY_REQUIRED', details: { actorId: bound, areaId },
|
||
});
|
||
}
|
||
return backlogService.next({ areaId });
|
||
},
|
||
});
|
||
tools.set('torch_backlog_health', {
|
||
description: 'Report unusual backlog conditions without changing any task.',
|
||
schema: {
|
||
stale_after_days: z.number().int().min(0).optional(),
|
||
stale_observed_commits: z.number().int().min(0).optional(),
|
||
},
|
||
invoke: ({ stale_after_days: staleAfterDays, stale_observed_commits: staleObservedCommits }) =>
|
||
backlogService.health({ staleAfterDays, staleObservedCommits }),
|
||
});
|
||
tools.set('torch_backlog_activity', {
|
||
description: 'Observe commit-linked task activity and neglected requests on managed branches. Read-only; closure text is intent, not authority.',
|
||
schema: { stale_days: z.number().int().min(1).max(365).optional(), max_commits: z.number().int().min(1).max(10_000).optional() },
|
||
invoke: ({ stale_days: staleDays, max_commits: maxCommits }) => backlogService.activity({ staleDays, maxCommits }),
|
||
});
|
||
for (const [name, method] of [
|
||
['torch_plan_landed_task_closure', 'planLandedClosure'], ['torch_reconcile_landed_tasks', 'reconcileLanded'],
|
||
]) tools.set(name, {
|
||
description: 'Manager-only exact landed task closure: preview or explicitly reconcile Closes intent with current evidence.',
|
||
schema: { integration_request: z.string().min(1), approved: z.boolean().optional() },
|
||
invoke: ({ integration_request: integrationRequest, approved }) => backlogService[method]({ integrationRequest, actorId, approved }),
|
||
});
|
||
for (const [name, method] of [
|
||
['torch_plan_backlog_claim', 'planClaim'], ['torch_claim_next_backlog_task', 'claimNext'],
|
||
]) tools.set(name, {
|
||
description: method === 'planClaim'
|
||
? 'Preview policy-controlled self-claim eligibility for the bound specialist without mutation.'
|
||
: 'Atomically claim the next explicitly routed ready task under approved policy, or resume current work. Never grants manager authority.',
|
||
schema: { area_id: z.string().min(1).optional() },
|
||
invoke: ({ area_id: areaId }) => backlogService[method]({
|
||
areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_get_backlog_task', {
|
||
description: 'Read one durable tracked backlog task and its transition history.',
|
||
schema: { task_id: z.string().min(1) },
|
||
invoke: ({ task_id: taskId }) => backlogService.get(taskId),
|
||
});
|
||
tools.set('torch_create_backlog_task', {
|
||
description: 'Create proposed work as the bound Session Manager identity.',
|
||
schema: {
|
||
actor_id: z.string().min(1).optional(), title: z.string().min(1), description: z.string().min(1),
|
||
priority: z.enum(['urgent', 'high', 'normal', 'low']).optional(),
|
||
affected_domains: z.array(z.string().min(1)).optional(),
|
||
dependencies: z.array(z.string().min(1)).optional(),
|
||
feature: z.string().min(1).max(120).optional(), milestone: z.string().min(1).max(120).optional(),
|
||
acceptance_criteria: z.array(z.string().min(1)).min(1),
|
||
observed_at: z.string().min(1).optional(),
|
||
},
|
||
invoke: ({
|
||
actor_id: actorIdClaim, affected_domains: affectedDomains,
|
||
acceptance_criteria: acceptanceCriteria, observed_at: observedAt, ...input
|
||
}) => backlogService.create({
|
||
...input, affectedDomains, acceptanceCriteria, observedAt,
|
||
actorId: claimedIdentity(controlPlane, actorId, actorIdClaim, 'actor_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_classify_backlog_task', {
|
||
description: 'Set or explicitly clear a task\'s feature and milestone labels as the Session Manager. Revision-checked and non-destructive to status or ownership.',
|
||
schema: {
|
||
task_id: z.string().min(1), actor_id: z.string().min(1).optional(),
|
||
expected_revision: z.number().int().min(1),
|
||
feature: z.string().min(1).max(120).optional(), milestone: z.string().min(1).max(120).optional(),
|
||
clear_feature: z.boolean().optional(), clear_milestone: z.boolean().optional(),
|
||
reason: z.string().min(1),
|
||
},
|
||
invoke: ({
|
||
task_id: taskId, actor_id: actorIdClaim, expected_revision: expectedRevision,
|
||
clear_feature: clearFeature, clear_milestone: clearMilestone, ...input
|
||
}) => backlogService.classify({
|
||
...input, taskId, expectedRevision, clearFeature, clearMilestone,
|
||
actorId: claimedIdentity(controlPlane, actorId, actorIdClaim, 'actor_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_transition_backlog_task', {
|
||
description: 'Apply one revision-checked backlog state transition under bound Fleet authority.',
|
||
schema: {
|
||
task_id: z.string().min(1), actor_id: z.string().min(1).optional(), to: z.string().min(1),
|
||
expected_revision: z.number().int().min(1), owner: z.string().min(1).optional(),
|
||
evidence: z.array(z.string().min(1)).optional(), commit: z.string().min(1).optional(),
|
||
integration_request: z.string().min(1).optional(), blocked_reason: z.string().min(1).optional(),
|
||
note: z.string().min(1).optional(),
|
||
},
|
||
invoke: ({
|
||
task_id: taskId, actor_id: actorIdClaim, expected_revision: expectedRevision,
|
||
integration_request: integrationRequest, blocked_reason: blockedReason, ...input
|
||
}) => backlogService.transition({
|
||
...input, taskId, expectedRevision, integrationRequest, blockedReason,
|
||
actorId: claimedIdentity(controlPlane, actorId, actorIdClaim, 'actor_id'),
|
||
}),
|
||
});
|
||
}
|
||
if (checkService) {
|
||
tools.set('torch_list_checks', {
|
||
description: 'List approved project check definitions.', schema: {},
|
||
invoke: () => ({ checks: checkService.listChecks() }),
|
||
});
|
||
tools.set('torch_list_check_receipts', {
|
||
description: 'List immutable check receipts, optionally filtered by exact commit, check, or bound area.',
|
||
schema: {
|
||
commit: z.string().min(1).optional(), check_id: z.string().min(1).optional(),
|
||
area_id: z.string().min(1).optional(),
|
||
},
|
||
invoke: ({ commit, check_id: checkId, area_id: areaId }) => ({
|
||
receipts: checkService.receipts({
|
||
commit, checkId,
|
||
areaId: areaId ? claimedIdentity(controlPlane, actorId, areaId, 'area_id') : undefined,
|
||
}),
|
||
}),
|
||
});
|
||
tools.set('torch_plan_check', {
|
||
description: 'Preview a configured check against the bound area and report blockers without executing it.',
|
||
schema: { check_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ check_id: checkId, area_id: areaId }) => checkService.plan({
|
||
checkId, areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_run_check', {
|
||
description: 'Execute an approved shell-free check in the bound clean worktree and record an exact-SHA receipt.',
|
||
schema: { check_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ check_id: checkId, area_id: areaId }) => checkService.run({
|
||
checkId, areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_prepare_check', {
|
||
description: 'Capture approved frozen check inputs before waiting for resource leases; does not execute the check.',
|
||
schema: { check_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ check_id: checkId, area_id: areaId }) => checkService.prepare({
|
||
checkId, areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_list_prepared_checks', {
|
||
description: 'List durable frozen checks for the bound identity, including interrupted running checks.',
|
||
schema: { area_id: z.string().min(1).optional() },
|
||
invoke: ({ area_id: areaId }) => ({ prepared: checkService.prepared({
|
||
areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}) }),
|
||
});
|
||
for (const [name, method] of [
|
||
['torch_run_prepared_check', 'runPrepared'], ['torch_cancel_prepared_check', 'cancelPrepared'],
|
||
]) tools.set(name, {
|
||
description: method === 'runPrepared'
|
||
? 'Run an owned frozen check after acquiring its resources; record its captured SHA, not the current worktree SHA.'
|
||
: 'Cancel an owned unstarted frozen check and remove only its owned input copy.',
|
||
schema: { prepared_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ prepared_id: preparedId, area_id: areaId }) => checkService[method]({
|
||
preparedId, areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
}
|
||
if (resourceService) {
|
||
tools.set('torch_list_resources', {
|
||
description: 'List configured scarce resources and capacities.', schema: {},
|
||
invoke: () => ({ resources: resourceService.list() }),
|
||
});
|
||
tools.set('torch_resource_status', {
|
||
description: 'Show holders, stale leases, and the FIFO queue for a resource.',
|
||
schema: { resource_id: z.string().min(1) },
|
||
invoke: ({ resource_id: resourceId }) => resourceService.status(resourceId),
|
||
});
|
||
tools.set('torch_acquire_resource', {
|
||
description: 'Acquire or join the FIFO queue for a scarce resource as the bound Fleet identity.',
|
||
schema: { resource_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ resource_id: resourceId, area_id: areaId }) => resourceService.acquire({
|
||
resourceId, areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_release_resource', {
|
||
description: 'Release a resource lease held by the bound Fleet identity.',
|
||
schema: { resource_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ resource_id: resourceId, area_id: areaId }) => resourceService.release({
|
||
resourceId, areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_cancel_resource_request', {
|
||
description: 'Cancel the bound Fleet identity queued request without leaking a lease.',
|
||
schema: { resource_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ resource_id: resourceId, area_id: areaId }) => resourceService.cancel({
|
||
resourceId, areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
}
|
||
if (integrationService) {
|
||
tools.set('torch_list_integrations', {
|
||
description: 'List native integration queue records and their exact readiness states.',
|
||
schema: { state: z.string().min(1).optional() },
|
||
invoke: ({ state }) => ({ requests: integrationService.list({ state }) }),
|
||
});
|
||
tools.set('torch_request_integration', {
|
||
description: 'Request integration of the bound domain branch tip and its exact commit receipts.',
|
||
schema: { area_id: z.string().min(1).optional(), commit: z.string().min(1).optional() },
|
||
invoke: ({ area_id: areaId, commit }) => integrationService.request({
|
||
areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'), commit,
|
||
}),
|
||
});
|
||
tools.set('torch_evaluate_integration', {
|
||
description: 'Re-evaluate source, convergence, and exact-SHA checks for an integration request.',
|
||
schema: { request_id: z.string().min(1) },
|
||
invoke: ({ request_id: requestId }) => integrationService.evaluate(requestId),
|
||
});
|
||
tools.set('torch_authorize_integration', {
|
||
description: 'Authorize a ready integration request as the bound identity when policy grants authority.',
|
||
schema: { request_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ request_id: requestId, area_id: areaId }) => integrationService.authorize({
|
||
requestId, actorId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_plan_integration_landing', {
|
||
description: 'Preview all main-protection gates without changing canonical main.',
|
||
schema: { request_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ request_id: requestId, area_id: areaId }) => integrationService.planLanding({
|
||
requestId, actorId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_land_integration', {
|
||
description: 'Fast-forward canonical main only after durable authorization and every protection gate passes.',
|
||
schema: { request_id: z.string().min(1), area_id: z.string().min(1).optional() },
|
||
invoke: ({ request_id: requestId, area_id: areaId }) => integrationService.land({
|
||
requestId, actorId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
}
|
||
if (contextTelemetryService) {
|
||
tools.set('torch_record_context_usage', {
|
||
description: 'Record measured or explicitly estimated context usage for the bound Fleet identity and its verified outcomes.',
|
||
schema: {
|
||
area_id: z.string().min(1).optional(), source: z.string().min(1), measurement: z.enum(['measured', 'estimated']),
|
||
runtime: z.string().min(1).optional(), runtime_session_id: z.string().min(1).optional(),
|
||
task: z.string().min(1).optional(), commit: z.string().min(1).optional(),
|
||
cached_input: z.number().int().nonnegative().optional(), uncached_input: z.number().int().nonnegative().optional(),
|
||
cache_creation: z.number().int().nonnegative().optional(), cache_read: z.number().int().nonnegative().optional(),
|
||
compactions: z.number().int().nonnegative().optional(), resumed_prompt_bytes: z.number().int().nonnegative().optional(),
|
||
cost_microusd: z.number().int().nonnegative().optional(), verified_items: z.number().int().nonnegative().optional(),
|
||
},
|
||
invoke: ({
|
||
area_id: areaId, runtime_session_id: runtimeSessionId, cached_input: cachedInput,
|
||
uncached_input: uncachedInput, cache_creation: cacheCreation, cache_read: cacheRead,
|
||
resumed_prompt_bytes: resumedPromptBytes, cost_microusd: costMicrousd,
|
||
verified_items: verifiedItems, ...input
|
||
}) => contextTelemetryService.record({
|
||
...input, runtimeSessionId, cachedInput, uncachedInput, cacheCreation, cacheRead,
|
||
resumedPromptBytes, costMicrousd, verifiedItems,
|
||
areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'),
|
||
}),
|
||
});
|
||
tools.set('torch_context_report', {
|
||
description: 'Aggregate context locality by Fleet identity while keeping measured and estimated evidence separate.',
|
||
schema: {
|
||
area_id: z.string().min(1).optional(), task: z.string().min(1).optional(),
|
||
commit: z.string().min(1).optional(), measurement: z.enum(['measured', 'estimated']).optional(),
|
||
},
|
||
invoke: ({ area_id: areaId, ...input }) => contextTelemetryService.report({
|
||
...input, areaId: areaId ? claimedIdentity(controlPlane, actorId, areaId, 'area_id') : actorId,
|
||
}),
|
||
});
|
||
}
|
||
if (scheduleService) {
|
||
tools.set('torch_list_schedules', {
|
||
description: 'List validated schedules and their current authority, lifetime, and due status.',
|
||
schema: {}, invoke: () => ({ schedules: scheduleService.list({ actorId }) }),
|
||
});
|
||
tools.set('torch_schedule_runs', {
|
||
description: 'List durable run evidence for configured schedules.',
|
||
schema: { schedule_id: z.string().min(1).optional(), limit: z.number().int().min(1).max(1000).optional() },
|
||
invoke: ({ schedule_id: scheduleId, limit }) => ({ runs: scheduleService.runs({ scheduleId, limit }) }),
|
||
});
|
||
tools.set('torch_plan_schedule', {
|
||
description: 'Preview a configured schedule and its blockers without executing it.',
|
||
schema: { schedule_id: z.string().min(1) },
|
||
invoke: ({ schedule_id: scheduleId }) => scheduleService.plan({ scheduleId, actorId }),
|
||
});
|
||
tools.set('torch_run_schedule', {
|
||
description: 'Run a read-only configured schedule as the bound Fleet identity. Coordination and mutating schedules cannot run through this MCP tool; they require an owner-approved CLI action or an approved, digest-matched system timer.',
|
||
schema: { schedule_id: z.string().min(1) },
|
||
invoke: ({ schedule_id: scheduleId }) => scheduleService.run({ scheduleId, actorId, approved: false }),
|
||
});
|
||
}
|
||
if (evolutionService) {
|
||
tools.set('torch_list_fleet_changes', {
|
||
description: 'List durable proposed, approved, provisioning, or active Fleet organization changes.',
|
||
schema: { state: z.enum(['proposed', 'approved', 'provisioning', 'active', 'retired', 'rejected']).optional() },
|
||
invoke: ({ state }) => ({ changes: evolutionService.list({ state }) }),
|
||
});
|
||
tools.set('torch_get_fleet_change', {
|
||
description: 'Read one durable Fleet organization change and its owner-approval state.',
|
||
schema: { change_id: z.string().min(1) },
|
||
invoke: ({ change_id: changeId }) => evolutionService.get(changeId),
|
||
});
|
||
tools.set('torch_assess_fleet_evolution', {
|
||
description: 'Inspect durable backlog, handoff, coordination, and pending-change evidence for recurring boundaries that may merit a new persistent domain. Advisory and read-only.',
|
||
schema: { assessor: z.string().min(1).optional() },
|
||
invoke: ({ assessor }) => evolutionService.assessDomainNeeds({
|
||
assessor: claimedIdentity(controlPlane, actorId, assessor, 'assessor'),
|
||
}),
|
||
});
|
||
tools.set('torch_propose_domain', {
|
||
description: 'Propose an evidence-backed persistent domain as the bound Session Manager. This never approves or activates it.',
|
||
schema: {
|
||
proposer: z.string().min(1).optional(),
|
||
domain: z.object({
|
||
id: z.string().min(1), title: z.string().min(1), kind: z.string().min(1).optional(),
|
||
scope: z.array(z.string().min(1)).min(1), not_scope: z.array(z.string().min(1)).optional(),
|
||
owned_paths: z.array(z.string().min(1)).min(1), shared_paths: z.array(z.string().min(1)).optional(),
|
||
neighbours: z.array(z.string().min(1)).optional(), required_checks: z.array(z.string().min(1)).optional(),
|
||
resources: z.array(z.string().min(1)).optional(),
|
||
runtime: z.string().min(1).optional(), branch: z.string().min(1).optional(),
|
||
model: z.string().min(1).optional(),
|
||
worktree_name: z.string().min(1).optional(),
|
||
}),
|
||
rationale: z.string().min(1),
|
||
expected_benefit: z.object({
|
||
summary: z.string().min(1), recurring_work: z.string().min(1),
|
||
context_locality: z.string().min(1), coordination_cost: z.string().min(1),
|
||
}),
|
||
evidence: z.array(z.string().min(1)).min(1),
|
||
},
|
||
invoke: ({ proposer, expected_benefit: benefit, ...input }) => evolutionService.proposeDomain({
|
||
...input,
|
||
proposer: claimedIdentity(controlPlane, actorId, proposer, 'proposer'),
|
||
expectedBenefit: {
|
||
summary: benefit.summary, recurringWork: benefit.recurring_work,
|
||
contextLocality: benefit.context_locality, coordinationCost: benefit.coordination_cost,
|
||
},
|
||
}),
|
||
});
|
||
tools.set('torch_propose_domain_retirement', {
|
||
description: 'Propose retiring a persistent domain when its coordination cost exceeds its continuing benefit. This never approves or activates retirement.',
|
||
schema: {
|
||
proposer: z.string().min(1).optional(), area_id: z.string().min(1), rationale: z.string().min(1),
|
||
expected_benefit: z.object({
|
||
summary: z.string().min(1), recurring_work: z.string().min(1),
|
||
context_locality: z.string().min(1), coordination_cost: z.string().min(1),
|
||
}),
|
||
evidence: z.array(z.string().min(1)).min(1),
|
||
},
|
||
invoke: ({ proposer, area_id: areaId, expected_benefit: benefit, ...input }) => evolutionService.proposeRetirement({
|
||
...input, areaId,
|
||
proposer: claimedIdentity(controlPlane, actorId, proposer, 'proposer'),
|
||
expectedBenefit: {
|
||
summary: benefit.summary, recurringWork: benefit.recurring_work,
|
||
contextLocality: benefit.context_locality, coordinationCost: benefit.coordination_cost,
|
||
},
|
||
}),
|
||
});
|
||
const boundaryProposalSchema = {
|
||
proposer: z.string().min(1).optional(),
|
||
source_domains: z.array(z.string().min(1)).min(1),
|
||
result_domains: z.array(z.object({
|
||
id: z.string().min(1), title: z.string().min(1), kind: z.string().min(1).optional(),
|
||
scope: z.array(z.string().min(1)).min(1), not_scope: z.array(z.string().min(1)).optional(),
|
||
owned_paths: z.array(z.string().min(1)).min(1), shared_paths: z.array(z.string().min(1)).optional(),
|
||
neighbours: z.array(z.string().min(1)).optional(), required_checks: z.array(z.string().min(1)).optional(),
|
||
resources: z.array(z.string().min(1)).optional(), runtime: z.string().min(1).optional(),
|
||
model: z.string().min(1).optional(), branch: z.string().min(1).optional(),
|
||
worktree_name: z.string().min(1).optional(),
|
||
})).min(1),
|
||
ownership_assignments: z.array(z.object({
|
||
source_domain_id: z.string().min(1), source_path: z.string().min(1), result_domain_id: z.string().min(1),
|
||
})).min(1),
|
||
rationale: z.string().min(1),
|
||
expected_benefit: z.object({
|
||
summary: z.string().min(1), recurring_work: z.string().min(1),
|
||
context_locality: z.string().min(1), coordination_cost: z.string().min(1),
|
||
}),
|
||
evidence: z.array(z.string().min(1)).min(1),
|
||
};
|
||
const invokeBoundaryProposal = (operation) => ({
|
||
proposer, source_domains: sourceDomains, result_domains: resultDomains,
|
||
ownership_assignments: ownershipAssignments, expected_benefit: benefit, ...input
|
||
}) => evolutionService[operation]({
|
||
...input, sourceDomains, resultDomains, ownershipAssignments,
|
||
proposer: claimedIdentity(controlPlane, actorId, proposer, 'proposer'),
|
||
expectedBenefit: {
|
||
summary: benefit.summary, recurringWork: benefit.recurring_work,
|
||
contextLocality: benefit.context_locality, coordinationCost: benefit.coordination_cost,
|
||
},
|
||
});
|
||
tools.set('torch_propose_domain_merge', {
|
||
description: 'Propose an evidence-backed merge of persistent domains. Approval records intent but cannot automatically migrate ownership.',
|
||
schema: boundaryProposalSchema,
|
||
invoke: invokeBoundaryProposal('proposeMerge'),
|
||
});
|
||
tools.set('torch_propose_domain_split', {
|
||
description: 'Propose an evidence-backed split of a persistent domain. Approval records intent but cannot automatically migrate ownership.',
|
||
schema: boundaryProposalSchema,
|
||
invoke: invokeBoundaryProposal('proposeSplit'),
|
||
});
|
||
}
|
||
if (hierarchyService) {
|
||
tools.set('torch_assess_hierarchy_needs', {
|
||
description: 'Measure recurring coordination, cross-domain work, handoffs, and approval waits in the configured observation window. Read-only; it never creates or activates a role.',
|
||
schema: { assessor: z.string().min(1).optional() },
|
||
invoke: ({ assessor }) => hierarchyService.assess({
|
||
assessor: claimedIdentity(controlPlane, actorId, assessor, 'assessor'),
|
||
}),
|
||
});
|
||
tools.set('torch_list_hierarchy_proposals', {
|
||
description: 'List durable hierarchy proposals and their owner-review state.',
|
||
schema: { state: z.enum(['proposed', 'pilot-approved', 'deferred', 'rejected', 'piloting', 'adopted', 'reversed']).optional() },
|
||
invoke: ({ state }) => ({ proposals: hierarchyService.list({ state }) }),
|
||
});
|
||
tools.set('torch_get_hierarchy_proposal', {
|
||
description: 'Read a durable hierarchy proposal, evidence, pilot criteria, and owner decision.',
|
||
schema: { proposal_id: z.string().min(1) },
|
||
invoke: ({ proposal_id: proposalId }) => hierarchyService.get(proposalId),
|
||
});
|
||
tools.set('torch_propose_hierarchy_change', {
|
||
description: 'Submit a sustained-evidence, owner-review hierarchy proposal. This cannot change active roles or start sessions.',
|
||
schema: {
|
||
proposer: z.string().min(1).optional(),
|
||
proposal: z.record(z.string(), z.unknown()),
|
||
},
|
||
invoke: ({ proposer, proposal }) => hierarchyService.propose({
|
||
proposer: claimedIdentity(controlPlane, actorId, proposer, 'proposer'), proposal,
|
||
}),
|
||
});
|
||
tools.set('torch_plan_hierarchy_pilot', {
|
||
description: 'Preview hierarchy differences, affected work, and blockers for an owner-approved bounded pilot. Read-only.',
|
||
schema: { proposal_id: z.string().min(1) },
|
||
invoke: ({ proposal_id: proposalId }) => hierarchyService.planPilot(proposalId),
|
||
});
|
||
}
|
||
if (artifactService) {
|
||
tools.set('torch_publish_artifact', {
|
||
description: 'Publish an image for an owned backlog task with exact identity, session, and commit provenance. The image is copied into private local TORCH state.',
|
||
schema: {
|
||
area_id: z.string().min(1).optional(), task_id: z.string().regex(/^TASK-[A-Za-z0-9-]+$/),
|
||
title: z.string().min(1), alt: z.string().min(1), file: z.string().min(1), commit: z.string().regex(/^[a-f0-9]{40}$/i),
|
||
},
|
||
invoke: ({ area_id: areaId, task_id: taskId, ...input }) => artifactService.publish({
|
||
areaId: claimedIdentity(controlPlane, actorId, areaId, 'area_id'), taskId, ...input,
|
||
}),
|
||
});
|
||
}
|
||
return tools;
|
||
}
|
||
|
||
export function describeTorchTools() {
|
||
const placeholder = Object.freeze({});
|
||
return [...createTorchToolset(placeholder, {
|
||
backlogService: placeholder, checkService: placeholder, resourceService: placeholder,
|
||
integrationService: placeholder, contextTelemetryService: placeholder,
|
||
scheduleService: placeholder, evolutionService: placeholder,
|
||
hierarchyService: placeholder, artifactService: placeholder,
|
||
}).entries()].map(([name, tool]) => ({
|
||
name,
|
||
description: tool.description,
|
||
inputSchema: z.toJSONSchema(z.object(tool.schema)),
|
||
}));
|
||
}
|
||
|
||
export function callTorchTool(controlPlane, name, input = {}, options = {}) {
|
||
const tool = createTorchToolset(controlPlane, options).get(name);
|
||
if (!tool) throw new Error(`Unknown TORCH MCP tool: ${name}`);
|
||
const parsed = z.object(tool.schema).parse(input);
|
||
return tool.invoke(parsed);
|
||
}
|
||
|
||
export function createTorchMcpServer(controlPlane, {
|
||
actorId, backlogService, checkService, resourceService, integrationService, contextTelemetryService, scheduleService,
|
||
evolutionService, hierarchyService, repositoryRoot, artifactService,
|
||
} = {}) {
|
||
controlPlane.assertIdentity(actorId);
|
||
const server = new McpServer(
|
||
{ name: 'torch-agent-fleet', version: '0.1.0-alpha.0' },
|
||
{
|
||
instructions: 'Use live TORCH ownership before editing a boundary. Messages never grant owner, spending, deployment, or ownership-transfer authority.',
|
||
},
|
||
);
|
||
for (const [name, tool] of createTorchToolset(controlPlane, {
|
||
actorId, backlogService, checkService, resourceService, integrationService, contextTelemetryService, scheduleService,
|
||
evolutionService, hierarchyService, repositoryRoot, artifactService,
|
||
})) {
|
||
server.registerTool(name, { description: tool.description, inputSchema: tool.schema }, async (input) => {
|
||
try {
|
||
const result = await tool.invoke(input);
|
||
return {
|
||
content: [{ type: 'text', text: JSON.stringify(result) }],
|
||
structuredContent: result,
|
||
};
|
||
} catch (error) {
|
||
return {
|
||
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
||
isError: true,
|
||
};
|
||
}
|
||
});
|
||
}
|
||
return server;
|
||
}
|