@gaia-ai/addon-remote-drupal 0.10.0 → 0.11.1
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/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +68 -22
- package/package.json +4 -4
package/dist/src/index.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export declare class DrupalGaiaRemote implements GaiaRemote {
|
|
|
5
5
|
constructor(api: JsonApiClient);
|
|
6
6
|
fetchActiveRuns(id: string): Promise<ActiveRun[]>;
|
|
7
7
|
activeRunCount(id: string): Promise<number>;
|
|
8
|
+
machineCapacity(hostKey: string): Promise<{
|
|
9
|
+
maxParallel: number;
|
|
10
|
+
currentLoad: number;
|
|
11
|
+
} | null>;
|
|
8
12
|
claimNext(c: ClaimOptions): Promise<ClaimedRun | null>;
|
|
9
13
|
getTicket(uuid: string): Promise<Ticket>;
|
|
10
14
|
getRunWorktree(uuid: string): Promise<string>;
|
|
@@ -47,6 +51,13 @@ export declare class DrupalGaiaRemote implements GaiaRemote {
|
|
|
47
51
|
* so it degrades to '' and the row still renders.
|
|
48
52
|
*/
|
|
49
53
|
private projectName;
|
|
54
|
+
/**
|
|
55
|
+
* Host concurrent-run budget for a machine uuid (GAIA-353).
|
|
56
|
+
*
|
|
57
|
+
* Best-effort: an unreadable or missing machine degrades to 0 so the row
|
|
58
|
+
* still lists; capacity is never invented from a conductor field.
|
|
59
|
+
*/
|
|
60
|
+
private machineMaxParallel;
|
|
50
61
|
private projectUuid;
|
|
51
62
|
}
|
|
52
63
|
export declare function drupalRemote(): RemotePlugin;
|
package/dist/src/index.js
CHANGED
|
@@ -34,12 +34,12 @@ export class DrupalGaiaRemote {
|
|
|
34
34
|
this.api = api;
|
|
35
35
|
}
|
|
36
36
|
async fetchActiveRuns(id) {
|
|
37
|
-
// The conductor identity `id` is the
|
|
37
|
+
// The conductor identity `id` is the registration key (conductor_id).
|
|
38
38
|
// gaia_run links to its owning conductor via the `conductor_id` entity-ref;
|
|
39
|
-
// filter on the related conductor's
|
|
39
|
+
// filter on the related conductor's registration string field.
|
|
40
40
|
const rows = await this.api
|
|
41
41
|
.collection('gaia_run')
|
|
42
|
-
.where('conductor_id.
|
|
42
|
+
.where('conductor_id.conductor_id', '=', id)
|
|
43
43
|
.whereIn('state', ACTIVE)
|
|
44
44
|
.fields(['state', 'state_at_start', 'worktree_path'])
|
|
45
45
|
.page(100)
|
|
@@ -55,22 +55,39 @@ export class DrupalGaiaRemote {
|
|
|
55
55
|
})));
|
|
56
56
|
}
|
|
57
57
|
async activeRunCount(id) {
|
|
58
|
-
// Filter on the related conductor's
|
|
58
|
+
// Filter on the related conductor's registration string field.
|
|
59
59
|
return this.api
|
|
60
60
|
.collection('gaia_run')
|
|
61
|
-
.where('conductor_id.
|
|
61
|
+
.where('conductor_id.conductor_id', '=', id)
|
|
62
62
|
.whereIn('state', ACTIVE)
|
|
63
63
|
.fields(['drupal_internal__id'])
|
|
64
64
|
.page(100)
|
|
65
65
|
.count();
|
|
66
66
|
}
|
|
67
|
+
async machineCapacity(hostKey) {
|
|
68
|
+
// Shared host budget (GAIA-353 D5): read gaia_machine after heartbeat so
|
|
69
|
+
// current_load is peers + this process. Filter on host_key (MachineContext
|
|
70
|
+
// stem), not the composed conductor_id.
|
|
71
|
+
const row = await this.api
|
|
72
|
+
.collection('gaia_machine')
|
|
73
|
+
.where('host_key', '=', hostKey)
|
|
74
|
+
.fields(['max_parallel', 'current_load'])
|
|
75
|
+
.first();
|
|
76
|
+
if (!row)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
maxParallel: row.attr('max_parallel') ?? 1,
|
|
80
|
+
currentLoad: row.attr('current_load') ?? 0,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
67
83
|
async claimNext(c) {
|
|
68
84
|
const res = (await this.api.post('gaia/claim-next', {
|
|
69
85
|
data: {
|
|
70
86
|
attributes: {
|
|
71
87
|
lease_seconds: c.leaseSeconds,
|
|
72
88
|
// Server (`ConductorResolverTrait`) narrows to the caller's conductor
|
|
73
|
-
// by
|
|
89
|
+
// by conductor_id (legacy machine_id still accepted).
|
|
90
|
+
conductor_id: c.conductorId,
|
|
74
91
|
machine_id: c.conductorId,
|
|
75
92
|
},
|
|
76
93
|
},
|
|
@@ -160,14 +177,16 @@ export class DrupalGaiaRemote {
|
|
|
160
177
|
return ticket.attr('branch_name') ?? '';
|
|
161
178
|
}
|
|
162
179
|
async registerConductor(reg) {
|
|
163
|
-
|
|
180
|
+
// Heartbeat is the authoritative upsert (creates machine + conductor).
|
|
181
|
+
// Keep JSON:API upsert as a best-effort create path for attributes the
|
|
182
|
+
// heartbeat already covers; key on conductor_id (GAIA-353).
|
|
183
|
+
const r = await this.api.upsert('gaia_conductor', { path: 'conductor_id', value: reg.id }, {
|
|
164
184
|
attributes: {
|
|
165
|
-
|
|
185
|
+
conductor_id: reg.id,
|
|
166
186
|
status: 'online',
|
|
167
187
|
workspace_root: reg.workspace,
|
|
168
188
|
label: reg.label,
|
|
169
189
|
states: reg.states,
|
|
170
|
-
max_parallel: reg.max_parallel,
|
|
171
190
|
...processIdentity(reg),
|
|
172
191
|
},
|
|
173
192
|
relationships: {
|
|
@@ -185,13 +204,14 @@ export class DrupalGaiaRemote {
|
|
|
185
204
|
return r.id; // drupal uuid
|
|
186
205
|
}
|
|
187
206
|
async heartbeat(reg, load, lease = 300) {
|
|
188
|
-
// Server-side upsert by
|
|
207
|
+
// Server-side upsert by conductor_id (see HeartbeatResource): never 404s,
|
|
189
208
|
// heals a vanished registration, and returns the resulting status. Sending
|
|
190
209
|
// the full registration lets the server recreate the entity when missing.
|
|
191
210
|
const res = (await this.api.post('gaia/heartbeat', {
|
|
192
211
|
data: {
|
|
193
212
|
attributes: {
|
|
194
|
-
|
|
213
|
+
conductor_id: reg.id,
|
|
214
|
+
host_key: reg.host_key ?? reg.id,
|
|
195
215
|
project: reg.project,
|
|
196
216
|
states: reg.states,
|
|
197
217
|
workspace_root: reg.workspace,
|
|
@@ -209,7 +229,7 @@ export class DrupalGaiaRemote {
|
|
|
209
229
|
async getConductorStatus(id) {
|
|
210
230
|
const c = await this.api
|
|
211
231
|
.collection('gaia_conductor')
|
|
212
|
-
.where('
|
|
232
|
+
.where('conductor_id', '=', id)
|
|
213
233
|
.fields(['status'])
|
|
214
234
|
.first();
|
|
215
235
|
return c ? (c.attr('status') ?? null) : null;
|
|
@@ -217,7 +237,7 @@ export class DrupalGaiaRemote {
|
|
|
217
237
|
async setConductorStatus(id, status) {
|
|
218
238
|
const c = await this.api
|
|
219
239
|
.collection('gaia_conductor')
|
|
220
|
-
.where('
|
|
240
|
+
.where('conductor_id', '=', id)
|
|
221
241
|
.first();
|
|
222
242
|
if (c) {
|
|
223
243
|
await this.api.update('gaia_conductor', c.id, { attributes: { status } });
|
|
@@ -232,10 +252,16 @@ export class DrupalGaiaRemote {
|
|
|
232
252
|
// Resolve each distinct project once rather than per row: a machine
|
|
233
253
|
// typically runs several conductors of the same project.
|
|
234
254
|
const projectNames = new Map();
|
|
255
|
+
// Host capacity SoT is gaia_machine (GAIA-353); conductor.max_parallel is gone.
|
|
256
|
+
const machineMaxParallel = new Map();
|
|
235
257
|
for (const r of rows) {
|
|
236
|
-
const
|
|
237
|
-
if (
|
|
238
|
-
projectNames.set(
|
|
258
|
+
const projectUuid = r.rel('project_id');
|
|
259
|
+
if (projectUuid && !projectNames.has(projectUuid)) {
|
|
260
|
+
projectNames.set(projectUuid, await this.projectName(projectUuid));
|
|
261
|
+
}
|
|
262
|
+
const machineUuid = r.rel('machine');
|
|
263
|
+
if (machineUuid && !machineMaxParallel.has(machineUuid)) {
|
|
264
|
+
machineMaxParallel.set(machineUuid, await this.machineMaxParallel(machineUuid));
|
|
239
265
|
}
|
|
240
266
|
}
|
|
241
267
|
return rows.map((r) => {
|
|
@@ -245,16 +271,21 @@ export class DrupalGaiaRemote {
|
|
|
245
271
|
const pid = r.attr('last_pid');
|
|
246
272
|
const name = r.attr('conductor_name');
|
|
247
273
|
return {
|
|
248
|
-
id: r.attr('
|
|
274
|
+
id: r.attr('conductor_id') ??
|
|
275
|
+
r.attr('machine_id') ??
|
|
276
|
+
r.id,
|
|
249
277
|
project: projectNames.get(r.rel('project_id') ?? '') ?? '',
|
|
250
278
|
label: r.attr('label') ?? '',
|
|
251
279
|
status: r.attr('status') ?? '',
|
|
252
280
|
// ISO-8601 over the wire, not the unix integer the field name implies.
|
|
253
281
|
lastSeen: toUnixSeconds(r.attr('last_seen')) ?? 0,
|
|
254
|
-
load
|
|
282
|
+
// Process load telemetry; host capacity is on gaia_machine (GAIA-353).
|
|
283
|
+
load: r.attr('reported_load') ??
|
|
284
|
+
r.attr('current_load') ??
|
|
285
|
+
0,
|
|
255
286
|
workspaceRoot: r.attr('workspace_root') ?? '',
|
|
256
287
|
leaseExpiresAt: toUnixSeconds(r.attr('lease_expires_at')) ?? 0,
|
|
257
|
-
maxParallel: r.
|
|
288
|
+
maxParallel: machineMaxParallel.get(r.rel('machine') ?? '') ?? 0,
|
|
258
289
|
...(typeof pid === 'number' ? { lastPid: pid } : {}),
|
|
259
290
|
...(typeof name === 'string' && name !== ''
|
|
260
291
|
? { conductorName: name }
|
|
@@ -287,7 +318,7 @@ export class DrupalGaiaRemote {
|
|
|
287
318
|
async fetchFinalizableRuns(id) {
|
|
288
319
|
const rows = await this.api
|
|
289
320
|
.collection('gaia_run')
|
|
290
|
-
.where('conductor_id.
|
|
321
|
+
.where('conductor_id.conductor_id', '=', id)
|
|
291
322
|
.where('state', '=', 'done')
|
|
292
323
|
.where('closed', '=', '0')
|
|
293
324
|
.fields(['worktree_path', 'agent', 'drupal_internal__id'])
|
|
@@ -332,7 +363,7 @@ export class DrupalGaiaRemote {
|
|
|
332
363
|
const fields = ['branch_name', 'state', 'closed'];
|
|
333
364
|
const done = await this.api
|
|
334
365
|
.collection('gaia_ticket')
|
|
335
|
-
.where('conductor_id.
|
|
366
|
+
.where('conductor_id.conductor_id', '=', id)
|
|
336
367
|
.whereIn('state', TERMINAL)
|
|
337
368
|
.where('cleaned_up', '=', '0')
|
|
338
369
|
.fields(fields)
|
|
@@ -340,7 +371,7 @@ export class DrupalGaiaRemote {
|
|
|
340
371
|
.list();
|
|
341
372
|
const closed = await this.api
|
|
342
373
|
.collection('gaia_ticket')
|
|
343
|
-
.where('conductor_id.
|
|
374
|
+
.where('conductor_id.conductor_id', '=', id)
|
|
344
375
|
.where('closed', '=', '1')
|
|
345
376
|
.where('cleaned_up', '=', '0')
|
|
346
377
|
.fields(fields)
|
|
@@ -462,6 +493,21 @@ export class DrupalGaiaRemote {
|
|
|
462
493
|
return '';
|
|
463
494
|
}
|
|
464
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Host concurrent-run budget for a machine uuid (GAIA-353).
|
|
498
|
+
*
|
|
499
|
+
* Best-effort: an unreadable or missing machine degrades to 0 so the row
|
|
500
|
+
* still lists; capacity is never invented from a conductor field.
|
|
501
|
+
*/
|
|
502
|
+
async machineMaxParallel(uuid) {
|
|
503
|
+
try {
|
|
504
|
+
const m = await this.api.resource('gaia_machine', uuid);
|
|
505
|
+
return m.attr('max_parallel') ?? 0;
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
465
511
|
async projectUuid(name) {
|
|
466
512
|
// A project's name→uuid never changes, so cache it: gatherBatch resolves
|
|
467
513
|
// many identifiers per `gaia deployment tickets` call, each of which would
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gaia-ai/addon-remote-drupal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "GAIA conductor remote addon: the Drupal/JSON:API control-plane remote.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"directory": "gaia-cli/addons/remote-drupal"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"dropsh": "^0.
|
|
23
|
+
"dropsh": "^0.6.1"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@gaia-ai/conductor": "^0.
|
|
27
|
-
"@gaia-ai/core": "^0.
|
|
26
|
+
"@gaia-ai/conductor": "^0.11.1",
|
|
27
|
+
"@gaia-ai/core": "^0.11.1"
|
|
28
28
|
}
|
|
29
29
|
}
|