pi-harness-runtime 1.1.429 → 1.1.430
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/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/dist/runtime-api.d.ts +61 -6
- package/packages/runtime/dist/runtime-api.d.ts.map +1 -1
- package/packages/runtime/dist/runtime-api.js +83 -30
- package/packages/runtime/dist/runtime-api.js.map +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/runtime/src/runtime-api.ts +168 -52
- package/packages/runtime/test/runtime-api.test.ts +169 -0
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/tooling-preference/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/two-phase-review/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/typecheck-packages.mjs +1 -1
- package/harness/task-graph.d.ts +0 -51
- package/packages/runtime/command-executor.ts +0 -523
- package/packages/runtime/index.ts +0 -50
- package/packages/runtime/local-runtime-agent.ts +0 -480
- package/packages/runtime/policy-engine.ts +0 -813
- package/packages/runtime/runtime-api.ts +0 -665
- package/packages/runtime/workspace-manager.ts +0 -591
- package/packages/types/src/ai-providers.js +0 -66
- package/packages/types/src/runtime-types.js +0 -3
|
@@ -1,665 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime API — RFC-0027
|
|
3
|
-
*
|
|
4
|
-
* HTTP/RPC API for runtime control, job management, and status queries.
|
|
5
|
-
*
|
|
6
|
-
* Endpoints:
|
|
7
|
-
* - GET /health - Health check
|
|
8
|
-
* - GET /jobs - List jobs
|
|
9
|
-
* - POST /jobs - Create job
|
|
10
|
-
* - GET /jobs/:id - Get job status
|
|
11
|
-
* - POST /jobs/:id/start - Start job
|
|
12
|
-
* - POST /jobs/:id/pause - Pause job
|
|
13
|
-
* - POST /jobs/:id/resume - Resume job
|
|
14
|
-
* - POST /jobs/:id/cancel - Cancel job
|
|
15
|
-
* - GET /jobs/:id/tasks - List tasks
|
|
16
|
-
* - GET /jobs/:id/tasks/:tid - Get task status
|
|
17
|
-
* - POST /jobs/:id/tasks/:tid - Update task
|
|
18
|
-
* - GET /quota - Get quota status
|
|
19
|
-
* - GET /config - Get runtime config
|
|
20
|
-
* - PUT /config - Update runtime config
|
|
21
|
-
* - WS /ws - WebSocket for real-time events
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { createServer, type Server, type IncomingMessage } from "node:http";
|
|
25
|
-
import { WebSocketServer, WebSocket } from "ws";
|
|
26
|
-
import { URL } from "node:url";
|
|
27
|
-
import { EventEmitter } from "node:events";
|
|
28
|
-
import type { JobStateMachine } from "../../harness/job-state-machine.js";
|
|
29
|
-
import type { TaskGraphManager } from "../../harness/task-graph.js";
|
|
30
|
-
import type { CheckpointManager } from "../../harness/job-state-machine.js";
|
|
31
|
-
|
|
32
|
-
export interface RuntimeApiConfig {
|
|
33
|
-
/** Port to listen on (default: 3849) */
|
|
34
|
-
port?: number;
|
|
35
|
-
/** Host to bind to (default: localhost) */
|
|
36
|
-
host?: string;
|
|
37
|
-
/** API key for authentication (optional) */
|
|
38
|
-
apiKey?: string;
|
|
39
|
-
/** Enable CORS (default: false) */
|
|
40
|
-
cors?: boolean;
|
|
41
|
-
/** Allowed origins for CORS */
|
|
42
|
-
corsOrigins?: string[];
|
|
43
|
-
/** Enable WebSocket (default: true) */
|
|
44
|
-
enableWebSocket?: boolean;
|
|
45
|
-
/** Request timeout in ms (default: 30000) */
|
|
46
|
-
requestTimeoutMs?: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface ApiResponse<T = unknown> {
|
|
50
|
-
success: boolean;
|
|
51
|
-
data?: T;
|
|
52
|
-
error?: string;
|
|
53
|
-
timestamp: string;
|
|
54
|
-
requestId: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface JobSummary {
|
|
58
|
-
jobId: string;
|
|
59
|
-
requirement: string;
|
|
60
|
-
status: string;
|
|
61
|
-
createdAt: string;
|
|
62
|
-
updatedAt: string;
|
|
63
|
-
progress: {
|
|
64
|
-
total: number;
|
|
65
|
-
done: number;
|
|
66
|
-
running: number;
|
|
67
|
-
failed: number;
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface TaskSummary {
|
|
72
|
-
taskId: string;
|
|
73
|
-
title: string;
|
|
74
|
-
status: string;
|
|
75
|
-
assignedAgent?: string;
|
|
76
|
-
dependencies: string[];
|
|
77
|
-
createdAt: string;
|
|
78
|
-
updatedAt: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface RuntimeStatus {
|
|
82
|
-
running: boolean;
|
|
83
|
-
version: string;
|
|
84
|
-
uptime: number;
|
|
85
|
-
jobs: {
|
|
86
|
-
total: number;
|
|
87
|
-
active: number;
|
|
88
|
-
paused: number;
|
|
89
|
-
completed: number;
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const DEFAULT_CONFIG: Required<
|
|
94
|
-
Omit<RuntimeApiConfig, "apiKey" | "corsOrigins">
|
|
95
|
-
> = {
|
|
96
|
-
port: 3849,
|
|
97
|
-
host: "localhost",
|
|
98
|
-
cors: false,
|
|
99
|
-
enableWebSocket: true,
|
|
100
|
-
requestTimeoutMs: 30000,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export class RuntimeApi extends EventEmitter {
|
|
104
|
-
private readonly config: RuntimeApiConfig;
|
|
105
|
-
private server: Server | null = null;
|
|
106
|
-
private wsServer: WebSocketServer | null = null;
|
|
107
|
-
private wsClients: Set<WebSocket> = new Set();
|
|
108
|
-
private isRunning = false;
|
|
109
|
-
private startTime: number = 0;
|
|
110
|
-
private jobMachines: Map<string, JobStateMachine> = new Map();
|
|
111
|
-
private taskGraphs: Map<string, TaskGraphManager> = new Map();
|
|
112
|
-
private checkpoints: Map<string, CheckpointManager> = new Map();
|
|
113
|
-
private requestCounter = 0;
|
|
114
|
-
|
|
115
|
-
constructor(config: RuntimeApiConfig = {}) {
|
|
116
|
-
super();
|
|
117
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
118
|
-
this.startTime = Date.now();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Start the Runtime API server
|
|
123
|
-
*/
|
|
124
|
-
async start(): Promise<void> {
|
|
125
|
-
if (this.isRunning) {
|
|
126
|
-
throw new Error("Runtime API is already running");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return new Promise((resolve, reject) => {
|
|
130
|
-
this.server = createServer(async (req, res) => {
|
|
131
|
-
const requestId = this.generateRequestId();
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
// Set timeout
|
|
135
|
-
req.setTimeout(this.config.requestTimeoutMs ?? 30000);
|
|
136
|
-
|
|
137
|
-
// Handle CORS
|
|
138
|
-
if (this.config.cors) {
|
|
139
|
-
this.setCorsHeaders(req, res);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Authenticate
|
|
143
|
-
if (!this.authenticate(req, res)) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Handle request
|
|
148
|
-
await this.handleRequest(req, res, requestId);
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.error(`[RuntimeApi] Request error: ${error}`);
|
|
151
|
-
this.sendJson(
|
|
152
|
-
res,
|
|
153
|
-
500,
|
|
154
|
-
{ error: "Internal server error" },
|
|
155
|
-
requestId,
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
this.server.on("error", (error) => {
|
|
161
|
-
reject(error);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
this.server.listen(this.config.port, this.config.host, () => {
|
|
165
|
-
this.isRunning = true;
|
|
166
|
-
this.startTime = Date.now();
|
|
167
|
-
console.log(
|
|
168
|
-
`[RuntimeApi] HTTP server listening on ${this.config.host}:${this.config.port}`,
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
// Start WebSocket server
|
|
172
|
-
if (this.config.enableWebSocket) {
|
|
173
|
-
this.startWebSocket();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
resolve();
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Stop the Runtime API server
|
|
183
|
-
*/
|
|
184
|
-
async stop(): Promise<void> {
|
|
185
|
-
return new Promise((resolve) => {
|
|
186
|
-
// Close WebSocket server
|
|
187
|
-
if (this.wsServer) {
|
|
188
|
-
this.wsServer.close();
|
|
189
|
-
this.wsServer = null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Close all WebSocket clients
|
|
193
|
-
for (const client of Array.from(this.wsClients)) {
|
|
194
|
-
client.close();
|
|
195
|
-
}
|
|
196
|
-
this.wsClients.clear();
|
|
197
|
-
|
|
198
|
-
// Close HTTP server
|
|
199
|
-
if (this.server) {
|
|
200
|
-
this.server.close(() => {
|
|
201
|
-
this.isRunning = false;
|
|
202
|
-
this.server = null;
|
|
203
|
-
console.log("[RuntimeApi] Stopped");
|
|
204
|
-
resolve();
|
|
205
|
-
});
|
|
206
|
-
} else {
|
|
207
|
-
resolve();
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Register a job machine
|
|
214
|
-
*/
|
|
215
|
-
registerJob(jobId: string, machine: JobStateMachine): void {
|
|
216
|
-
this.jobMachines.set(jobId, machine);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Register a task graph
|
|
221
|
-
*/
|
|
222
|
-
registerTaskGraph(jobId: string, graph: TaskGraphManager): void {
|
|
223
|
-
this.taskGraphs.set(jobId, graph);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Register a checkpoint manager
|
|
228
|
-
*/
|
|
229
|
-
registerCheckpointManager(jobId: string, manager: CheckpointManager): void {
|
|
230
|
-
this.checkpoints.set(jobId, manager);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Emit an event to all WebSocket clients
|
|
235
|
-
*/
|
|
236
|
-
broadcast(event: string, data: unknown): void {
|
|
237
|
-
const message = JSON.stringify({
|
|
238
|
-
event,
|
|
239
|
-
data,
|
|
240
|
-
timestamp: new Date().toISOString(),
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
for (const client of Array.from(this.wsClients)) {
|
|
244
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
245
|
-
client.send(message);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// ─── Private Methods ────────────────────────────────────────────────
|
|
251
|
-
|
|
252
|
-
private startWebSocket(): void {
|
|
253
|
-
this.wsServer = new WebSocketServer({
|
|
254
|
-
noServer: true,
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
this.server?.on("upgrade", (request, socket, head) => {
|
|
258
|
-
const url = new URL(request.url ?? "/", `http://${request.headers.host}`);
|
|
259
|
-
if (url.pathname === "/ws") {
|
|
260
|
-
this.wsServer?.handleUpgrade(request, socket, head, (ws) => {
|
|
261
|
-
this.wsServer?.emit("connection", ws, request);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
this.wsServer.on("connection", (ws: WebSocket, _req: IncomingMessage) => {
|
|
267
|
-
this.wsClients.add(ws);
|
|
268
|
-
console.log("[RuntimeApi] WebSocket client connected");
|
|
269
|
-
|
|
270
|
-
ws.on("close", () => {
|
|
271
|
-
this.wsClients.delete(ws);
|
|
272
|
-
console.log("[RuntimeApi] WebSocket client disconnected");
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
ws.on("error", (error: Error) => {
|
|
276
|
-
console.error("[RuntimeApi] WebSocket error:", error);
|
|
277
|
-
this.wsClients.delete(ws);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Send welcome message
|
|
281
|
-
ws.send(
|
|
282
|
-
JSON.stringify({
|
|
283
|
-
event: "connected",
|
|
284
|
-
data: { message: "Connected to Runtime API" },
|
|
285
|
-
timestamp: new Date().toISOString(),
|
|
286
|
-
}),
|
|
287
|
-
);
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
private authenticate(
|
|
292
|
-
req: IncomingMessage,
|
|
293
|
-
res: import("node:http").ServerResponse,
|
|
294
|
-
): boolean {
|
|
295
|
-
if (!this.config.apiKey) {
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const apiKey = req.headers["x-api-key"];
|
|
300
|
-
if (apiKey !== this.config.apiKey) {
|
|
301
|
-
this.sendJson(
|
|
302
|
-
res,
|
|
303
|
-
401,
|
|
304
|
-
{ error: "Unauthorized" },
|
|
305
|
-
this.generateRequestId(),
|
|
306
|
-
);
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private setCorsHeaders(
|
|
314
|
-
req: IncomingMessage,
|
|
315
|
-
res: import("node:http").ServerResponse,
|
|
316
|
-
): void {
|
|
317
|
-
const origin = req.headers.origin;
|
|
318
|
-
const allowedOrigins = this.config.corsOrigins ?? ["*"];
|
|
319
|
-
|
|
320
|
-
if (
|
|
321
|
-
allowedOrigins.includes("*") ||
|
|
322
|
-
(origin && allowedOrigins.includes(origin))
|
|
323
|
-
) {
|
|
324
|
-
res.setHeader("Access-Control-Allow-Origin", origin ?? "*");
|
|
325
|
-
res.setHeader(
|
|
326
|
-
"Access-Control-Allow-Methods",
|
|
327
|
-
"GET, POST, PUT, DELETE, OPTIONS",
|
|
328
|
-
);
|
|
329
|
-
res.setHeader(
|
|
330
|
-
"Access-Control-Allow-Headers",
|
|
331
|
-
"Content-Type, Authorization, X-Api-Key",
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private async handleRequest(
|
|
337
|
-
req: IncomingMessage,
|
|
338
|
-
res: import("node:http").ServerResponse,
|
|
339
|
-
requestId: string,
|
|
340
|
-
): Promise<void> {
|
|
341
|
-
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
342
|
-
const path = url.pathname;
|
|
343
|
-
const method = req.method ?? "GET";
|
|
344
|
-
|
|
345
|
-
// Handle OPTIONS for CORS
|
|
346
|
-
if (method === "OPTIONS") {
|
|
347
|
-
res.writeHead(204);
|
|
348
|
-
res.end();
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Route requests
|
|
353
|
-
if (path === "/health" && method === "GET") {
|
|
354
|
-
this.handleHealth(res, requestId);
|
|
355
|
-
} else if (path === "/status" && method === "GET") {
|
|
356
|
-
this.handleStatus(res, requestId);
|
|
357
|
-
} else if (path === "/jobs" && method === "GET") {
|
|
358
|
-
await this.handleListJobs(res, requestId);
|
|
359
|
-
} else if (path === "/jobs" && method === "POST") {
|
|
360
|
-
await this.handleCreateJob(req, res, requestId);
|
|
361
|
-
} else if (path === "/quota" && method === "GET") {
|
|
362
|
-
await this.handleGetQuota(res, requestId);
|
|
363
|
-
} else if (path === "/config" && method === "GET") {
|
|
364
|
-
this.handleGetConfig(res, requestId);
|
|
365
|
-
} else if (path.match(/^\/jobs\/([^/]+)\/?$/)) {
|
|
366
|
-
const jobId = path.match(/^\/jobs\/([^/]+)\/?$/)?.[1];
|
|
367
|
-
if (jobId) {
|
|
368
|
-
await this.handleJob(jobId, method, url, req, res, requestId);
|
|
369
|
-
}
|
|
370
|
-
} else {
|
|
371
|
-
this.sendJson(res, 404, { error: "Not found" }, requestId);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
private handleHealth(
|
|
376
|
-
res: import("node:http").ServerResponse,
|
|
377
|
-
requestId: string,
|
|
378
|
-
): void {
|
|
379
|
-
this.sendJson(res, 200, { status: "healthy" }, requestId);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
private handleStatus(
|
|
383
|
-
res: import("node:http").ServerResponse,
|
|
384
|
-
requestId: string,
|
|
385
|
-
): void {
|
|
386
|
-
const status: RuntimeStatus = {
|
|
387
|
-
running: this.isRunning,
|
|
388
|
-
version: "1.0.0",
|
|
389
|
-
uptime: Date.now() - this.startTime,
|
|
390
|
-
jobs: {
|
|
391
|
-
total: this.jobMachines.size,
|
|
392
|
-
active: 0,
|
|
393
|
-
paused: 0,
|
|
394
|
-
completed: 0,
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
// Count job statuses
|
|
399
|
-
for (const machine of Array.from(this.jobMachines.values())) {
|
|
400
|
-
const summary = machine.getStatusSummary();
|
|
401
|
-
if (summary) {
|
|
402
|
-
if (summary.status === "running") status.jobs.active++;
|
|
403
|
-
else if (
|
|
404
|
-
summary.status === "paused" ||
|
|
405
|
-
summary.status === "paused_quota"
|
|
406
|
-
)
|
|
407
|
-
status.jobs.paused++;
|
|
408
|
-
else if (summary.isTerminal) status.jobs.completed++;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
this.sendJson(res, 200, status, requestId);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
private async handleListJobs(
|
|
416
|
-
res: import("node:http").ServerResponse,
|
|
417
|
-
requestId: string,
|
|
418
|
-
): Promise<void> {
|
|
419
|
-
const jobs: JobSummary[] = [];
|
|
420
|
-
|
|
421
|
-
for (const entry of Array.from(this.jobMachines.entries())) {
|
|
422
|
-
const [jobId, machine] = entry;
|
|
423
|
-
const checkpoint = await machine.getCheckpoint();
|
|
424
|
-
const graph = this.taskGraphs.get(jobId);
|
|
425
|
-
|
|
426
|
-
// Get progress from task graph
|
|
427
|
-
let progress = { total: 0, done: 0, running: 0, failed: 0 };
|
|
428
|
-
if (graph) {
|
|
429
|
-
try {
|
|
430
|
-
const tasks = graph.getAllTasks();
|
|
431
|
-
progress = {
|
|
432
|
-
total: tasks.length,
|
|
433
|
-
done: tasks.filter((t) => t.status === "done").length,
|
|
434
|
-
running: tasks.filter((t) => t.status === "running").length,
|
|
435
|
-
failed: tasks.filter((t) => t.status === "failed").length,
|
|
436
|
-
};
|
|
437
|
-
} catch {
|
|
438
|
-
// Fallback to default
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
jobs.push({
|
|
443
|
-
jobId,
|
|
444
|
-
requirement: checkpoint?.requirement ?? "Unknown",
|
|
445
|
-
status: machine.getStatusSummary()?.status ?? "unknown",
|
|
446
|
-
createdAt: checkpoint?.createdAt ?? new Date().toISOString(),
|
|
447
|
-
updatedAt: checkpoint?.updatedAt ?? new Date().toISOString(),
|
|
448
|
-
progress,
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
this.sendJson(res, 200, { jobs }, requestId);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
private async handleCreateJob(
|
|
456
|
-
_req: IncomingMessage,
|
|
457
|
-
res: import("node:http").ServerResponse,
|
|
458
|
-
requestId: string,
|
|
459
|
-
): Promise<void> {
|
|
460
|
-
let body = "";
|
|
461
|
-
|
|
462
|
-
for await (const chunk of _req) {
|
|
463
|
-
body += chunk;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
let data: { requirement?: string };
|
|
467
|
-
try {
|
|
468
|
-
data = JSON.parse(body);
|
|
469
|
-
} catch {
|
|
470
|
-
this.sendJson(res, 400, { error: "Invalid JSON" }, requestId);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
if (!data.requirement) {
|
|
475
|
-
this.sendJson(
|
|
476
|
-
res,
|
|
477
|
-
400,
|
|
478
|
-
{ error: "Missing 'requirement' field" },
|
|
479
|
-
requestId,
|
|
480
|
-
);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// In a real implementation, this would create a new job
|
|
485
|
-
// For now, just acknowledge the request
|
|
486
|
-
this.sendJson(
|
|
487
|
-
res,
|
|
488
|
-
201,
|
|
489
|
-
{
|
|
490
|
-
message: "Job creation not implemented - use harness CLI",
|
|
491
|
-
requirement: data.requirement,
|
|
492
|
-
},
|
|
493
|
-
requestId,
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
private async handleJob(
|
|
498
|
-
jobId: string,
|
|
499
|
-
method: string,
|
|
500
|
-
url: URL,
|
|
501
|
-
req: IncomingMessage,
|
|
502
|
-
res: import("node:http").ServerResponse,
|
|
503
|
-
requestId: string,
|
|
504
|
-
): Promise<void> {
|
|
505
|
-
const machine = this.jobMachines.get(jobId);
|
|
506
|
-
|
|
507
|
-
if (!machine) {
|
|
508
|
-
this.sendJson(res, 404, { error: "Job not found" }, requestId);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Get job details
|
|
513
|
-
if (method === "GET") {
|
|
514
|
-
const checkpoint = await machine.getCheckpoint();
|
|
515
|
-
const graph = this.taskGraphs.get(jobId);
|
|
516
|
-
// Get progress from task graph
|
|
517
|
-
let progress = { total: 0, done: 0, running: 0, failed: 0 };
|
|
518
|
-
if (graph) {
|
|
519
|
-
try {
|
|
520
|
-
const tasks = graph.getAllTasks();
|
|
521
|
-
progress = {
|
|
522
|
-
total: tasks.length,
|
|
523
|
-
done: tasks.filter((t) => t.status === "done").length,
|
|
524
|
-
running: tasks.filter((t) => t.status === "running").length,
|
|
525
|
-
failed: tasks.filter((t) => t.status === "failed").length,
|
|
526
|
-
};
|
|
527
|
-
} catch {
|
|
528
|
-
// Fallback to default
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
this.sendJson(
|
|
533
|
-
res,
|
|
534
|
-
200,
|
|
535
|
-
{
|
|
536
|
-
jobId,
|
|
537
|
-
requirement: checkpoint?.requirement ?? "Unknown",
|
|
538
|
-
status: machine.getStatusSummary()?.status ?? "unknown",
|
|
539
|
-
canResume: machine.getStatusSummary()?.canResume ?? false,
|
|
540
|
-
isTerminal: machine.getStatusSummary()?.isTerminal ?? false,
|
|
541
|
-
progress,
|
|
542
|
-
checkpoint,
|
|
543
|
-
},
|
|
544
|
-
requestId,
|
|
545
|
-
);
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Job actions
|
|
550
|
-
const action = url.searchParams.get("action");
|
|
551
|
-
|
|
552
|
-
switch (method) {
|
|
553
|
-
case "POST":
|
|
554
|
-
if (action === "start") {
|
|
555
|
-
await machine.resumeJob(jobId);
|
|
556
|
-
this.broadcast("job.started", { jobId });
|
|
557
|
-
this.sendJson(res, 200, { message: "Job started", jobId }, requestId);
|
|
558
|
-
} else if (action === "pause") {
|
|
559
|
-
const result = await machine.transition("paused");
|
|
560
|
-
this.broadcast("job.paused", { jobId });
|
|
561
|
-
this.sendJson(
|
|
562
|
-
res,
|
|
563
|
-
200,
|
|
564
|
-
{ message: "Job paused", jobId, result },
|
|
565
|
-
requestId,
|
|
566
|
-
);
|
|
567
|
-
} else if (action === "resume") {
|
|
568
|
-
await machine.resumeJob(jobId);
|
|
569
|
-
this.broadcast("job.resumed", { jobId });
|
|
570
|
-
this.sendJson(res, 200, { message: "Job resumed", jobId }, requestId);
|
|
571
|
-
} else if (action === "cancel") {
|
|
572
|
-
const result = await machine.transition("cancelled");
|
|
573
|
-
this.broadcast("job.cancelled", { jobId });
|
|
574
|
-
this.sendJson(
|
|
575
|
-
res,
|
|
576
|
-
200,
|
|
577
|
-
{ message: "Job cancelled", jobId, result },
|
|
578
|
-
requestId,
|
|
579
|
-
);
|
|
580
|
-
} else {
|
|
581
|
-
this.sendJson(res, 400, { error: "Unknown action" }, requestId);
|
|
582
|
-
}
|
|
583
|
-
break;
|
|
584
|
-
|
|
585
|
-
default:
|
|
586
|
-
this.sendJson(res, 405, { error: "Method not allowed" }, requestId);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
private async handleGetQuota(
|
|
591
|
-
res: import("node:http").ServerResponse,
|
|
592
|
-
requestId: string,
|
|
593
|
-
): Promise<void> {
|
|
594
|
-
// In a real implementation, this would query the quota manager
|
|
595
|
-
this.sendJson(
|
|
596
|
-
res,
|
|
597
|
-
200,
|
|
598
|
-
{
|
|
599
|
-
message: "Quota endpoint - integrate with packages/quota-manager",
|
|
600
|
-
usage: {
|
|
601
|
-
h5_used_pct: 0,
|
|
602
|
-
weekly_used_pct: 0,
|
|
603
|
-
},
|
|
604
|
-
},
|
|
605
|
-
requestId,
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
private handleGetConfig(
|
|
610
|
-
res: import("node:http").ServerResponse,
|
|
611
|
-
requestId: string,
|
|
612
|
-
): void {
|
|
613
|
-
// Don't expose sensitive config
|
|
614
|
-
this.sendJson(
|
|
615
|
-
res,
|
|
616
|
-
200,
|
|
617
|
-
{
|
|
618
|
-
port: this.config.port,
|
|
619
|
-
host: this.config.host,
|
|
620
|
-
cors: this.config.cors,
|
|
621
|
-
enableWebSocket: this.config.enableWebSocket,
|
|
622
|
-
requestTimeoutMs: this.config.requestTimeoutMs,
|
|
623
|
-
// Expose version without exposing apiKey
|
|
624
|
-
version: "1.0.0",
|
|
625
|
-
},
|
|
626
|
-
requestId,
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
private sendJson(
|
|
631
|
-
res: import("node:http").ServerResponse,
|
|
632
|
-
status: number,
|
|
633
|
-
data: unknown,
|
|
634
|
-
requestId: string,
|
|
635
|
-
): void {
|
|
636
|
-
const response: ApiResponse = {
|
|
637
|
-
success: status >= 200 && status < 300,
|
|
638
|
-
...(status >= 200 && status < 300
|
|
639
|
-
? { data }
|
|
640
|
-
: { error: (data as { error?: string }).error }),
|
|
641
|
-
timestamp: new Date().toISOString(),
|
|
642
|
-
requestId,
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
res.writeHead(status, { "Content-Type": "application/json" });
|
|
646
|
-
res.end(JSON.stringify(response));
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
private generateRequestId(): string {
|
|
650
|
-
this.requestCounter++;
|
|
651
|
-
return `req_${Date.now().toString(36)}_${this.requestCounter.toString(36)}`;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Create RuntimeApi with default config for harness runtime
|
|
657
|
-
*/
|
|
658
|
-
export function createHarnessRuntimeApi(): RuntimeApi {
|
|
659
|
-
return new RuntimeApi({
|
|
660
|
-
port: 3849,
|
|
661
|
-
host: "localhost",
|
|
662
|
-
enableWebSocket: true,
|
|
663
|
-
requestTimeoutMs: 30000,
|
|
664
|
-
});
|
|
665
|
-
}
|