@mcoda/core 0.1.4
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/api/AgentsApi.d.ts +36 -0
- package/dist/api/AgentsApi.d.ts.map +1 -0
- package/dist/api/AgentsApi.js +176 -0
- package/dist/api/QaTasksApi.d.ts +8 -0
- package/dist/api/QaTasksApi.d.ts.map +1 -0
- package/dist/api/QaTasksApi.js +36 -0
- package/dist/api/TasksApi.d.ts +7 -0
- package/dist/api/TasksApi.d.ts.map +1 -0
- package/dist/api/TasksApi.js +34 -0
- package/dist/config/ConfigService.d.ts +3 -0
- package/dist/config/ConfigService.d.ts.map +1 -0
- package/dist/config/ConfigService.js +2 -0
- package/dist/domain/dependencies/Dependency.d.ts +3 -0
- package/dist/domain/dependencies/Dependency.d.ts.map +1 -0
- package/dist/domain/dependencies/Dependency.js +2 -0
- package/dist/domain/epics/Epic.d.ts +3 -0
- package/dist/domain/epics/Epic.d.ts.map +1 -0
- package/dist/domain/epics/Epic.js +2 -0
- package/dist/domain/projects/Project.d.ts +3 -0
- package/dist/domain/projects/Project.d.ts.map +1 -0
- package/dist/domain/projects/Project.js +2 -0
- package/dist/domain/tasks/Task.d.ts +3 -0
- package/dist/domain/tasks/Task.d.ts.map +1 -0
- package/dist/domain/tasks/Task.js +2 -0
- package/dist/domain/userStories/UserStory.d.ts +3 -0
- package/dist/domain/userStories/UserStory.d.ts.map +1 -0
- package/dist/domain/userStories/UserStory.js +2 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/prompts/PdrPrompts.d.ts +4 -0
- package/dist/prompts/PdrPrompts.d.ts.map +1 -0
- package/dist/prompts/PdrPrompts.js +21 -0
- package/dist/prompts/PromptLoader.d.ts +3 -0
- package/dist/prompts/PromptLoader.d.ts.map +1 -0
- package/dist/prompts/PromptLoader.js +2 -0
- package/dist/prompts/SdsPrompts.d.ts +5 -0
- package/dist/prompts/SdsPrompts.d.ts.map +1 -0
- package/dist/prompts/SdsPrompts.js +44 -0
- package/dist/services/agents/AgentManagementService.d.ts +3 -0
- package/dist/services/agents/AgentManagementService.d.ts.map +1 -0
- package/dist/services/agents/AgentManagementService.js +2 -0
- package/dist/services/agents/GatewayAgentService.d.ts +92 -0
- package/dist/services/agents/GatewayAgentService.d.ts.map +1 -0
- package/dist/services/agents/GatewayAgentService.js +870 -0
- package/dist/services/agents/RoutingApiClient.d.ts +23 -0
- package/dist/services/agents/RoutingApiClient.d.ts.map +1 -0
- package/dist/services/agents/RoutingApiClient.js +62 -0
- package/dist/services/agents/RoutingService.d.ts +50 -0
- package/dist/services/agents/RoutingService.d.ts.map +1 -0
- package/dist/services/agents/RoutingService.js +386 -0
- package/dist/services/agents/generated/RoutingApiClient.d.ts +21 -0
- package/dist/services/agents/generated/RoutingApiClient.d.ts.map +1 -0
- package/dist/services/agents/generated/RoutingApiClient.js +68 -0
- package/dist/services/backlog/BacklogService.d.ts +98 -0
- package/dist/services/backlog/BacklogService.d.ts.map +1 -0
- package/dist/services/backlog/BacklogService.js +453 -0
- package/dist/services/backlog/TaskOrderingService.d.ts +88 -0
- package/dist/services/backlog/TaskOrderingService.d.ts.map +1 -0
- package/dist/services/backlog/TaskOrderingService.js +675 -0
- package/dist/services/docs/DocsService.d.ts +82 -0
- package/dist/services/docs/DocsService.d.ts.map +1 -0
- package/dist/services/docs/DocsService.js +1631 -0
- package/dist/services/estimate/EstimateService.d.ts +12 -0
- package/dist/services/estimate/EstimateService.d.ts.map +1 -0
- package/dist/services/estimate/EstimateService.js +103 -0
- package/dist/services/estimate/VelocityService.d.ts +19 -0
- package/dist/services/estimate/VelocityService.d.ts.map +1 -0
- package/dist/services/estimate/VelocityService.js +237 -0
- package/dist/services/estimate/types.d.ts +30 -0
- package/dist/services/estimate/types.d.ts.map +1 -0
- package/dist/services/estimate/types.js +1 -0
- package/dist/services/execution/ExecutionService.d.ts +3 -0
- package/dist/services/execution/ExecutionService.d.ts.map +1 -0
- package/dist/services/execution/ExecutionService.js +2 -0
- package/dist/services/execution/QaFollowupService.d.ts +38 -0
- package/dist/services/execution/QaFollowupService.d.ts.map +1 -0
- package/dist/services/execution/QaFollowupService.js +236 -0
- package/dist/services/execution/QaProfileService.d.ts +22 -0
- package/dist/services/execution/QaProfileService.d.ts.map +1 -0
- package/dist/services/execution/QaProfileService.js +142 -0
- package/dist/services/execution/QaTasksService.d.ts +101 -0
- package/dist/services/execution/QaTasksService.d.ts.map +1 -0
- package/dist/services/execution/QaTasksService.js +1117 -0
- package/dist/services/execution/TaskSelectionService.d.ts +50 -0
- package/dist/services/execution/TaskSelectionService.d.ts.map +1 -0
- package/dist/services/execution/TaskSelectionService.js +281 -0
- package/dist/services/execution/TaskStateService.d.ts +19 -0
- package/dist/services/execution/TaskStateService.d.ts.map +1 -0
- package/dist/services/execution/TaskStateService.js +59 -0
- package/dist/services/execution/WorkOnTasksService.d.ts +80 -0
- package/dist/services/execution/WorkOnTasksService.d.ts.map +1 -0
- package/dist/services/execution/WorkOnTasksService.js +1833 -0
- package/dist/services/jobs/JobInsightsService.d.ts +97 -0
- package/dist/services/jobs/JobInsightsService.d.ts.map +1 -0
- package/dist/services/jobs/JobInsightsService.js +263 -0
- package/dist/services/jobs/JobResumeService.d.ts +16 -0
- package/dist/services/jobs/JobResumeService.d.ts.map +1 -0
- package/dist/services/jobs/JobResumeService.js +113 -0
- package/dist/services/jobs/JobService.d.ts +149 -0
- package/dist/services/jobs/JobService.d.ts.map +1 -0
- package/dist/services/jobs/JobService.js +490 -0
- package/dist/services/jobs/JobsApiClient.d.ts +73 -0
- package/dist/services/jobs/JobsApiClient.d.ts.map +1 -0
- package/dist/services/jobs/JobsApiClient.js +67 -0
- package/dist/services/openapi/OpenApiService.d.ts +54 -0
- package/dist/services/openapi/OpenApiService.d.ts.map +1 -0
- package/dist/services/openapi/OpenApiService.js +503 -0
- package/dist/services/planning/CreateTasksService.d.ts +68 -0
- package/dist/services/planning/CreateTasksService.d.ts.map +1 -0
- package/dist/services/planning/CreateTasksService.js +989 -0
- package/dist/services/planning/KeyHelpers.d.ts +5 -0
- package/dist/services/planning/KeyHelpers.d.ts.map +1 -0
- package/dist/services/planning/KeyHelpers.js +62 -0
- package/dist/services/planning/PlanningService.d.ts +3 -0
- package/dist/services/planning/PlanningService.d.ts.map +1 -0
- package/dist/services/planning/PlanningService.js +2 -0
- package/dist/services/planning/RefineTasksService.d.ts +56 -0
- package/dist/services/planning/RefineTasksService.d.ts.map +1 -0
- package/dist/services/planning/RefineTasksService.js +1328 -0
- package/dist/services/review/CodeReviewService.d.ts +103 -0
- package/dist/services/review/CodeReviewService.d.ts.map +1 -0
- package/dist/services/review/CodeReviewService.js +1187 -0
- package/dist/services/system/SystemUpdateService.d.ts +55 -0
- package/dist/services/system/SystemUpdateService.d.ts.map +1 -0
- package/dist/services/system/SystemUpdateService.js +136 -0
- package/dist/services/tasks/TaskApiResolver.d.ts +7 -0
- package/dist/services/tasks/TaskApiResolver.d.ts.map +1 -0
- package/dist/services/tasks/TaskApiResolver.js +41 -0
- package/dist/services/tasks/TaskDetailService.d.ts +106 -0
- package/dist/services/tasks/TaskDetailService.d.ts.map +1 -0
- package/dist/services/tasks/TaskDetailService.js +332 -0
- package/dist/services/telemetry/TelemetryService.d.ts +53 -0
- package/dist/services/telemetry/TelemetryService.d.ts.map +1 -0
- package/dist/services/telemetry/TelemetryService.js +434 -0
- package/dist/workspace/WorkspaceManager.d.ts +35 -0
- package/dist/workspace/WorkspaceManager.d.ts.map +1 -0
- package/dist/workspace/WorkspaceManager.js +201 -0
- package/package.json +45 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import { PathHelper } from "@mcoda/shared";
|
|
5
|
+
import { WorkspaceRepository, } from "@mcoda/db";
|
|
6
|
+
const nowIso = () => new Date().toISOString();
|
|
7
|
+
export class JobService {
|
|
8
|
+
constructor(workspace, workspaceRepo, options = {}) {
|
|
9
|
+
this.workspaceRepo = workspaceRepo;
|
|
10
|
+
this.checkpointCounters = new Map();
|
|
11
|
+
this.workspaceRepoInit = false;
|
|
12
|
+
this.telemetryWarningShown = false;
|
|
13
|
+
this.telemetryRemoteWarningShown = false;
|
|
14
|
+
const resolvedRoot = typeof workspace === "string" ? workspace : workspace.workspaceRoot;
|
|
15
|
+
this.workspaceRoot = resolvedRoot;
|
|
16
|
+
this.workspaceId = typeof workspace === "string" ? resolvedRoot : workspace.workspaceId ?? resolvedRoot;
|
|
17
|
+
this.perRunTelemetryDisabled = options.noTelemetry ?? false;
|
|
18
|
+
this.envTelemetryDisabled = (process.env.MCODA_TELEMETRY ?? "").toLowerCase() === "off";
|
|
19
|
+
this.requireRepo = options.requireRepo ?? false;
|
|
20
|
+
}
|
|
21
|
+
get mcodaDir() {
|
|
22
|
+
return path.join(this.workspaceRoot, ".mcoda");
|
|
23
|
+
}
|
|
24
|
+
get commandRunsPath() {
|
|
25
|
+
return path.join(this.mcodaDir, "command_runs.json");
|
|
26
|
+
}
|
|
27
|
+
get jobsStorePath() {
|
|
28
|
+
return path.join(this.mcodaDir, "jobs.json");
|
|
29
|
+
}
|
|
30
|
+
get tokenUsagePath() {
|
|
31
|
+
return path.join(this.mcodaDir, "token_usage.json");
|
|
32
|
+
}
|
|
33
|
+
jobDir(jobId) {
|
|
34
|
+
return path.join(this.mcodaDir, "jobs", jobId);
|
|
35
|
+
}
|
|
36
|
+
manifestPath(jobId) {
|
|
37
|
+
return path.join(this.jobDir(jobId), "manifest.json");
|
|
38
|
+
}
|
|
39
|
+
checkpointDir(jobId) {
|
|
40
|
+
return path.join(this.jobDir(jobId), "checkpoints");
|
|
41
|
+
}
|
|
42
|
+
logsDir(jobId) {
|
|
43
|
+
return path.join(this.jobDir(jobId), "logs");
|
|
44
|
+
}
|
|
45
|
+
async ensureMcoda() {
|
|
46
|
+
await PathHelper.ensureDir(this.mcodaDir);
|
|
47
|
+
if (this.workspaceRepoInit)
|
|
48
|
+
return;
|
|
49
|
+
this.workspaceRepoInit = true;
|
|
50
|
+
if (process.env.MCODA_DISABLE_DB === "1") {
|
|
51
|
+
if (this.requireRepo) {
|
|
52
|
+
throw new Error("Workspace DB disabled via MCODA_DISABLE_DB; job operations require the workspace DB per SDS.");
|
|
53
|
+
}
|
|
54
|
+
this.workspaceRepo = undefined;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
if (!this.workspaceRepo) {
|
|
59
|
+
this.workspaceRepo = await WorkspaceRepository.create(this.workspaceRoot);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (this.requireRepo) {
|
|
64
|
+
throw new Error(`Workspace DB could not be opened for jobs (${error.message}); run mcoda init/create-tasks to initialize the workspace.`);
|
|
65
|
+
}
|
|
66
|
+
// Fall back to JSON stores if sqlite is unavailable or schema mismatches.
|
|
67
|
+
this.workspaceRepo = undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async readJsonArray(filePath) {
|
|
71
|
+
try {
|
|
72
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
if (Array.isArray(parsed))
|
|
75
|
+
return parsed;
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async writeJsonArray(filePath, records) {
|
|
83
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
84
|
+
await fs.writeFile(filePath, JSON.stringify(records, null, 2), "utf8");
|
|
85
|
+
}
|
|
86
|
+
async appendJsonArray(filePath, record) {
|
|
87
|
+
const existing = await this.readJsonArray(filePath);
|
|
88
|
+
existing.push(record);
|
|
89
|
+
await this.writeJsonArray(filePath, existing);
|
|
90
|
+
}
|
|
91
|
+
async listJobs() {
|
|
92
|
+
await this.ensureMcoda();
|
|
93
|
+
if (this.workspaceRepo && "listJobs" in this.workspaceRepo) {
|
|
94
|
+
const rows = await this.workspaceRepo.listJobs();
|
|
95
|
+
return rows;
|
|
96
|
+
}
|
|
97
|
+
return this.readJsonArray(this.jobsStorePath);
|
|
98
|
+
}
|
|
99
|
+
async getJob(jobId) {
|
|
100
|
+
await this.ensureMcoda();
|
|
101
|
+
if (this.workspaceRepo && "getJob" in this.workspaceRepo) {
|
|
102
|
+
return (await this.workspaceRepo.getJob(jobId)) ?? undefined;
|
|
103
|
+
}
|
|
104
|
+
const jobs = await this.readJsonArray(this.jobsStorePath);
|
|
105
|
+
return jobs.find((j) => j.id === jobId);
|
|
106
|
+
}
|
|
107
|
+
async readCheckpoints(jobId) {
|
|
108
|
+
const dir = this.checkpointDir(jobId);
|
|
109
|
+
try {
|
|
110
|
+
const entries = await fs.readdir(dir);
|
|
111
|
+
const checkpoints = [];
|
|
112
|
+
for (const entry of entries.sort()) {
|
|
113
|
+
const raw = await fs.readFile(path.join(dir, entry), "utf8");
|
|
114
|
+
checkpoints.push(JSON.parse(raw));
|
|
115
|
+
}
|
|
116
|
+
return checkpoints;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async readLog(jobId) {
|
|
123
|
+
const logPath = path.join(this.logsDir(jobId), "stream.log");
|
|
124
|
+
try {
|
|
125
|
+
return await fs.readFile(logPath, "utf8");
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return "";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async listJobLogs(jobId, options = {}) {
|
|
132
|
+
await this.ensureMcoda();
|
|
133
|
+
if (this.workspaceRepo && "getDb" in this.workspaceRepo) {
|
|
134
|
+
const db = this.workspaceRepo.getDb();
|
|
135
|
+
const clauses = ["tr.job_id = ?"];
|
|
136
|
+
const params = [jobId];
|
|
137
|
+
if (options.since) {
|
|
138
|
+
clauses.push("datetime(tl.timestamp) >= datetime(?)");
|
|
139
|
+
params.push(options.since);
|
|
140
|
+
}
|
|
141
|
+
if (options.after?.timestamp) {
|
|
142
|
+
clauses.push("(datetime(tl.timestamp) > datetime(?) OR (tl.timestamp = ? AND COALESCE(tl.sequence,0) > COALESCE(?,0)))");
|
|
143
|
+
params.push(options.after.timestamp, options.after.timestamp, options.after.sequence ?? 0);
|
|
144
|
+
}
|
|
145
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
146
|
+
const rows = (await db.all(`
|
|
147
|
+
SELECT tl.timestamp, tl.sequence, tl.level, tl.source, tl.message, tl.details_json, tr.task_id, tr.command, t.key as task_key
|
|
148
|
+
FROM task_logs tl
|
|
149
|
+
JOIN task_runs tr ON tr.id = tl.task_run_id
|
|
150
|
+
LEFT JOIN tasks t ON t.id = tr.task_id
|
|
151
|
+
${where}
|
|
152
|
+
ORDER BY datetime(tl.timestamp) ASC, tl.sequence ASC
|
|
153
|
+
LIMIT 500
|
|
154
|
+
`, ...params));
|
|
155
|
+
return rows.map((row) => {
|
|
156
|
+
const details = row.details_json ? JSON.parse(row.details_json) : null;
|
|
157
|
+
return {
|
|
158
|
+
timestamp: row.timestamp,
|
|
159
|
+
sequence: row.sequence ?? null,
|
|
160
|
+
level: row.level ?? null,
|
|
161
|
+
source: row.source ?? null,
|
|
162
|
+
message: row.message ?? null,
|
|
163
|
+
taskId: row.task_id ?? null,
|
|
164
|
+
taskKey: row.task_key ?? null,
|
|
165
|
+
phase: details?.phase ?? row.command ?? null,
|
|
166
|
+
details,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
// Fallback to file-based stream log when DB access is not available.
|
|
171
|
+
const raw = await this.readLog(jobId);
|
|
172
|
+
const lines = raw.split(/\r?\n/).filter(Boolean);
|
|
173
|
+
return lines.map((line, idx) => ({
|
|
174
|
+
timestamp: nowIso(),
|
|
175
|
+
sequence: idx,
|
|
176
|
+
message: line,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
async summarizeTaskRuns(jobId) {
|
|
180
|
+
await this.ensureMcoda();
|
|
181
|
+
if (this.workspaceRepo && "getDb" in this.workspaceRepo) {
|
|
182
|
+
const db = this.workspaceRepo.getDb();
|
|
183
|
+
const rows = (await db.all(`
|
|
184
|
+
SELECT tr.task_id, tr.status, tr.started_at, tr.finished_at, tr.command, t.key as task_key
|
|
185
|
+
FROM task_runs tr
|
|
186
|
+
LEFT JOIN tasks t ON t.id = tr.task_id
|
|
187
|
+
WHERE tr.job_id = ?
|
|
188
|
+
ORDER BY datetime(tr.started_at) ASC, datetime(tr.finished_at) ASC
|
|
189
|
+
`, jobId));
|
|
190
|
+
return rows.map((row) => ({
|
|
191
|
+
taskId: row.task_id ?? null,
|
|
192
|
+
taskKey: row.task_key ?? null,
|
|
193
|
+
status: row.status ?? null,
|
|
194
|
+
startedAt: row.started_at ?? null,
|
|
195
|
+
finishedAt: row.finished_at ?? null,
|
|
196
|
+
command: row.command ?? null,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
async startCommandRun(commandName, projectKey, options) {
|
|
202
|
+
await this.ensureMcoda();
|
|
203
|
+
const startedAt = nowIso();
|
|
204
|
+
let record = {
|
|
205
|
+
id: randomUUID(),
|
|
206
|
+
commandName,
|
|
207
|
+
workspaceId: this.workspaceId,
|
|
208
|
+
projectKey,
|
|
209
|
+
jobId: options?.jobId,
|
|
210
|
+
taskIds: options?.taskIds,
|
|
211
|
+
gitBranch: options?.gitBranch,
|
|
212
|
+
gitBaseBranch: options?.gitBaseBranch,
|
|
213
|
+
startedAt,
|
|
214
|
+
status: "running",
|
|
215
|
+
spProcessed: null,
|
|
216
|
+
};
|
|
217
|
+
if (this.workspaceRepo) {
|
|
218
|
+
const row = await this.workspaceRepo.createCommandRun({
|
|
219
|
+
workspaceId: this.workspaceId,
|
|
220
|
+
commandName,
|
|
221
|
+
jobId: record.jobId,
|
|
222
|
+
taskIds: record.taskIds,
|
|
223
|
+
gitBranch: record.gitBranch,
|
|
224
|
+
gitBaseBranch: record.gitBaseBranch,
|
|
225
|
+
startedAt,
|
|
226
|
+
status: "running",
|
|
227
|
+
});
|
|
228
|
+
record = { ...record, id: row.id };
|
|
229
|
+
}
|
|
230
|
+
await this.appendJsonArray(this.commandRunsPath, record);
|
|
231
|
+
return record;
|
|
232
|
+
}
|
|
233
|
+
async finishCommandRun(runId, status, errorSummary, spProcessed) {
|
|
234
|
+
const runs = await this.readJsonArray(this.commandRunsPath);
|
|
235
|
+
const idx = runs.findIndex((r) => r.id === runId);
|
|
236
|
+
if (idx === -1)
|
|
237
|
+
return;
|
|
238
|
+
const completedAt = nowIso();
|
|
239
|
+
const durationSeconds = runs[idx].startedAt ? (Date.parse(completedAt) - Date.parse(runs[idx].startedAt)) / 1000 : undefined;
|
|
240
|
+
runs[idx] = { ...runs[idx], completedAt, status, errorSummary, durationSeconds, spProcessed: spProcessed ?? runs[idx].spProcessed };
|
|
241
|
+
await this.writeJsonArray(this.commandRunsPath, runs);
|
|
242
|
+
if (this.workspaceRepo) {
|
|
243
|
+
await this.workspaceRepo.completeCommandRun(runId, {
|
|
244
|
+
status,
|
|
245
|
+
completedAt,
|
|
246
|
+
errorSummary,
|
|
247
|
+
durationSeconds,
|
|
248
|
+
spProcessed: spProcessed ?? runs[idx].spProcessed ?? null,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async startJob(type, commandRunId, projectKey, options = {}) {
|
|
253
|
+
await this.ensureMcoda();
|
|
254
|
+
const createdAt = nowIso();
|
|
255
|
+
let record = {
|
|
256
|
+
id: randomUUID(),
|
|
257
|
+
type,
|
|
258
|
+
state: "running",
|
|
259
|
+
commandRunId,
|
|
260
|
+
commandName: options.commandName ?? type,
|
|
261
|
+
workspaceId: this.workspaceId,
|
|
262
|
+
projectKey,
|
|
263
|
+
payload: options.payload,
|
|
264
|
+
totalItems: options.totalItems,
|
|
265
|
+
processedItems: options.processedItems,
|
|
266
|
+
totalUnits: options.totalItems,
|
|
267
|
+
completedUnits: options.processedItems,
|
|
268
|
+
createdAt,
|
|
269
|
+
updatedAt: createdAt,
|
|
270
|
+
};
|
|
271
|
+
if (this.workspaceRepo) {
|
|
272
|
+
const row = await this.workspaceRepo.createJob({
|
|
273
|
+
workspaceId: this.workspaceId,
|
|
274
|
+
type,
|
|
275
|
+
state: "running",
|
|
276
|
+
commandName: record.commandName,
|
|
277
|
+
payload: options.payload,
|
|
278
|
+
totalItems: record.totalItems,
|
|
279
|
+
processedItems: record.processedItems,
|
|
280
|
+
});
|
|
281
|
+
record = { ...record, id: row.id, createdAt: row.createdAt, updatedAt: row.updatedAt };
|
|
282
|
+
}
|
|
283
|
+
const jobs = await this.readJsonArray(this.jobsStorePath);
|
|
284
|
+
jobs.push(record);
|
|
285
|
+
await this.writeJsonArray(this.jobsStorePath, jobs);
|
|
286
|
+
await this.writeManifest(record);
|
|
287
|
+
if (commandRunId) {
|
|
288
|
+
await this.attachCommandRunToJob(commandRunId, record.id);
|
|
289
|
+
}
|
|
290
|
+
return record;
|
|
291
|
+
}
|
|
292
|
+
async attachCommandRunToJob(commandRunId, jobId) {
|
|
293
|
+
const runs = await this.readJsonArray(this.commandRunsPath);
|
|
294
|
+
const idx = runs.findIndex((r) => r.id === commandRunId);
|
|
295
|
+
if (idx !== -1) {
|
|
296
|
+
runs[idx] = { ...runs[idx], jobId };
|
|
297
|
+
await this.writeJsonArray(this.commandRunsPath, runs);
|
|
298
|
+
}
|
|
299
|
+
if (this.workspaceRepo && "setCommandRunJobId" in this.workspaceRepo) {
|
|
300
|
+
try {
|
|
301
|
+
await this.workspaceRepo.setCommandRunJobId(commandRunId, jobId);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// ignore linking failures
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async updateJobStatus(jobId, state, metadata) {
|
|
309
|
+
const jobs = await this.readJsonArray(this.jobsStorePath);
|
|
310
|
+
const idx = jobs.findIndex((j) => j.id === jobId);
|
|
311
|
+
if (idx === -1)
|
|
312
|
+
return;
|
|
313
|
+
const completedAt = state !== "running" && state !== "queued" ? nowIso() : jobs[idx].completedAt;
|
|
314
|
+
const durationSeconds = completedAt && jobs[idx].createdAt ? (Date.parse(completedAt) - Date.parse(jobs[idx].createdAt)) / 1000 : jobs[idx].durationSeconds;
|
|
315
|
+
const jobStateDetail = metadata?.job_state_detail ?? metadata?.jobStateDetail ?? jobs[idx].jobStateDetail;
|
|
316
|
+
const payloadUpdate = metadata?.payload ??
|
|
317
|
+
(metadata
|
|
318
|
+
? Object.fromEntries(Object.entries(metadata).filter(([key]) => !["payload", "totalItems", "processedItems", "lastCheckpoint", "errorSummary", "error"].includes(key)))
|
|
319
|
+
: undefined);
|
|
320
|
+
const updated = {
|
|
321
|
+
...jobs[idx],
|
|
322
|
+
state,
|
|
323
|
+
jobState: state,
|
|
324
|
+
jobStateDetail,
|
|
325
|
+
payload: payloadUpdate ? { ...(jobs[idx].payload ?? {}), ...payloadUpdate } : jobs[idx].payload,
|
|
326
|
+
totalItems: metadata?.totalItems ?? jobs[idx].totalItems,
|
|
327
|
+
processedItems: metadata?.processedItems ?? jobs[idx].processedItems,
|
|
328
|
+
totalUnits: metadata?.totalUnits ?? metadata?.totalItems ?? jobs[idx].totalUnits ?? jobs[idx].totalItems,
|
|
329
|
+
completedUnits: metadata?.completedUnits ?? metadata?.processedItems ?? jobs[idx].completedUnits ?? jobs[idx].processedItems,
|
|
330
|
+
lastCheckpoint: metadata?.lastCheckpoint ?? jobs[idx].lastCheckpoint,
|
|
331
|
+
errorSummary: metadata?.errorSummary ?? metadata?.error ?? jobs[idx].errorSummary,
|
|
332
|
+
updatedAt: nowIso(),
|
|
333
|
+
completedAt,
|
|
334
|
+
durationSeconds,
|
|
335
|
+
};
|
|
336
|
+
jobs[idx] = updated;
|
|
337
|
+
await this.writeJsonArray(this.jobsStorePath, jobs);
|
|
338
|
+
await this.writeManifest(updated);
|
|
339
|
+
if (this.workspaceRepo) {
|
|
340
|
+
await this.workspaceRepo.updateJobState(jobId, {
|
|
341
|
+
state,
|
|
342
|
+
commandName: updated.commandName ?? updated.type,
|
|
343
|
+
totalItems: updated.totalItems,
|
|
344
|
+
processedItems: updated.processedItems,
|
|
345
|
+
lastCheckpoint: updated.lastCheckpoint,
|
|
346
|
+
errorSummary: updated.errorSummary,
|
|
347
|
+
completedAt: updated.completedAt ?? null,
|
|
348
|
+
payload: updated.payload,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async writeCheckpoint(jobId, checkpoint) {
|
|
353
|
+
const dir = this.checkpointDir(jobId);
|
|
354
|
+
await PathHelper.ensureDir(dir);
|
|
355
|
+
let current = this.checkpointCounters.get(jobId);
|
|
356
|
+
if (current === undefined) {
|
|
357
|
+
try {
|
|
358
|
+
const entries = await fs.readdir(dir);
|
|
359
|
+
const nums = entries
|
|
360
|
+
.map((e) => Number.parseInt(e.replace(/\.ckpt\.json$/, ""), 10))
|
|
361
|
+
.filter((n) => Number.isFinite(n));
|
|
362
|
+
current = nums.length ? Math.max(...nums) : 0;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
current = 0;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const next = (current ?? 0) + 1;
|
|
369
|
+
this.checkpointCounters.set(jobId, next);
|
|
370
|
+
const filename = `${String(next).padStart(6, "0")}.ckpt.json`;
|
|
371
|
+
const target = path.join(dir, filename);
|
|
372
|
+
const job = await this.getJob(jobId);
|
|
373
|
+
const createdAt = nowIso();
|
|
374
|
+
const payload = {
|
|
375
|
+
schema_version: 1,
|
|
376
|
+
job_id: jobId,
|
|
377
|
+
checkpoint_seq: next,
|
|
378
|
+
checkpoint_id: randomUUID(),
|
|
379
|
+
created_at: createdAt,
|
|
380
|
+
status: job?.state ?? "running",
|
|
381
|
+
stage: checkpoint.stage,
|
|
382
|
+
timestamp: checkpoint.timestamp ?? createdAt,
|
|
383
|
+
reason: checkpoint.details?.reason,
|
|
384
|
+
progress: {
|
|
385
|
+
total: job?.totalItems ?? checkpoint.details?.totalItems ?? null,
|
|
386
|
+
completed: job?.processedItems ?? checkpoint.details?.processedItems ?? null,
|
|
387
|
+
},
|
|
388
|
+
details: checkpoint.details,
|
|
389
|
+
};
|
|
390
|
+
await fs.writeFile(target, JSON.stringify(payload, null, 2), "utf8");
|
|
391
|
+
if (this.workspaceRepo) {
|
|
392
|
+
await this.workspaceRepo.updateJobState(jobId, {
|
|
393
|
+
lastCheckpoint: checkpoint.stage,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async appendLog(jobId, content) {
|
|
398
|
+
const logDir = this.logsDir(jobId);
|
|
399
|
+
await PathHelper.ensureDir(logDir);
|
|
400
|
+
const logPath = path.join(logDir, "stream.log");
|
|
401
|
+
await fs.appendFile(logPath, content, "utf8");
|
|
402
|
+
}
|
|
403
|
+
async readTelemetryConfig() {
|
|
404
|
+
const configPath = path.join(this.mcodaDir, "config.json");
|
|
405
|
+
try {
|
|
406
|
+
const raw = await fs.readFile(configPath, "utf8");
|
|
407
|
+
const parsed = JSON.parse(raw);
|
|
408
|
+
return parsed.telemetry;
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async shouldRecordTokenUsage() {
|
|
415
|
+
if (this.telemetryConfig === undefined) {
|
|
416
|
+
this.telemetryConfig = await this.readTelemetryConfig();
|
|
417
|
+
}
|
|
418
|
+
if (this.telemetryConfig?.strict) {
|
|
419
|
+
if (!this.telemetryWarningShown) {
|
|
420
|
+
// eslint-disable-next-line no-console
|
|
421
|
+
console.warn("Telemetry strict mode is enabled; token usage will not be recorded locally.");
|
|
422
|
+
this.telemetryWarningShown = true;
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
if ((this.perRunTelemetryDisabled || this.envTelemetryDisabled || this.telemetryConfig?.optOut) &&
|
|
427
|
+
!this.telemetryRemoteWarningShown) {
|
|
428
|
+
// eslint-disable-next-line no-console
|
|
429
|
+
console.warn("Remote telemetry export disabled for this run (--no-telemetry/MCODA_TELEMETRY=off or opt-out). Local logging still enabled unless telemetry.strict is set.");
|
|
430
|
+
this.telemetryRemoteWarningShown = true;
|
|
431
|
+
}
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
async recordTokenUsage(entry) {
|
|
435
|
+
const recordTelemetry = await this.shouldRecordTokenUsage();
|
|
436
|
+
if (!recordTelemetry)
|
|
437
|
+
return;
|
|
438
|
+
const normalized = {
|
|
439
|
+
workspaceId: entry.workspaceId ?? this.workspaceId,
|
|
440
|
+
agentId: entry.agentId ?? null,
|
|
441
|
+
modelName: entry.modelName ?? null,
|
|
442
|
+
jobId: entry.jobId ?? null,
|
|
443
|
+
commandRunId: entry.commandRunId ?? null,
|
|
444
|
+
taskRunId: entry.taskRunId ?? null,
|
|
445
|
+
taskId: entry.taskId ?? null,
|
|
446
|
+
projectId: entry.projectId ?? null,
|
|
447
|
+
epicId: entry.epicId ?? null,
|
|
448
|
+
userStoryId: entry.userStoryId ?? null,
|
|
449
|
+
tokensPrompt: entry.tokensPrompt ?? entry.promptTokens ?? null,
|
|
450
|
+
tokensCompletion: entry.tokensCompletion ?? entry.completionTokens ?? null,
|
|
451
|
+
tokensTotal: entry.tokensTotal ?? null,
|
|
452
|
+
costEstimate: entry.costEstimate ?? entry.costUsd ?? null,
|
|
453
|
+
durationSeconds: entry.durationSeconds ?? null,
|
|
454
|
+
timestamp: entry.timestamp,
|
|
455
|
+
metadata: {
|
|
456
|
+
...(entry.metadata ?? {}),
|
|
457
|
+
...(entry.commandName ? { commandName: entry.commandName } : {}),
|
|
458
|
+
...(entry.action ? { action: entry.action } : {}),
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
const fileRecord = {
|
|
462
|
+
...normalized,
|
|
463
|
+
...(entry.commandName ? { commandName: entry.commandName } : {}),
|
|
464
|
+
...(entry.action ? { action: entry.action } : {}),
|
|
465
|
+
};
|
|
466
|
+
await this.appendJsonArray(this.tokenUsagePath, fileRecord);
|
|
467
|
+
if (this.workspaceRepo) {
|
|
468
|
+
await this.workspaceRepo.recordTokenUsage(normalized);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async writeManifest(job, extras = {}) {
|
|
472
|
+
await PathHelper.ensureDir(this.jobDir(job.id));
|
|
473
|
+
const manifestPath = this.manifestPath(job.id);
|
|
474
|
+
const payload = {
|
|
475
|
+
schema_version: 1,
|
|
476
|
+
job_id: job.id,
|
|
477
|
+
updated_at: new Date().toISOString(),
|
|
478
|
+
status: job.status ?? job.state,
|
|
479
|
+
progress: { total: job.totalItems ?? null, completed: job.processedItems ?? null },
|
|
480
|
+
...job,
|
|
481
|
+
...extras,
|
|
482
|
+
};
|
|
483
|
+
await fs.writeFile(manifestPath, JSON.stringify(payload, null, 2), "utf8");
|
|
484
|
+
}
|
|
485
|
+
async close() {
|
|
486
|
+
if (this.workspaceRepo) {
|
|
487
|
+
await this.workspaceRepo.close();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
|
|
2
|
+
export interface JobsApiJob {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
command_name?: string;
|
|
6
|
+
job_state?: string;
|
|
7
|
+
job_state_detail?: string;
|
|
8
|
+
job_state_detail_code?: string;
|
|
9
|
+
total_units?: number | null;
|
|
10
|
+
completed_units?: number | null;
|
|
11
|
+
created_at?: string;
|
|
12
|
+
updated_at?: string;
|
|
13
|
+
last_checkpoint_at?: string;
|
|
14
|
+
completed_at?: string | null;
|
|
15
|
+
payload_json?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface JobsApiLogs {
|
|
18
|
+
entries: Array<{
|
|
19
|
+
timestamp: string;
|
|
20
|
+
sequence?: number | null;
|
|
21
|
+
level?: string | null;
|
|
22
|
+
source?: string | null;
|
|
23
|
+
message?: string | null;
|
|
24
|
+
task_id?: string | null;
|
|
25
|
+
task_key?: string | null;
|
|
26
|
+
phase?: string | null;
|
|
27
|
+
details?: Record<string, unknown> | null;
|
|
28
|
+
}>;
|
|
29
|
+
cursor?: {
|
|
30
|
+
timestamp: string;
|
|
31
|
+
sequence?: number | null;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export interface JobsApiTasksSummary {
|
|
35
|
+
totals: Record<string, number>;
|
|
36
|
+
tasks: Array<{
|
|
37
|
+
task_id?: string | null;
|
|
38
|
+
task_key?: string | null;
|
|
39
|
+
status?: string | null;
|
|
40
|
+
started_at?: string | null;
|
|
41
|
+
finished_at?: string | null;
|
|
42
|
+
command?: string | null;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
export declare class JobsApiClient {
|
|
46
|
+
private workspace;
|
|
47
|
+
private baseUrl;
|
|
48
|
+
constructor(workspace: WorkspaceResolution, baseUrl: string);
|
|
49
|
+
private fetchJson;
|
|
50
|
+
private postJson;
|
|
51
|
+
listJobs(params: {
|
|
52
|
+
status?: string;
|
|
53
|
+
type?: string;
|
|
54
|
+
project?: string;
|
|
55
|
+
since?: string;
|
|
56
|
+
limit?: number;
|
|
57
|
+
}): Promise<JobsApiJob[] | undefined>;
|
|
58
|
+
getJob(jobId: string): Promise<JobsApiJob | undefined>;
|
|
59
|
+
getCheckpoint(jobId: string): Promise<Record<string, unknown> | undefined>;
|
|
60
|
+
getLogs(jobId: string, params: {
|
|
61
|
+
since?: string;
|
|
62
|
+
after?: {
|
|
63
|
+
timestamp: string;
|
|
64
|
+
sequence?: number | null;
|
|
65
|
+
};
|
|
66
|
+
}): Promise<JobsApiLogs | undefined>;
|
|
67
|
+
getTasksSummary(jobId: string): Promise<JobsApiTasksSummary | undefined>;
|
|
68
|
+
cancelJob(jobId: string, options?: {
|
|
69
|
+
force?: boolean;
|
|
70
|
+
reason?: string;
|
|
71
|
+
}): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=JobsApiClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JobsApiClient.d.ts","sourceRoot":"","sources":["../../../src/services/jobs/JobsApiClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export class JobsApiClient {
|
|
2
|
+
constructor(workspace, baseUrl) {
|
|
3
|
+
this.workspace = workspace;
|
|
4
|
+
this.baseUrl = baseUrl;
|
|
5
|
+
}
|
|
6
|
+
async fetchJson(path, query) {
|
|
7
|
+
try {
|
|
8
|
+
const url = new URL(path, this.baseUrl);
|
|
9
|
+
if (query) {
|
|
10
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
11
|
+
if (value === undefined)
|
|
12
|
+
return;
|
|
13
|
+
url.searchParams.set(key, String(value));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const resp = await fetch(url.toString(), { headers: { accept: "application/json" } });
|
|
17
|
+
if (!resp.ok)
|
|
18
|
+
return undefined;
|
|
19
|
+
return (await resp.json());
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async postJson(path, body) {
|
|
26
|
+
try {
|
|
27
|
+
const resp = await fetch(new URL(path, this.baseUrl), {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
30
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
31
|
+
});
|
|
32
|
+
if (!resp.ok)
|
|
33
|
+
return undefined;
|
|
34
|
+
return (await resp.json());
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async listJobs(params) {
|
|
41
|
+
return this.fetchJson("/jobs", params);
|
|
42
|
+
}
|
|
43
|
+
async getJob(jobId) {
|
|
44
|
+
return this.fetchJson(`/jobs/${jobId}`);
|
|
45
|
+
}
|
|
46
|
+
async getCheckpoint(jobId) {
|
|
47
|
+
return this.fetchJson(`/jobs/${jobId}/checkpoint`);
|
|
48
|
+
}
|
|
49
|
+
async getLogs(jobId, params) {
|
|
50
|
+
const sequence = params.after && typeof params.after.sequence === "number" ? params.after.sequence : undefined;
|
|
51
|
+
const after = params.after?.timestamp;
|
|
52
|
+
return this.fetchJson(`/jobs/${jobId}/logs`, {
|
|
53
|
+
since: params.since,
|
|
54
|
+
after: after || undefined,
|
|
55
|
+
sequence,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async getTasksSummary(jobId) {
|
|
59
|
+
return this.fetchJson(`/jobs/${jobId}/tasks/summary`);
|
|
60
|
+
}
|
|
61
|
+
async cancelJob(jobId, options = {}) {
|
|
62
|
+
await this.postJson(`/jobs/${jobId}/cancel`, {
|
|
63
|
+
force: options.force ?? false,
|
|
64
|
+
reason: options.reason ?? (options.force ? "force" : "user"),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AgentService } from "@mcoda/agents";
|
|
2
|
+
import { DocdexClient } from "@mcoda/integrations";
|
|
3
|
+
import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
|
|
4
|
+
import { JobService } from "../jobs/JobService.js";
|
|
5
|
+
import { RoutingService } from "../agents/RoutingService.js";
|
|
6
|
+
export interface GenerateOpenapiOptions {
|
|
7
|
+
workspace: WorkspaceResolution;
|
|
8
|
+
agentName?: string;
|
|
9
|
+
agentStream?: boolean;
|
|
10
|
+
force?: boolean;
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
validateOnly?: boolean;
|
|
13
|
+
cliVersion: string;
|
|
14
|
+
onToken?: (token: string) => void;
|
|
15
|
+
projectKey?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface GenerateOpenapiResult {
|
|
18
|
+
jobId: string;
|
|
19
|
+
commandRunId: string;
|
|
20
|
+
outputPath?: string;
|
|
21
|
+
spec: string;
|
|
22
|
+
docdexId?: string;
|
|
23
|
+
warnings: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare class OpenApiService {
|
|
26
|
+
private docdex;
|
|
27
|
+
private jobService;
|
|
28
|
+
private agentService;
|
|
29
|
+
private routingService;
|
|
30
|
+
private workspace;
|
|
31
|
+
constructor(workspace: WorkspaceResolution, deps: {
|
|
32
|
+
docdex?: DocdexClient;
|
|
33
|
+
jobService?: JobService;
|
|
34
|
+
agentService: AgentService;
|
|
35
|
+
routingService: RoutingService;
|
|
36
|
+
noTelemetry?: boolean;
|
|
37
|
+
});
|
|
38
|
+
static create(workspace: WorkspaceResolution, options?: {
|
|
39
|
+
noTelemetry?: boolean;
|
|
40
|
+
}): Promise<OpenApiService>;
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
private resolveAgent;
|
|
43
|
+
private invokeAgent;
|
|
44
|
+
private sanitizeOutput;
|
|
45
|
+
private validateSpec;
|
|
46
|
+
private runOpenapiValidator;
|
|
47
|
+
private buildPrompt;
|
|
48
|
+
private ensureOpenapiDir;
|
|
49
|
+
private backupIfNeeded;
|
|
50
|
+
private registerOpenapi;
|
|
51
|
+
private validateExistingSpec;
|
|
52
|
+
generateFromDocs(options: GenerateOpenapiOptions): Promise<GenerateOpenapiResult>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=OpenApiService.d.ts.map
|