meshy-node 0.0.5 → 0.0.6
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/README.md +1 -1
- package/dashboard/assets/DashboardPage-Cq9h6J6k.js +200 -0
- package/dashboard/assets/{DiffTab-BnEEacma.js → DiffTab-B22VET7i.js} +4 -4
- package/dashboard/assets/{FilesTab-BA_AQGff.js → FilesTab-DkODTD1c.js} +2 -2
- package/dashboard/assets/{PreviewTab-F2z-8pM5.js → PreviewTab-CtdlW-8Z.js} +1 -1
- package/dashboard/assets/{folder-TnLGJD38.js → folder-D6MCA6XV.js} +1 -1
- package/dashboard/assets/{index-Cf2Ckzj8.js → index-Br1FoGY0.js} +3 -3
- package/dashboard/index.html +1 -1
- package/main.cjs +266 -45
- package/package.json +1 -1
- package/dashboard/assets/DashboardPage-Du861ESc.js +0 -200
package/dashboard/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Meshy Dashboard</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🕸</text></svg>" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Br1FoGY0.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-CS0F09J4.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/main.cjs
CHANGED
|
@@ -23672,6 +23672,10 @@ var NodeRegistry = class {
|
|
|
23672
23672
|
endpoint: info.endpoint
|
|
23673
23673
|
});
|
|
23674
23674
|
}
|
|
23675
|
+
upsertNode(info) {
|
|
23676
|
+
this.nodes.set(info.id, { ...info });
|
|
23677
|
+
this.store.upsertNode(info);
|
|
23678
|
+
}
|
|
23675
23679
|
removeNode(id) {
|
|
23676
23680
|
this.nodes.delete(id);
|
|
23677
23681
|
this.store.removeNode(id);
|
|
@@ -24092,6 +24096,44 @@ var TaskEngine = class {
|
|
|
24092
24096
|
const tasks = this.store.getAllTasks(filter);
|
|
24093
24097
|
return { tasks, total: tasks.length };
|
|
24094
24098
|
}
|
|
24099
|
+
importTasks(tasks) {
|
|
24100
|
+
let created = 0;
|
|
24101
|
+
let updated = 0;
|
|
24102
|
+
for (const task of tasks) {
|
|
24103
|
+
const existing = this.store.getTask(task.id);
|
|
24104
|
+
if (!existing) {
|
|
24105
|
+
this.store.createTask(task);
|
|
24106
|
+
created++;
|
|
24107
|
+
this.eventBus.emit("task.created", {
|
|
24108
|
+
taskId: task.id,
|
|
24109
|
+
title: task.title,
|
|
24110
|
+
priority: task.priority
|
|
24111
|
+
});
|
|
24112
|
+
this.eventBus.emit("task.status", {
|
|
24113
|
+
taskId: task.id,
|
|
24114
|
+
status: task.status,
|
|
24115
|
+
...task.result ? { result: task.result } : {},
|
|
24116
|
+
...task.error ? { error: task.error } : {}
|
|
24117
|
+
});
|
|
24118
|
+
continue;
|
|
24119
|
+
}
|
|
24120
|
+
if (existing.updatedAt > task.updatedAt) {
|
|
24121
|
+
continue;
|
|
24122
|
+
}
|
|
24123
|
+
const previousStatus = existing.status;
|
|
24124
|
+
this.store.updateTask(task.id, task);
|
|
24125
|
+
updated++;
|
|
24126
|
+
if (task.status !== previousStatus) {
|
|
24127
|
+
this.eventBus.emit("task.status", {
|
|
24128
|
+
taskId: task.id,
|
|
24129
|
+
status: task.status,
|
|
24130
|
+
...task.result ? { result: task.result } : {},
|
|
24131
|
+
...task.error ? { error: task.error } : {}
|
|
24132
|
+
});
|
|
24133
|
+
}
|
|
24134
|
+
}
|
|
24135
|
+
return { created, updated };
|
|
24136
|
+
}
|
|
24095
24137
|
updateTask(id, updates) {
|
|
24096
24138
|
const existing = this.store.getTask(id);
|
|
24097
24139
|
if (!existing) return null;
|
|
@@ -24789,11 +24831,67 @@ var HeartbeatMonitor = class {
|
|
|
24789
24831
|
}
|
|
24790
24832
|
// ── Leader: handle keepalive from follower ──────────────────────────
|
|
24791
24833
|
handleKeepalive(req) {
|
|
24792
|
-
const {
|
|
24834
|
+
const {
|
|
24835
|
+
nodeId,
|
|
24836
|
+
name,
|
|
24837
|
+
endpoint,
|
|
24838
|
+
status,
|
|
24839
|
+
load,
|
|
24840
|
+
capabilities,
|
|
24841
|
+
transportType,
|
|
24842
|
+
workDir,
|
|
24843
|
+
devtunnelEnabled,
|
|
24844
|
+
devtunnelEndpoint,
|
|
24845
|
+
previewOrigin,
|
|
24846
|
+
workDirFolders
|
|
24847
|
+
} = req;
|
|
24848
|
+
const existingNode = this.nodeRegistry.getNode(nodeId);
|
|
24849
|
+
const normalizedName = name ?? existingNode?.name ?? nodeId;
|
|
24850
|
+
const normalizedEndpoint = endpoint ?? existingNode?.endpoint ?? devtunnelEndpoint ?? "";
|
|
24851
|
+
const normalizedCapabilities = capabilities ?? existingNode?.capabilities ?? [];
|
|
24852
|
+
const currentCapabilities = existingNode?.capabilities ?? [];
|
|
24793
24853
|
this.log.debug("keepalive received", { nodeId, status });
|
|
24794
24854
|
this.followerMissedMap.set(nodeId, 0);
|
|
24795
|
-
|
|
24796
|
-
const
|
|
24855
|
+
let node = existingNode;
|
|
24856
|
+
const now = Date.now();
|
|
24857
|
+
if (!node) {
|
|
24858
|
+
this.nodeRegistry.addNode({
|
|
24859
|
+
id: nodeId,
|
|
24860
|
+
name: normalizedName,
|
|
24861
|
+
endpoint: normalizedEndpoint,
|
|
24862
|
+
role: "follower",
|
|
24863
|
+
status,
|
|
24864
|
+
capabilities: normalizedCapabilities,
|
|
24865
|
+
joinedAt: now,
|
|
24866
|
+
lastHeartbeat: now,
|
|
24867
|
+
...transportType ? { transportType } : {},
|
|
24868
|
+
...workDir ? { workDir } : {},
|
|
24869
|
+
...devtunnelEnabled && devtunnelEndpoint ? { devtunnelEndpoint } : {},
|
|
24870
|
+
...previewOrigin ? { previewOrigin } : {},
|
|
24871
|
+
...workDirFolders ? { workDirFolders } : {}
|
|
24872
|
+
});
|
|
24873
|
+
this.log.info("keepalive restored missing follower", { nodeId, endpoint: normalizedEndpoint });
|
|
24874
|
+
node = this.nodeRegistry.getNode(nodeId);
|
|
24875
|
+
}
|
|
24876
|
+
this.nodeRegistry.updateHeartbeat(nodeId, now);
|
|
24877
|
+
node = this.nodeRegistry.getNode(nodeId);
|
|
24878
|
+
if (node && normalizedEndpoint && node.endpoint !== normalizedEndpoint) {
|
|
24879
|
+
this.nodeRegistry.updateEndpoint(nodeId, normalizedEndpoint);
|
|
24880
|
+
node = this.nodeRegistry.getNode(nodeId);
|
|
24881
|
+
}
|
|
24882
|
+
if (node && (node.name !== normalizedName || node.transportType !== transportType || node.workDir !== workDir || currentCapabilities.length !== normalizedCapabilities.length || currentCapabilities.some((capability, index) => capability !== normalizedCapabilities[index]))) {
|
|
24883
|
+
this.nodeRegistry.upsertNode({
|
|
24884
|
+
...node,
|
|
24885
|
+
name: normalizedName,
|
|
24886
|
+
endpoint: normalizedEndpoint || node.endpoint,
|
|
24887
|
+
status,
|
|
24888
|
+
lastHeartbeat: now,
|
|
24889
|
+
capabilities: normalizedCapabilities,
|
|
24890
|
+
...transportType ? { transportType } : {},
|
|
24891
|
+
...workDir ? { workDir } : {}
|
|
24892
|
+
});
|
|
24893
|
+
node = this.nodeRegistry.getNode(nodeId);
|
|
24894
|
+
}
|
|
24797
24895
|
if (node && node.status === "offline") {
|
|
24798
24896
|
this.nodeRegistry.updateStatus(nodeId, status);
|
|
24799
24897
|
this.log.info("keepalive restored node", { nodeId, status });
|
|
@@ -24840,8 +24938,13 @@ var HeartbeatMonitor = class {
|
|
|
24840
24938
|
const self = this.nodeRegistry.getSelf();
|
|
24841
24939
|
const request = {
|
|
24842
24940
|
nodeId: self.id,
|
|
24941
|
+
name: self.name,
|
|
24942
|
+
endpoint: self.endpoint,
|
|
24843
24943
|
status: self.status,
|
|
24844
24944
|
load: 0,
|
|
24945
|
+
capabilities: self.capabilities,
|
|
24946
|
+
transportType: self.transportType,
|
|
24947
|
+
workDir: self.workDir,
|
|
24845
24948
|
devtunnelEnabled: self.devtunnelEndpoint !== void 0,
|
|
24846
24949
|
devtunnelEndpoint: self.devtunnelEndpoint,
|
|
24847
24950
|
previewOrigin: self.previewOrigin,
|
|
@@ -26191,7 +26294,8 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
|
|
|
26191
26294
|
id: this.selfInfo.id,
|
|
26192
26295
|
endpoint: this.selfInfo.endpoint,
|
|
26193
26296
|
name: this.config.node.name,
|
|
26194
|
-
capabilities: []
|
|
26297
|
+
capabilities: [],
|
|
26298
|
+
tasks: this.taskEngine.listTasks().tasks
|
|
26195
26299
|
};
|
|
26196
26300
|
if (this.selfInfo.devtunnelEndpoint) {
|
|
26197
26301
|
joinBody.devtunnelEndpoint = this.selfInfo.devtunnelEndpoint;
|
|
@@ -26212,13 +26316,36 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
|
|
|
26212
26316
|
throw new Error(`Failed to join cluster via ${seedUrl}: ${response.status}`);
|
|
26213
26317
|
}
|
|
26214
26318
|
const data = await response.json();
|
|
26215
|
-
|
|
26216
|
-
|
|
26217
|
-
|
|
26218
|
-
|
|
26219
|
-
|
|
26319
|
+
this.applyClusterState(data.nodes, data.leaderId, data.term);
|
|
26320
|
+
}
|
|
26321
|
+
async leaveCluster() {
|
|
26322
|
+
const leader = this.nodeRegistry.getLeader();
|
|
26323
|
+
const leaderEndpoint = this.nodeRegistry.getLeaderEndpoint();
|
|
26324
|
+
const self = this.nodeRegistry.getSelf();
|
|
26325
|
+
if (!leader || !leaderEndpoint || leader.id === self.id) {
|
|
26326
|
+
return;
|
|
26220
26327
|
}
|
|
26221
|
-
|
|
26328
|
+
try {
|
|
26329
|
+
await fetch(`${leaderEndpoint}/api/cluster/leave`, {
|
|
26330
|
+
method: "POST",
|
|
26331
|
+
headers: { "Content-Type": "application/json" },
|
|
26332
|
+
body: JSON.stringify({ nodeId: self.id })
|
|
26333
|
+
});
|
|
26334
|
+
} catch {
|
|
26335
|
+
}
|
|
26336
|
+
const refreshedSelf = {
|
|
26337
|
+
...self,
|
|
26338
|
+
role: "follower",
|
|
26339
|
+
status: "online",
|
|
26340
|
+
lastHeartbeat: Date.now(),
|
|
26341
|
+
workDirFolders: listWorkDirFolders(this.getWorkDir())
|
|
26342
|
+
};
|
|
26343
|
+
this.selfInfo = refreshedSelf;
|
|
26344
|
+
this.nodeRegistry.setSelf(refreshedSelf);
|
|
26345
|
+
this.nodeRegistry.syncFromHeartbeat([refreshedSelf]);
|
|
26346
|
+
this.nodeRegistry.clearLeader();
|
|
26347
|
+
this.election.stepDown();
|
|
26348
|
+
await this.election.startElection();
|
|
26222
26349
|
}
|
|
26223
26350
|
async stop() {
|
|
26224
26351
|
if (!this.started) return;
|
|
@@ -26381,6 +26508,22 @@ ${joinErrors.map((e) => ` - ${e}`).join("\n")}`
|
|
|
26381
26508
|
this.selfInfo.role = current.role;
|
|
26382
26509
|
this.selfInfo.status = current.status;
|
|
26383
26510
|
}
|
|
26511
|
+
applyClusterState(nodes, leaderId, term) {
|
|
26512
|
+
const remoteSelf = nodes.find((node) => node.id === this.selfInfo.id);
|
|
26513
|
+
const nextSelf = {
|
|
26514
|
+
...this.selfInfo,
|
|
26515
|
+
...remoteSelf ?? {},
|
|
26516
|
+
transportType: this.config.transport.type,
|
|
26517
|
+
workDir: this.selfInfo.workDir,
|
|
26518
|
+
workDirFolders: listWorkDirFolders(this.getWorkDir()),
|
|
26519
|
+
...this.selfInfo.devtunnelEndpoint ? { devtunnelEndpoint: this.selfInfo.devtunnelEndpoint } : {},
|
|
26520
|
+
...this.selfInfo.previewOrigin ? { previewOrigin: this.selfInfo.previewOrigin } : {}
|
|
26521
|
+
};
|
|
26522
|
+
this.selfInfo = nextSelf;
|
|
26523
|
+
this.nodeRegistry.setSelf(nextSelf);
|
|
26524
|
+
this.nodeRegistry.syncFromHeartbeat(nodes);
|
|
26525
|
+
this.election.handleLeaderAnnounce({ leaderId, term });
|
|
26526
|
+
}
|
|
26384
26527
|
cloneTransportConfig(config) {
|
|
26385
26528
|
return {
|
|
26386
26529
|
type: config.type,
|
|
@@ -30611,12 +30754,33 @@ var NodeInfoSchema = external_exports.object({
|
|
|
30611
30754
|
devtunnelEndpoint: external_exports.string().optional(),
|
|
30612
30755
|
previewOrigin: external_exports.string().optional()
|
|
30613
30756
|
});
|
|
30757
|
+
var JoinTaskSchema = external_exports.object({
|
|
30758
|
+
id: external_exports.string(),
|
|
30759
|
+
title: external_exports.string(),
|
|
30760
|
+
description: external_exports.string(),
|
|
30761
|
+
agent: external_exports.string(),
|
|
30762
|
+
project: external_exports.string().nullable(),
|
|
30763
|
+
effectiveProjectPath: external_exports.string().nullable(),
|
|
30764
|
+
payload: external_exports.record(external_exports.unknown()),
|
|
30765
|
+
status: external_exports.enum(["pending", "assigned", "running", "completed", "failed", "cancelled", "archived"]),
|
|
30766
|
+
priority: external_exports.enum(["low", "normal", "high", "critical"]),
|
|
30767
|
+
assignedTo: external_exports.string().nullable(),
|
|
30768
|
+
assignedNodeName: external_exports.string().nullable().optional(),
|
|
30769
|
+
createdBy: external_exports.string(),
|
|
30770
|
+
result: external_exports.record(external_exports.unknown()).nullable(),
|
|
30771
|
+
error: external_exports.string().nullable(),
|
|
30772
|
+
retryCount: external_exports.number(),
|
|
30773
|
+
maxRetries: external_exports.number(),
|
|
30774
|
+
createdAt: external_exports.number(),
|
|
30775
|
+
updatedAt: external_exports.number()
|
|
30776
|
+
});
|
|
30614
30777
|
var JoinClusterBody = external_exports.object({
|
|
30615
30778
|
id: external_exports.string().min(1),
|
|
30616
30779
|
endpoint: external_exports.string().url(),
|
|
30617
30780
|
name: external_exports.string().min(1),
|
|
30618
30781
|
capabilities: external_exports.array(external_exports.string()).default([]),
|
|
30619
|
-
devtunnelEndpoint: external_exports.string().url().optional()
|
|
30782
|
+
devtunnelEndpoint: external_exports.string().url().optional(),
|
|
30783
|
+
tasks: external_exports.array(JoinTaskSchema).default([])
|
|
30620
30784
|
});
|
|
30621
30785
|
var JoinClusterResponse = external_exports.object({
|
|
30622
30786
|
clusterId: external_exports.string(),
|
|
@@ -30640,6 +30804,10 @@ var ElectionBody = external_exports.object({
|
|
|
30640
30804
|
candidateId: external_exports.string(),
|
|
30641
30805
|
term: external_exports.number().int().min(1)
|
|
30642
30806
|
});
|
|
30807
|
+
var ConnectClusterBody = external_exports.object({
|
|
30808
|
+
leaderEndpoint: external_exports.string().url(),
|
|
30809
|
+
transport: external_exports.literal("devtunnel")
|
|
30810
|
+
});
|
|
30643
30811
|
|
|
30644
30812
|
// ../../packages/api/src/schemas/nodes.ts
|
|
30645
30813
|
var NodeInfoSchema2 = external_exports.object({
|
|
@@ -30707,6 +30875,7 @@ var TaskListResponse = external_exports.object({
|
|
|
30707
30875
|
priority: external_exports.enum(["low", "normal", "high", "critical"]),
|
|
30708
30876
|
assignedTo: external_exports.string().nullable(),
|
|
30709
30877
|
assignedNodeName: external_exports.string().nullable().optional(),
|
|
30878
|
+
assignedNodeAvailable: external_exports.boolean().optional(),
|
|
30710
30879
|
createdBy: external_exports.string(),
|
|
30711
30880
|
result: external_exports.record(external_exports.unknown()).nullable(),
|
|
30712
30881
|
error: external_exports.string().nullable(),
|
|
@@ -30885,13 +31054,32 @@ function createErrorMiddleware() {
|
|
|
30885
31054
|
|
|
30886
31055
|
// ../../packages/api/src/routes/cluster.ts
|
|
30887
31056
|
var import_express = __toESM(require_express2(), 1);
|
|
31057
|
+
var CONNECTIVITY_TIMEOUT_MS = 8e3;
|
|
31058
|
+
async function verifyHealth(endpoint) {
|
|
31059
|
+
const controller = new AbortController();
|
|
31060
|
+
const timer = setTimeout(() => controller.abort(), CONNECTIVITY_TIMEOUT_MS);
|
|
31061
|
+
try {
|
|
31062
|
+
const response = await fetch(`${endpoint}/api/system/health`, {
|
|
31063
|
+
signal: controller.signal
|
|
31064
|
+
});
|
|
31065
|
+
if (!response.ok) {
|
|
31066
|
+
throw new Error(`health check returned ${response.status}`);
|
|
31067
|
+
}
|
|
31068
|
+
} catch (err) {
|
|
31069
|
+
throw new Error(
|
|
31070
|
+
`Failed to verify connectivity for ${endpoint}: ${err instanceof Error ? err.message : String(err)}`
|
|
31071
|
+
);
|
|
31072
|
+
} finally {
|
|
31073
|
+
clearTimeout(timer);
|
|
31074
|
+
}
|
|
31075
|
+
}
|
|
30888
31076
|
function asyncHandler(fn) {
|
|
30889
31077
|
return (req, res, next) => fn(req, res, next).catch(next);
|
|
30890
31078
|
}
|
|
30891
31079
|
function createClusterRoutes() {
|
|
30892
31080
|
const router = (0, import_express.Router)();
|
|
30893
31081
|
router.post("/join", asyncHandler(async (req, res) => {
|
|
30894
|
-
const { nodeRegistry, election } = req.app.locals.deps;
|
|
31082
|
+
const { nodeRegistry, election, taskEngine } = req.app.locals.deps;
|
|
30895
31083
|
if (!election.isLeader()) {
|
|
30896
31084
|
const leaderEndpoint = nodeRegistry.getLeaderEndpoint();
|
|
30897
31085
|
res.status(421).json({
|
|
@@ -30922,6 +31110,7 @@ function createClusterRoutes() {
|
|
|
30922
31110
|
...body.devtunnelEndpoint && { devtunnelEndpoint: body.devtunnelEndpoint }
|
|
30923
31111
|
};
|
|
30924
31112
|
nodeRegistry.addNode(node);
|
|
31113
|
+
taskEngine.importTasks(body.tasks ?? []);
|
|
30925
31114
|
const clusterState = nodeRegistry.getClusterState();
|
|
30926
31115
|
res.status(201).json({
|
|
30927
31116
|
clusterId: clusterState.clusterId ?? nanoid(),
|
|
@@ -30930,13 +31119,50 @@ function createClusterRoutes() {
|
|
|
30930
31119
|
nodes: clusterState.nodes
|
|
30931
31120
|
});
|
|
30932
31121
|
}));
|
|
31122
|
+
router.post("/connect", asyncHandler(async (req, res) => {
|
|
31123
|
+
const {
|
|
31124
|
+
nodeRegistry,
|
|
31125
|
+
joinCurrentNodeToCluster
|
|
31126
|
+
} = req.app.locals.deps;
|
|
31127
|
+
const body = ConnectClusterBody.parse(req.body);
|
|
31128
|
+
if (!joinCurrentNodeToCluster) {
|
|
31129
|
+
throw new MeshyError("VALIDATION_ERROR", "Join flow is not available on this node", 501);
|
|
31130
|
+
}
|
|
31131
|
+
await verifyHealth(body.leaderEndpoint);
|
|
31132
|
+
await joinCurrentNodeToCluster(body.leaderEndpoint);
|
|
31133
|
+
const self = nodeRegistry.getSelf();
|
|
31134
|
+
const clusterState = nodeRegistry.getClusterState();
|
|
31135
|
+
res.json({
|
|
31136
|
+
ok: true,
|
|
31137
|
+
transport: body.transport,
|
|
31138
|
+
self,
|
|
31139
|
+
leaderId: clusterState.leaderId,
|
|
31140
|
+
term: clusterState.term,
|
|
31141
|
+
nodes: clusterState.nodes
|
|
31142
|
+
});
|
|
31143
|
+
}));
|
|
30933
31144
|
router.post("/leave", asyncHandler(async (req, res) => {
|
|
30934
|
-
const { nodeRegistry
|
|
31145
|
+
const { nodeRegistry } = req.app.locals.deps;
|
|
30935
31146
|
const { nodeId } = req.body;
|
|
30936
31147
|
nodeRegistry.removeNode(nodeId);
|
|
30937
|
-
eventBus.emit("node.left", { nodeId, reason: "left" });
|
|
30938
31148
|
res.json({ ok: true });
|
|
30939
31149
|
}));
|
|
31150
|
+
router.post("/disconnect", asyncHandler(async (req, res) => {
|
|
31151
|
+
const { leaveCurrentCluster, nodeRegistry } = req.app.locals.deps;
|
|
31152
|
+
if (!leaveCurrentCluster) {
|
|
31153
|
+
throw new MeshyError("VALIDATION_ERROR", "Leave flow is not available on this node", 501);
|
|
31154
|
+
}
|
|
31155
|
+
await leaveCurrentCluster();
|
|
31156
|
+
const self = nodeRegistry.getSelf();
|
|
31157
|
+
const clusterState = nodeRegistry.getClusterState();
|
|
31158
|
+
res.json({
|
|
31159
|
+
ok: true,
|
|
31160
|
+
self,
|
|
31161
|
+
leaderId: clusterState.leaderId,
|
|
31162
|
+
term: clusterState.term,
|
|
31163
|
+
nodes: clusterState.nodes
|
|
31164
|
+
});
|
|
31165
|
+
}));
|
|
30940
31166
|
router.get("/state", asyncHandler(async (req, res) => {
|
|
30941
31167
|
const { nodeRegistry, taskEngine } = req.app.locals.deps;
|
|
30942
31168
|
const clusterState = nodeRegistry.getClusterState();
|
|
@@ -31826,7 +32052,7 @@ function createNodeRoutes() {
|
|
|
31826
32052
|
res.json(node);
|
|
31827
32053
|
}));
|
|
31828
32054
|
router.delete("/:id", asyncHandler2(async (req, res) => {
|
|
31829
|
-
const { nodeRegistry,
|
|
32055
|
+
const { nodeRegistry, election } = req.app.locals.deps;
|
|
31830
32056
|
if (!election.isLeader()) {
|
|
31831
32057
|
throw new MeshyError("NOT_LEADER", "Only the leader node can delete nodes", 403);
|
|
31832
32058
|
}
|
|
@@ -31835,7 +32061,6 @@ function createNodeRoutes() {
|
|
|
31835
32061
|
throw new MeshyError("NODE_NOT_FOUND", `Node ${req.params.id} not found`, 404);
|
|
31836
32062
|
}
|
|
31837
32063
|
nodeRegistry.removeNode(req.params.id);
|
|
31838
|
-
eventBus.emit("node.left", { nodeId: req.params.id, reason: "deleted" });
|
|
31839
32064
|
res.json({ ok: true });
|
|
31840
32065
|
}));
|
|
31841
32066
|
router.post("/:id/devtunnel", asyncHandler2(async (req, res) => {
|
|
@@ -32042,17 +32267,15 @@ var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "assigned", "running"]
|
|
|
32042
32267
|
function asyncHandler3(fn) {
|
|
32043
32268
|
return (req, res, next) => fn(req, res, next).catch(next);
|
|
32044
32269
|
}
|
|
32045
|
-
function
|
|
32046
|
-
if (
|
|
32270
|
+
function withAssignedNodeMetadata(task, nodeRegistry) {
|
|
32271
|
+
if (!task.assignedTo) {
|
|
32047
32272
|
return task;
|
|
32048
32273
|
}
|
|
32049
32274
|
const node = nodeRegistry.getNode(task.assignedTo);
|
|
32050
|
-
if (!node?.name) {
|
|
32051
|
-
return task;
|
|
32052
|
-
}
|
|
32053
32275
|
return {
|
|
32054
32276
|
...task,
|
|
32055
|
-
assignedNodeName: node
|
|
32277
|
+
assignedNodeName: task.assignedNodeName ?? node?.name ?? null,
|
|
32278
|
+
assignedNodeAvailable: node?.status !== "offline" && Boolean(node)
|
|
32056
32279
|
};
|
|
32057
32280
|
}
|
|
32058
32281
|
function createTaskRoutes() {
|
|
@@ -32071,10 +32294,10 @@ function createTaskRoutes() {
|
|
|
32071
32294
|
});
|
|
32072
32295
|
if (body.assignTo) {
|
|
32073
32296
|
const assigned = taskEngine.assignTask(task.id, body.assignTo);
|
|
32074
|
-
res.status(201).json(
|
|
32297
|
+
res.status(201).json(withAssignedNodeMetadata(assigned, nodeRegistry));
|
|
32075
32298
|
return;
|
|
32076
32299
|
}
|
|
32077
|
-
res.status(201).json(
|
|
32300
|
+
res.status(201).json(withAssignedNodeMetadata(task, nodeRegistry));
|
|
32078
32301
|
}));
|
|
32079
32302
|
router.get("/", asyncHandler3(async (req, res) => {
|
|
32080
32303
|
const { taskEngine, nodeRegistry } = req.app.locals.deps;
|
|
@@ -32087,7 +32310,7 @@ function createTaskRoutes() {
|
|
|
32087
32310
|
if (query.offset) filter.offset = query.offset;
|
|
32088
32311
|
const result = taskEngine.listTasks(filter);
|
|
32089
32312
|
res.json({
|
|
32090
|
-
tasks: result.tasks.map((task) =>
|
|
32313
|
+
tasks: result.tasks.map((task) => withAssignedNodeMetadata(task, nodeRegistry)),
|
|
32091
32314
|
total: result.total
|
|
32092
32315
|
});
|
|
32093
32316
|
}));
|
|
@@ -32128,7 +32351,7 @@ function createTaskRoutes() {
|
|
|
32128
32351
|
if (!task) {
|
|
32129
32352
|
throw new MeshyError("TASK_NOT_FOUND", `Task ${req.params.id} not found`, 404);
|
|
32130
32353
|
}
|
|
32131
|
-
res.json(
|
|
32354
|
+
res.json(withAssignedNodeMetadata(task, nodeRegistry));
|
|
32132
32355
|
}));
|
|
32133
32356
|
router.patch("/:id", asyncHandler3(async (req, res) => {
|
|
32134
32357
|
const { taskEngine, nodeRegistry, logger: rootLogger } = req.app.locals.deps;
|
|
@@ -32144,7 +32367,7 @@ function createTaskRoutes() {
|
|
|
32144
32367
|
currentStatus: existing.status,
|
|
32145
32368
|
incomingStatus: updates.status
|
|
32146
32369
|
});
|
|
32147
|
-
res.json(
|
|
32370
|
+
res.json(withAssignedNodeMetadata(existing, nodeRegistry));
|
|
32148
32371
|
return;
|
|
32149
32372
|
}
|
|
32150
32373
|
if (updates.status === "archived" && !ARCHIVABLE_STATUSES.has(existing.status)) {
|
|
@@ -32160,14 +32383,14 @@ function createTaskRoutes() {
|
|
|
32160
32383
|
currentStatus: existing.status,
|
|
32161
32384
|
incomingStatus: updates.status
|
|
32162
32385
|
});
|
|
32163
|
-
res.json(
|
|
32386
|
+
res.json(withAssignedNodeMetadata(existing, nodeRegistry));
|
|
32164
32387
|
return;
|
|
32165
32388
|
}
|
|
32166
32389
|
const task = taskEngine.updateTask(req.params.id, updates);
|
|
32167
32390
|
if (!task) {
|
|
32168
32391
|
throw new MeshyError("TASK_NOT_FOUND", `Task ${req.params.id} not found`, 404);
|
|
32169
32392
|
}
|
|
32170
|
-
res.json(
|
|
32393
|
+
res.json(withAssignedNodeMetadata(task, nodeRegistry));
|
|
32171
32394
|
}));
|
|
32172
32395
|
router.delete("/:id", asyncHandler3(async (req, res) => {
|
|
32173
32396
|
const { taskEngine } = req.app.locals.deps;
|
|
@@ -32218,12 +32441,12 @@ function createTaskRoutes() {
|
|
|
32218
32441
|
const { taskEngine, nodeRegistry } = req.app.locals.deps;
|
|
32219
32442
|
const body = AssignTaskBody.parse(req.body);
|
|
32220
32443
|
const task = taskEngine.assignTask(req.params.id, body.nodeId);
|
|
32221
|
-
res.json(
|
|
32444
|
+
res.json(withAssignedNodeMetadata(task, nodeRegistry));
|
|
32222
32445
|
}));
|
|
32223
32446
|
router.post("/:id/retry", asyncHandler3(async (req, res) => {
|
|
32224
32447
|
const { taskEngine, nodeRegistry } = req.app.locals.deps;
|
|
32225
32448
|
const task = taskEngine.retryTask(req.params.id);
|
|
32226
|
-
res.json(
|
|
32449
|
+
res.json(withAssignedNodeMetadata(task, nodeRegistry));
|
|
32227
32450
|
}));
|
|
32228
32451
|
router.get("/:id/logs", asyncHandler3(async (req, res) => {
|
|
32229
32452
|
const { engineRegistry, taskEngine, nodeRegistry, logger: rootLogger } = req.app.locals.deps;
|
|
@@ -32308,26 +32531,18 @@ function createTaskRoutes() {
|
|
|
32308
32531
|
}
|
|
32309
32532
|
if (task.assignedTo && task.assignedTo !== nodeRegistry.getSelf()?.id) {
|
|
32310
32533
|
const node = nodeRegistry.getNode(task.assignedTo);
|
|
32311
|
-
const selfId = nodeRegistry.getSelf()?.id ?? null;
|
|
32312
32534
|
if (!node || node.status === "offline") {
|
|
32313
|
-
log.warn("assigned worker unavailable
|
|
32535
|
+
log.warn("assigned worker unavailable for follow-up", {
|
|
32314
32536
|
taskId: task.id,
|
|
32315
|
-
assignedTo: task.assignedTo,
|
|
32316
|
-
nodeFound: Boolean(node),
|
|
32317
|
-
nodeStatus: node?.status,
|
|
32318
|
-
reassignedTo: selfId
|
|
32319
|
-
});
|
|
32320
|
-
startLocalTaskFollowUp({
|
|
32321
|
-
taskEngine,
|
|
32322
|
-
engineRegistry,
|
|
32323
|
-
logger: log
|
|
32324
|
-
}, task, body.content, selfId, {
|
|
32325
32537
|
assignedTo: task.assignedTo,
|
|
32326
32538
|
nodeFound: Boolean(node),
|
|
32327
32539
|
nodeStatus: node?.status
|
|
32328
32540
|
});
|
|
32329
|
-
|
|
32330
|
-
|
|
32541
|
+
throw new MeshyError(
|
|
32542
|
+
"NODE_OFFLINE",
|
|
32543
|
+
`Task ${task.id} is unavailable because ${task.assignedNodeName ?? task.assignedTo} is no longer reachable`,
|
|
32544
|
+
409
|
|
32545
|
+
);
|
|
32331
32546
|
}
|
|
32332
32547
|
taskEngine.updateTask(task.id, { status: "running" });
|
|
32333
32548
|
let workerEndpoint = node.endpoint;
|
|
@@ -33637,6 +33852,12 @@ async function main() {
|
|
|
33637
33852
|
config: { apiKey: config.cluster.apiKey },
|
|
33638
33853
|
workDir: meshyNode.getWorkDir(),
|
|
33639
33854
|
persistNodeNamePreference: (name) => persistDefaultNodeName(config.storage.path, name),
|
|
33855
|
+
joinCurrentNodeToCluster: async (leaderEndpoint) => {
|
|
33856
|
+
await meshyNode.joinCluster(leaderEndpoint);
|
|
33857
|
+
},
|
|
33858
|
+
leaveCurrentCluster: async () => {
|
|
33859
|
+
await meshyNode.leaveCluster();
|
|
33860
|
+
},
|
|
33640
33861
|
switchTransport: async (type) => {
|
|
33641
33862
|
return meshyNode.switchTransport(type);
|
|
33642
33863
|
},
|