feat(wave1): populate @mosaic/types and migrate @mosaic/queue imports
- @mosaic/types: full type definitions extracted from queue, bootstrap, context packages - @mosaic/queue: type imports now sourced from @mosaic/types via workspace:* - Task, TaskStatus, TaskPriority, TaskLane, CreateTaskInput, etc. centralised - Runtime constants (TASK_STATUSES etc.) remain in queue/src/task.ts
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" \"bin/**/*.ts\" \"vitest.config.ts\"",
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"test": "vitest run",
|
||||
"prepublishOnly": "pnpm lint && pnpm test && pnpm build"
|
||||
},
|
||||
@@ -32,9 +33,13 @@
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mosaic/types": "workspace:*",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"commander": "^14.0.3",
|
||||
"ioredis": "^5.10.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ interface CompleteCommandOptions {
|
||||
}
|
||||
|
||||
interface ClosableRedisTaskClient extends RedisTaskClient {
|
||||
ping(): Promise<string>;
|
||||
quit(): Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface QueueMcpDependencies {
|
||||
readonly serverInfo: Implementation;
|
||||
}
|
||||
|
||||
type ToolSchema = z.ZodObject<Record<string, z.ZodTypeAny>>;
|
||||
type ToolSchema = z.ZodTypeAny;
|
||||
|
||||
interface QueueMcpToolDefinition<TArgs extends z.ZodTypeAny> {
|
||||
readonly name: string;
|
||||
@@ -55,6 +55,7 @@ interface QueueMcpToolDefinition<TArgs extends z.ZodTypeAny> {
|
||||
}
|
||||
|
||||
interface ClosableRedisTaskClient extends RedisTaskClient {
|
||||
ping(): Promise<string>;
|
||||
quit(): Promise<string>;
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
name: 'queue_list',
|
||||
description: 'List queue tasks with optional project/mission/status filters',
|
||||
inputSchema: queueListToolInputSchema,
|
||||
execute: async (session, input) => {
|
||||
execute: async (session: QueueMcpSession, input: z.output<typeof queueListToolInputSchema>) => {
|
||||
const tasks = await session.repository.list(input);
|
||||
return {
|
||||
tasks,
|
||||
@@ -84,7 +85,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
name: 'queue_get',
|
||||
description: 'Get a single queue task by taskId',
|
||||
inputSchema: queueGetToolInputSchema,
|
||||
execute: async (session, input) => {
|
||||
execute: async (session: QueueMcpSession, input: z.output<typeof queueGetToolInputSchema>) => {
|
||||
const task = await session.repository.get(input.taskId);
|
||||
return {
|
||||
task,
|
||||
@@ -95,7 +96,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
name: 'queue_claim',
|
||||
description: 'Atomically claim a task for an agent',
|
||||
inputSchema: queueClaimToolInputSchema,
|
||||
execute: async (session, input) => {
|
||||
execute: async (session: QueueMcpSession, input: z.output<typeof queueClaimToolInputSchema>) => {
|
||||
const task = await session.repository.claim(input.taskId, {
|
||||
agentId: input.agentId,
|
||||
ttlSeconds: input.ttlSeconds,
|
||||
@@ -110,7 +111,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
name: 'queue_heartbeat',
|
||||
description: 'Refresh claim ownership TTL for a task',
|
||||
inputSchema: queueHeartbeatToolInputSchema,
|
||||
execute: async (session, input) => {
|
||||
execute: async (session: QueueMcpSession, input: z.output<typeof queueHeartbeatToolInputSchema>) => {
|
||||
const task = await session.repository.heartbeat(input.taskId, {
|
||||
agentId: input.agentId,
|
||||
ttlSeconds: input.ttlSeconds,
|
||||
@@ -125,7 +126,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
name: 'queue_release',
|
||||
description: 'Release a claimed task back to pending',
|
||||
inputSchema: queueReleaseToolInputSchema,
|
||||
execute: async (session, input) => {
|
||||
execute: async (session: QueueMcpSession, input: z.output<typeof queueReleaseToolInputSchema>) => {
|
||||
const task = await session.repository.release(input.taskId, {
|
||||
agentId: input.agentId,
|
||||
});
|
||||
@@ -139,7 +140,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
name: 'queue_complete',
|
||||
description: 'Mark a claimed task as completed',
|
||||
inputSchema: queueCompleteToolInputSchema,
|
||||
execute: async (session, input) => {
|
||||
execute: async (session: QueueMcpSession, input: z.output<typeof queueCompleteToolInputSchema>) => {
|
||||
const task = await session.repository.complete(input.taskId, {
|
||||
agentId: input.agentId,
|
||||
summary: input.summary,
|
||||
@@ -154,7 +155,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
name: 'queue_fail',
|
||||
description: 'Mark a claimed task as failed with a reason',
|
||||
inputSchema: queueFailToolInputSchema,
|
||||
execute: async (session, input) => {
|
||||
execute: async (session: QueueMcpSession, input: z.output<typeof queueFailToolInputSchema>) => {
|
||||
const task = await session.repository.fail(input.taskId, {
|
||||
agentId: input.agentId,
|
||||
reason: input.reason,
|
||||
@@ -169,7 +170,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
name: 'queue_status',
|
||||
description: 'Return queue health and task status counters',
|
||||
inputSchema: queueStatusToolInputSchema,
|
||||
execute: async (session) => {
|
||||
execute: async (session: QueueMcpSession) => {
|
||||
const tasks = await session.repository.list({});
|
||||
const health = await session.checkHealth();
|
||||
const counts = countStatuses(tasks);
|
||||
@@ -181,7 +182,7 @@ export const QUEUE_MCP_TOOL_DEFINITIONS = [
|
||||
};
|
||||
},
|
||||
},
|
||||
] as const satisfies readonly QueueMcpToolDefinition<ToolSchema>[];
|
||||
] as const;
|
||||
|
||||
export function buildQueueMcpServer(
|
||||
dependencyOverrides: Partial<QueueMcpDependencies> = {},
|
||||
@@ -196,11 +197,11 @@ export function buildQueueMcpServer(
|
||||
description: definition.description,
|
||||
inputSchema: definition.inputSchema,
|
||||
},
|
||||
async (args) => {
|
||||
async (args: unknown) => {
|
||||
return withSession(dependencies, async (session) => {
|
||||
try {
|
||||
const parsedArgs = definition.inputSchema.parse(args);
|
||||
const response = await definition.execute(session, parsedArgs);
|
||||
const response = await definition.execute(session, parsedArgs as never);
|
||||
return toToolResult(response);
|
||||
} catch (error) {
|
||||
return toToolErrorResult(error);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Redis, { type RedisOptions } from 'ioredis';
|
||||
import Redis, { type Redis as RedisClient, type RedisOptions } from 'ioredis';
|
||||
|
||||
const ERR_MISSING_REDIS_URL =
|
||||
'Missing required Valkey/Redis connection URL. Set VALKEY_URL or REDIS_URL.';
|
||||
@@ -36,7 +36,7 @@ export function resolveRedisUrl(env: NodeJS.ProcessEnv = process.env): string {
|
||||
return resolvedUrl;
|
||||
}
|
||||
|
||||
export function createRedisClient<TClient = Redis>(
|
||||
export function createRedisClient<TClient = RedisClient>(
|
||||
options: CreateRedisClientOptions<TClient> = {},
|
||||
): TClient {
|
||||
const redisUrl = resolveRedisUrl(options.env);
|
||||
|
||||
@@ -518,22 +518,28 @@ type TaskWithoutCompletionAndFailureFields = Omit<
|
||||
>;
|
||||
|
||||
function withoutClaimFields(task: Task): TaskWithoutClaimFields {
|
||||
const draft: Partial<Task> = { ...task };
|
||||
delete draft.claimedBy;
|
||||
delete draft.claimedAt;
|
||||
delete draft.claimTTL;
|
||||
return draft as TaskWithoutClaimFields;
|
||||
const {
|
||||
claimedBy: _claimedBy,
|
||||
claimedAt: _claimedAt,
|
||||
claimTTL: _claimTTL,
|
||||
...taskWithoutClaimFields
|
||||
} = task;
|
||||
|
||||
return taskWithoutClaimFields;
|
||||
}
|
||||
|
||||
function withoutCompletionAndFailureFields(
|
||||
task: TaskWithoutClaimFields,
|
||||
): TaskWithoutCompletionAndFailureFields {
|
||||
const draft: Partial<TaskWithoutClaimFields> = { ...task };
|
||||
delete draft.completedAt;
|
||||
delete draft.failedAt;
|
||||
delete draft.failureReason;
|
||||
delete draft.completionSummary;
|
||||
return draft as TaskWithoutCompletionAndFailureFields;
|
||||
const {
|
||||
completedAt: _completedAt,
|
||||
failedAt: _failedAt,
|
||||
failureReason: _failureReason,
|
||||
completionSummary: _completionSummary,
|
||||
...taskWithoutCompletionAndFailureFields
|
||||
} = task;
|
||||
|
||||
return taskWithoutCompletionAndFailureFields;
|
||||
}
|
||||
|
||||
function deserializeTask(taskId: string, raw: string): Task {
|
||||
@@ -588,18 +594,18 @@ function deserializeTask(taskId: string, raw: string): Task {
|
||||
}
|
||||
|
||||
return {
|
||||
id: parsed.id,
|
||||
project: parsed.project,
|
||||
mission: parsed.mission,
|
||||
taskId: parsed.taskId,
|
||||
title: parsed.title,
|
||||
status: parsed.status,
|
||||
priority: parsed.priority,
|
||||
dependencies: parsed.dependencies,
|
||||
lane: parsed.lane,
|
||||
retryCount: parsed.retryCount,
|
||||
createdAt: parsed.createdAt,
|
||||
updatedAt: parsed.updatedAt,
|
||||
id: parsed.id as string,
|
||||
project: parsed.project as string,
|
||||
mission: parsed.mission as string,
|
||||
taskId: parsed.taskId as string,
|
||||
title: parsed.title as string,
|
||||
status: parsed.status as TaskStatus,
|
||||
priority: parsed.priority as TaskPriority,
|
||||
dependencies: parsed.dependencies as string[],
|
||||
lane: parsed.lane as TaskLane,
|
||||
retryCount: parsed.retryCount as number,
|
||||
createdAt: parsed.createdAt as number,
|
||||
updatedAt: parsed.updatedAt as number,
|
||||
...(typeof parsed.description === 'string'
|
||||
? { description: parsed.description }
|
||||
: {}),
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { runQueueCli, type QueueCliDependencies, type QueueRepository } from '../src/cli.js';
|
||||
import type { Task } from '@mosaic/types';
|
||||
|
||||
function createRepositoryMock(): QueueRepository {
|
||||
return {
|
||||
create: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
Promise.resolve<Task>({
|
||||
id: 'MQ-005',
|
||||
project: 'queue',
|
||||
mission: 'phase1',
|
||||
@@ -23,7 +24,7 @@ function createRepositoryMock(): QueueRepository {
|
||||
list: vi.fn(() => Promise.resolve([])),
|
||||
get: vi.fn(() => Promise.resolve(null)),
|
||||
claim: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
Promise.resolve<Task>({
|
||||
id: 'MQ-005',
|
||||
project: 'queue',
|
||||
mission: 'phase1',
|
||||
@@ -42,7 +43,7 @@ function createRepositoryMock(): QueueRepository {
|
||||
}),
|
||||
),
|
||||
release: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
Promise.resolve<Task>({
|
||||
id: 'MQ-005',
|
||||
project: 'queue',
|
||||
mission: 'phase1',
|
||||
@@ -58,7 +59,7 @@ function createRepositoryMock(): QueueRepository {
|
||||
}),
|
||||
),
|
||||
complete: vi.fn(() =>
|
||||
Promise.resolve({
|
||||
Promise.resolve<Task>({
|
||||
id: 'MQ-005',
|
||||
project: 'queue',
|
||||
mission: 'phase1',
|
||||
|
||||
Reference in New Issue
Block a user