@kikimoradev/kcloud 0.15.0 → 0.17.0
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/main.js +1649 -755
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -13,6 +13,24 @@ import createClient from "openapi-fetch";
|
|
|
13
13
|
import { finished } from "node:stream/promises";
|
|
14
14
|
import { StdioServerTransport, serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
15
15
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
16
|
+
//#region ../../packages/cli-kit/src/bytes.ts
|
|
17
|
+
const units = [
|
|
18
|
+
"B",
|
|
19
|
+
"KiB",
|
|
20
|
+
"MiB",
|
|
21
|
+
"GiB",
|
|
22
|
+
"TiB"
|
|
23
|
+
];
|
|
24
|
+
const bytesOf = (bytes) => {
|
|
25
|
+
let value = bytes;
|
|
26
|
+
let unit = 0;
|
|
27
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
28
|
+
value /= 1024;
|
|
29
|
+
unit += 1;
|
|
30
|
+
}
|
|
31
|
+
return unit === 0 ? `${bytes} B` : `${value.toFixed(1)} ${units[unit] ?? ""}`;
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
16
34
|
//#region ../../packages/cli-kit/src/errors.ts
|
|
17
35
|
const exitCodes = {
|
|
18
36
|
ok: 0,
|
|
@@ -91,12 +109,13 @@ const commandFactoryOf = (spec) => (definition) => {
|
|
|
91
109
|
}
|
|
92
110
|
};
|
|
93
111
|
};
|
|
94
|
-
const defineGroup$1 = ({ name, summary, usage, subcommands }) => ({
|
|
112
|
+
const defineGroup$1 = ({ name, summary, usage, subcommands, fallback }) => ({
|
|
95
113
|
name,
|
|
96
114
|
summary,
|
|
97
115
|
usage,
|
|
98
116
|
optionsUsage: [],
|
|
99
117
|
subcommands,
|
|
118
|
+
...fallback === void 0 ? {} : { fallback },
|
|
100
119
|
execute: () => {
|
|
101
120
|
throw new Error(`${name} is a group of commands and runs none of its own`);
|
|
102
121
|
}
|
|
@@ -334,23 +353,27 @@ const report = (error, terminal, environment, program, debugVariable) => {
|
|
|
334
353
|
};
|
|
335
354
|
const dispatcherOf = (spec) => {
|
|
336
355
|
const rendering = spec;
|
|
337
|
-
const
|
|
356
|
+
const executed = (command, args, help, context) => {
|
|
357
|
+
if (!help) return command.execute(args, context);
|
|
358
|
+
context.terminal.line(commandUsageOf(rendering, command));
|
|
359
|
+
return Promise.resolve(exitCodes.ok);
|
|
360
|
+
};
|
|
361
|
+
const walk = async (commands, args, trail, context, fallback) => {
|
|
338
362
|
const located = locateCommand(args, spec.globalOptions);
|
|
363
|
+
const command = located.name === null ? void 0 : commands.find(({ name }) => name === located.name);
|
|
364
|
+
const listsHelp = located.name === null && located.help;
|
|
365
|
+
if (command === void 0 && fallback !== void 0 && !listsHelp) return executed(fallback, args, located.help, context);
|
|
366
|
+
const listed = fallback === void 0 ? commands : [fallback, ...commands];
|
|
339
367
|
if (located.name === null) {
|
|
340
368
|
if (located.help) {
|
|
341
|
-
context.terminal.line(commandsUsageOf(rendering,
|
|
369
|
+
context.terminal.line(commandsUsageOf(rendering, listed, trail));
|
|
342
370
|
return exitCodes.ok;
|
|
343
371
|
}
|
|
344
|
-
throw new UsageError(trail.length === 0 ? "Name a command." : `${pathOf(trail)}needs a subcommand.`, commandsUsageOf(rendering,
|
|
345
|
-
}
|
|
346
|
-
const command = commands.find(({ name }) => name === located.name);
|
|
347
|
-
if (command === void 0) throw new UsageError(`There is no command ${pathOf(trail)}${located.name}.`, commandsUsageOf(rendering, commands, trail));
|
|
348
|
-
if (command.subcommands.length > 0) return walk(command.subcommands, located.args, [...trail, command.name], context);
|
|
349
|
-
if (located.help) {
|
|
350
|
-
context.terminal.line(commandUsageOf(rendering, command));
|
|
351
|
-
return exitCodes.ok;
|
|
372
|
+
throw new UsageError(trail.length === 0 ? "Name a command." : `${pathOf(trail)}needs a subcommand.`, commandsUsageOf(rendering, listed, trail));
|
|
352
373
|
}
|
|
353
|
-
|
|
374
|
+
if (command === void 0) throw new UsageError(`There is no command ${pathOf(trail)}${located.name}.`, commandsUsageOf(rendering, listed, trail));
|
|
375
|
+
if (command.subcommands.length > 0) return walk(command.subcommands, located.args, [...trail, command.name], context, command.fallback);
|
|
376
|
+
return executed(command, located.args, located.help, context);
|
|
354
377
|
};
|
|
355
378
|
return async ({ argv, commands, context }) => {
|
|
356
379
|
try {
|
|
@@ -363,6 +386,12 @@ const dispatcherOf = (spec) => {
|
|
|
363
386
|
}
|
|
364
387
|
};
|
|
365
388
|
};
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region ../../packages/cli-kit/src/list.ts
|
|
391
|
+
const listOf = (items) => {
|
|
392
|
+
const last = items.at(-1);
|
|
393
|
+
return items.length < 2 || last === void 0 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${last}`;
|
|
394
|
+
};
|
|
366
395
|
const permissionsOutsideOwner = 63;
|
|
367
396
|
const isMissing = (error) => error instanceof Error && error.code === "ENOENT";
|
|
368
397
|
const ensurePrivateDirectory = async (path) => {
|
|
@@ -486,243 +515,6 @@ const spec = {
|
|
|
486
515
|
const defineCommand = commandFactoryOf(spec);
|
|
487
516
|
const defineGroup = (definition) => defineGroup$1(definition);
|
|
488
517
|
const runCli = dispatcherOf(spec);
|
|
489
|
-
const agentEventLevelSchema = z.enum([
|
|
490
|
-
"info",
|
|
491
|
-
"warn",
|
|
492
|
-
"error"
|
|
493
|
-
]);
|
|
494
|
-
const agentRoles = [
|
|
495
|
-
"monitor",
|
|
496
|
-
"executor",
|
|
497
|
-
"summarizer"
|
|
498
|
-
];
|
|
499
|
-
const agentRoleSchema = z.enum(agentRoles);
|
|
500
|
-
const agentLogStreams = ["stdout", "stderr"];
|
|
501
|
-
z.enum(agentLogStreams);
|
|
502
|
-
const platformStream = "platform";
|
|
503
|
-
const agentEventStreams = [...agentLogStreams, platformStream];
|
|
504
|
-
const agentEventStreamSchema = z.enum(agentEventStreams);
|
|
505
|
-
const authKindSchema = z.enum([
|
|
506
|
-
"apiKey",
|
|
507
|
-
"oauth",
|
|
508
|
-
"claude.ai",
|
|
509
|
-
"unknown"
|
|
510
|
-
]);
|
|
511
|
-
const agentControlStateSchema = z.enum([
|
|
512
|
-
"running",
|
|
513
|
-
"pausing",
|
|
514
|
-
"paused"
|
|
515
|
-
]);
|
|
516
|
-
z.enum([
|
|
517
|
-
"session",
|
|
518
|
-
"diagnostic",
|
|
519
|
-
"task"
|
|
520
|
-
]);
|
|
521
|
-
const maxCostUsd = 1e3;
|
|
522
|
-
const countSchema = z.int().min(0).max(2147483647);
|
|
523
|
-
const costUsdSchema = z.number().min(0).max(maxCostUsd);
|
|
524
|
-
const instantSchema$2 = z.iso.datetime();
|
|
525
|
-
const workerEnvelope = (event) => ({
|
|
526
|
-
ts: instantSchema$2,
|
|
527
|
-
level: agentEventLevelSchema,
|
|
528
|
-
event: z.literal(event)
|
|
529
|
-
});
|
|
530
|
-
const roleEnvelope = (event) => ({
|
|
531
|
-
...workerEnvelope(event),
|
|
532
|
-
agent: agentRoleSchema.optional()
|
|
533
|
-
});
|
|
534
|
-
const agentEventSchema = z.discriminatedUnion("event", [
|
|
535
|
-
z.looseObject({
|
|
536
|
-
...workerEnvelope("worker.started"),
|
|
537
|
-
version: z.string(),
|
|
538
|
-
claudeVersion: z.string().optional(),
|
|
539
|
-
nodeVersion: z.string(),
|
|
540
|
-
authKind: authKindSchema,
|
|
541
|
-
pid: countSchema,
|
|
542
|
-
projectDir: z.string(),
|
|
543
|
-
paused: z.boolean().optional()
|
|
544
|
-
}),
|
|
545
|
-
z.looseObject({
|
|
546
|
-
...workerEnvelope("worker.stopped"),
|
|
547
|
-
signal: z.string().optional(),
|
|
548
|
-
drained: z.boolean().optional(),
|
|
549
|
-
forced: z.boolean().optional()
|
|
550
|
-
}),
|
|
551
|
-
z.looseObject({
|
|
552
|
-
...workerEnvelope("worker.draining"),
|
|
553
|
-
reason: z.string(),
|
|
554
|
-
timeoutMs: countSchema.optional()
|
|
555
|
-
}),
|
|
556
|
-
z.looseObject({
|
|
557
|
-
...workerEnvelope("update.available"),
|
|
558
|
-
from: z.string(),
|
|
559
|
-
to: z.string(),
|
|
560
|
-
installError: z.string().optional()
|
|
561
|
-
}),
|
|
562
|
-
z.looseObject({
|
|
563
|
-
...workerEnvelope("update.installed"),
|
|
564
|
-
from: z.string(),
|
|
565
|
-
to: z.string()
|
|
566
|
-
}),
|
|
567
|
-
z.looseObject({
|
|
568
|
-
...roleEnvelope("control.changed"),
|
|
569
|
-
state: agentControlStateSchema
|
|
570
|
-
}),
|
|
571
|
-
z.looseObject({
|
|
572
|
-
...roleEnvelope("cycle.started"),
|
|
573
|
-
cycle: countSchema
|
|
574
|
-
}),
|
|
575
|
-
z.looseObject({
|
|
576
|
-
...roleEnvelope("cycle.finished"),
|
|
577
|
-
cycle: countSchema,
|
|
578
|
-
ok: z.boolean(),
|
|
579
|
-
durationMs: countSchema,
|
|
580
|
-
costUsd: costUsdSchema.optional(),
|
|
581
|
-
addedTasks: countSchema,
|
|
582
|
-
skippedDuplicates: countSchema,
|
|
583
|
-
sessionId: z.string().optional(),
|
|
584
|
-
error: z.string().optional()
|
|
585
|
-
}),
|
|
586
|
-
z.looseObject({
|
|
587
|
-
...roleEnvelope("monitor.offHours"),
|
|
588
|
-
resumeAt: instantSchema$2
|
|
589
|
-
}),
|
|
590
|
-
z.looseObject({
|
|
591
|
-
...roleEnvelope("monitor.limitWait"),
|
|
592
|
-
resumeAt: instantSchema$2
|
|
593
|
-
}),
|
|
594
|
-
z.looseObject({
|
|
595
|
-
...roleEnvelope("monitor.sleeping"),
|
|
596
|
-
nextCycleAt: instantSchema$2
|
|
597
|
-
}),
|
|
598
|
-
z.looseObject({
|
|
599
|
-
...roleEnvelope("monitor.authBlocked"),
|
|
600
|
-
reason: z.string()
|
|
601
|
-
}),
|
|
602
|
-
z.looseObject({
|
|
603
|
-
...roleEnvelope("monitor.admissionWait"),
|
|
604
|
-
ticket: z.string(),
|
|
605
|
-
since: instantSchema$2,
|
|
606
|
-
seq: countSchema
|
|
607
|
-
}),
|
|
608
|
-
z.looseObject({
|
|
609
|
-
...roleEnvelope("executor.admissionWait"),
|
|
610
|
-
ticket: z.string(),
|
|
611
|
-
since: instantSchema$2,
|
|
612
|
-
seq: countSchema,
|
|
613
|
-
taskId: z.string()
|
|
614
|
-
}),
|
|
615
|
-
z.looseObject({
|
|
616
|
-
...roleEnvelope("admission.granted"),
|
|
617
|
-
ticket: z.string(),
|
|
618
|
-
seq: countSchema
|
|
619
|
-
}),
|
|
620
|
-
z.looseObject({
|
|
621
|
-
...roleEnvelope("admission.released"),
|
|
622
|
-
ticket: z.string(),
|
|
623
|
-
seq: countSchema
|
|
624
|
-
}),
|
|
625
|
-
z.looseObject({
|
|
626
|
-
...roleEnvelope("admission.cancelled"),
|
|
627
|
-
ticket: z.string(),
|
|
628
|
-
seq: countSchema
|
|
629
|
-
}),
|
|
630
|
-
z.looseObject({
|
|
631
|
-
...roleEnvelope("task.started"),
|
|
632
|
-
taskId: z.string(),
|
|
633
|
-
title: z.string()
|
|
634
|
-
}),
|
|
635
|
-
z.looseObject({
|
|
636
|
-
...roleEnvelope("task.finished"),
|
|
637
|
-
taskId: z.string(),
|
|
638
|
-
title: z.string(),
|
|
639
|
-
ok: z.boolean(),
|
|
640
|
-
durationMs: countSchema,
|
|
641
|
-
costUsd: costUsdSchema.optional(),
|
|
642
|
-
numTurns: countSchema.optional(),
|
|
643
|
-
willRetry: z.boolean().optional(),
|
|
644
|
-
attempt: countSchema.optional(),
|
|
645
|
-
maxAttempts: countSchema.optional(),
|
|
646
|
-
sessionId: z.string().optional(),
|
|
647
|
-
error: z.string().optional()
|
|
648
|
-
}),
|
|
649
|
-
z.looseObject({
|
|
650
|
-
...roleEnvelope("task.retryScheduled"),
|
|
651
|
-
taskId: z.string(),
|
|
652
|
-
resumeAt: instantSchema$2
|
|
653
|
-
}),
|
|
654
|
-
z.looseObject(roleEnvelope("executor.waiting")),
|
|
655
|
-
z.looseObject({
|
|
656
|
-
...roleEnvelope("executor.limitWait"),
|
|
657
|
-
resumeAt: instantSchema$2
|
|
658
|
-
}),
|
|
659
|
-
z.looseObject({
|
|
660
|
-
...roleEnvelope("executor.authBlocked"),
|
|
661
|
-
reason: z.string()
|
|
662
|
-
}),
|
|
663
|
-
z.looseObject({
|
|
664
|
-
...roleEnvelope("summary.started"),
|
|
665
|
-
taskId: z.string()
|
|
666
|
-
}),
|
|
667
|
-
z.looseObject({
|
|
668
|
-
...roleEnvelope("summary.finished"),
|
|
669
|
-
taskId: z.string(),
|
|
670
|
-
ok: z.boolean(),
|
|
671
|
-
durationMs: countSchema,
|
|
672
|
-
costUsd: costUsdSchema.optional(),
|
|
673
|
-
sessionId: z.string().optional(),
|
|
674
|
-
error: z.string().optional()
|
|
675
|
-
}),
|
|
676
|
-
z.looseObject({
|
|
677
|
-
...roleEnvelope("session.init"),
|
|
678
|
-
model: z.string(),
|
|
679
|
-
sessionId: z.string(),
|
|
680
|
-
taskId: z.string().optional(),
|
|
681
|
-
cycle: countSchema.optional()
|
|
682
|
-
}),
|
|
683
|
-
z.looseObject({
|
|
684
|
-
...roleEnvelope("session.stderr"),
|
|
685
|
-
line: z.string()
|
|
686
|
-
}),
|
|
687
|
-
z.looseObject({
|
|
688
|
-
...roleEnvelope("session.procError"),
|
|
689
|
-
message: z.string()
|
|
690
|
-
}),
|
|
691
|
-
z.looseObject({
|
|
692
|
-
...roleEnvelope("session.killed"),
|
|
693
|
-
reason: z.string()
|
|
694
|
-
}),
|
|
695
|
-
z.looseObject({
|
|
696
|
-
...roleEnvelope("session.text"),
|
|
697
|
-
text: z.string()
|
|
698
|
-
}),
|
|
699
|
-
z.looseObject({
|
|
700
|
-
...roleEnvelope("session.tool"),
|
|
701
|
-
tool: z.string(),
|
|
702
|
-
detail: z.string()
|
|
703
|
-
}),
|
|
704
|
-
z.looseObject({
|
|
705
|
-
...roleEnvelope("session.toolError"),
|
|
706
|
-
output: z.string()
|
|
707
|
-
})
|
|
708
|
-
]);
|
|
709
|
-
z.looseObject({
|
|
710
|
-
ts: instantSchema$2,
|
|
711
|
-
level: agentEventLevelSchema,
|
|
712
|
-
agent: agentRoleSchema.optional(),
|
|
713
|
-
event: z.string().min(1).max(64)
|
|
714
|
-
});
|
|
715
|
-
agentEventSchema.options.map((option) => option.shape.event.value);
|
|
716
|
-
({
|
|
717
|
-
rawLog: "log.raw",
|
|
718
|
-
rejectedLog: "log.rejected",
|
|
719
|
-
throttledLog: "log.throttled",
|
|
720
|
-
stateChanged: "platform.stateChanged",
|
|
721
|
-
crashLooping: "platform.crashLooping",
|
|
722
|
-
provisioned: "platform.provisioned",
|
|
723
|
-
sandboxStarted: "platform.sandboxStarted",
|
|
724
|
-
sandboxStopped: "platform.sandboxStopped"
|
|
725
|
-
}).rawLog;
|
|
726
518
|
//#endregion
|
|
727
519
|
//#region ../../packages/contracts/src/patterns.ts
|
|
728
520
|
const slugPattern = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;
|
|
@@ -972,6 +764,7 @@ const errorCodes = Object.keys({
|
|
|
972
764
|
"agent.tool_duplicate": "rule_violation",
|
|
973
765
|
"agent.context_too_long": "rule_violation",
|
|
974
766
|
"agent.docker_unavailable": "rule_violation",
|
|
767
|
+
"agent.node_unsuitable": "rule_violation",
|
|
975
768
|
"agent.storage_exhausted": "rule_violation",
|
|
976
769
|
"agent.invalid_transition": "rule_violation",
|
|
977
770
|
"agent.busy": "conflict",
|
|
@@ -995,6 +788,13 @@ const errorCodes = Object.keys({
|
|
|
995
788
|
"node.registration_invalid": "unauthenticated",
|
|
996
789
|
"node.registration_used": "conflict",
|
|
997
790
|
"node.in_use": "conflict",
|
|
791
|
+
"node.host_unknown": "rule_violation",
|
|
792
|
+
"node.split_exceeds_host": "rule_violation",
|
|
793
|
+
"node.package_unavailable": "not_found",
|
|
794
|
+
"agent.backup_not_found": "not_found",
|
|
795
|
+
"agent.backup_unavailable": "rule_violation",
|
|
796
|
+
"agent.backup_without_export": "rule_violation",
|
|
797
|
+
"agent.backup_in_progress": "conflict",
|
|
998
798
|
"project.last_admin": "conflict",
|
|
999
799
|
"agent.node_unassigned": "rule_violation",
|
|
1000
800
|
"stream.too_many": "rate_limited",
|
|
@@ -1031,16 +831,253 @@ const errorBodySchema = z.object({
|
|
|
1031
831
|
message: z.string()
|
|
1032
832
|
})).optional()
|
|
1033
833
|
}).meta({ id: "ErrorBody" });
|
|
1034
|
-
const
|
|
1035
|
-
"
|
|
1036
|
-
"
|
|
1037
|
-
"
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
"
|
|
1041
|
-
"
|
|
1042
|
-
"
|
|
1043
|
-
|
|
834
|
+
const agentEventLevelSchema = z.enum([
|
|
835
|
+
"info",
|
|
836
|
+
"warn",
|
|
837
|
+
"error"
|
|
838
|
+
]);
|
|
839
|
+
const agentRoles = [
|
|
840
|
+
"monitor",
|
|
841
|
+
"executor",
|
|
842
|
+
"summarizer"
|
|
843
|
+
];
|
|
844
|
+
const agentRoleSchema = z.enum(agentRoles);
|
|
845
|
+
const agentLogStreams = ["stdout", "stderr"];
|
|
846
|
+
z.enum(agentLogStreams);
|
|
847
|
+
const platformStream = "platform";
|
|
848
|
+
const agentEventStreams = [...agentLogStreams, platformStream];
|
|
849
|
+
const agentEventStreamSchema = z.enum(agentEventStreams);
|
|
850
|
+
const authKindSchema = z.enum([
|
|
851
|
+
"apiKey",
|
|
852
|
+
"oauth",
|
|
853
|
+
"claude.ai",
|
|
854
|
+
"unknown"
|
|
855
|
+
]);
|
|
856
|
+
const agentControlStateSchema = z.enum([
|
|
857
|
+
"running",
|
|
858
|
+
"pausing",
|
|
859
|
+
"paused"
|
|
860
|
+
]);
|
|
861
|
+
z.enum([
|
|
862
|
+
"session",
|
|
863
|
+
"diagnostic",
|
|
864
|
+
"task"
|
|
865
|
+
]);
|
|
866
|
+
const maxCostUsd = 1e3;
|
|
867
|
+
const countSchema = z.int().min(0).max(2147483647);
|
|
868
|
+
const costUsdSchema = z.number().min(0).max(maxCostUsd);
|
|
869
|
+
const instantSchema$2 = z.iso.datetime();
|
|
870
|
+
const workerEnvelope = (event) => ({
|
|
871
|
+
ts: instantSchema$2,
|
|
872
|
+
level: agentEventLevelSchema,
|
|
873
|
+
event: z.literal(event)
|
|
874
|
+
});
|
|
875
|
+
const roleEnvelope = (event) => ({
|
|
876
|
+
...workerEnvelope(event),
|
|
877
|
+
agent: agentRoleSchema.optional()
|
|
878
|
+
});
|
|
879
|
+
const agentEventSchema = z.discriminatedUnion("event", [
|
|
880
|
+
z.looseObject({
|
|
881
|
+
...workerEnvelope("worker.started"),
|
|
882
|
+
version: z.string(),
|
|
883
|
+
claudeVersion: z.string().optional(),
|
|
884
|
+
nodeVersion: z.string(),
|
|
885
|
+
authKind: authKindSchema,
|
|
886
|
+
pid: countSchema,
|
|
887
|
+
projectDir: z.string(),
|
|
888
|
+
paused: z.boolean().optional()
|
|
889
|
+
}),
|
|
890
|
+
z.looseObject({
|
|
891
|
+
...workerEnvelope("worker.stopped"),
|
|
892
|
+
signal: z.string().optional(),
|
|
893
|
+
drained: z.boolean().optional(),
|
|
894
|
+
forced: z.boolean().optional()
|
|
895
|
+
}),
|
|
896
|
+
z.looseObject({
|
|
897
|
+
...workerEnvelope("worker.draining"),
|
|
898
|
+
reason: z.string(),
|
|
899
|
+
timeoutMs: countSchema.optional()
|
|
900
|
+
}),
|
|
901
|
+
z.looseObject({
|
|
902
|
+
...workerEnvelope("update.available"),
|
|
903
|
+
from: z.string(),
|
|
904
|
+
to: z.string(),
|
|
905
|
+
installError: z.string().optional()
|
|
906
|
+
}),
|
|
907
|
+
z.looseObject({
|
|
908
|
+
...workerEnvelope("update.installed"),
|
|
909
|
+
from: z.string(),
|
|
910
|
+
to: z.string()
|
|
911
|
+
}),
|
|
912
|
+
z.looseObject({
|
|
913
|
+
...roleEnvelope("control.changed"),
|
|
914
|
+
state: agentControlStateSchema
|
|
915
|
+
}),
|
|
916
|
+
z.looseObject({
|
|
917
|
+
...roleEnvelope("cycle.started"),
|
|
918
|
+
cycle: countSchema
|
|
919
|
+
}),
|
|
920
|
+
z.looseObject({
|
|
921
|
+
...roleEnvelope("cycle.finished"),
|
|
922
|
+
cycle: countSchema,
|
|
923
|
+
ok: z.boolean(),
|
|
924
|
+
durationMs: countSchema,
|
|
925
|
+
costUsd: costUsdSchema.optional(),
|
|
926
|
+
addedTasks: countSchema,
|
|
927
|
+
skippedDuplicates: countSchema,
|
|
928
|
+
sessionId: z.string().optional(),
|
|
929
|
+
error: z.string().optional()
|
|
930
|
+
}),
|
|
931
|
+
z.looseObject({
|
|
932
|
+
...roleEnvelope("monitor.offHours"),
|
|
933
|
+
resumeAt: instantSchema$2
|
|
934
|
+
}),
|
|
935
|
+
z.looseObject({
|
|
936
|
+
...roleEnvelope("monitor.limitWait"),
|
|
937
|
+
resumeAt: instantSchema$2
|
|
938
|
+
}),
|
|
939
|
+
z.looseObject({
|
|
940
|
+
...roleEnvelope("monitor.sleeping"),
|
|
941
|
+
nextCycleAt: instantSchema$2
|
|
942
|
+
}),
|
|
943
|
+
z.looseObject({
|
|
944
|
+
...roleEnvelope("monitor.authBlocked"),
|
|
945
|
+
reason: z.string()
|
|
946
|
+
}),
|
|
947
|
+
z.looseObject({
|
|
948
|
+
...roleEnvelope("monitor.admissionWait"),
|
|
949
|
+
ticket: z.string(),
|
|
950
|
+
since: instantSchema$2,
|
|
951
|
+
seq: countSchema
|
|
952
|
+
}),
|
|
953
|
+
z.looseObject({
|
|
954
|
+
...roleEnvelope("executor.admissionWait"),
|
|
955
|
+
ticket: z.string(),
|
|
956
|
+
since: instantSchema$2,
|
|
957
|
+
seq: countSchema,
|
|
958
|
+
taskId: z.string()
|
|
959
|
+
}),
|
|
960
|
+
z.looseObject({
|
|
961
|
+
...roleEnvelope("admission.granted"),
|
|
962
|
+
ticket: z.string(),
|
|
963
|
+
seq: countSchema
|
|
964
|
+
}),
|
|
965
|
+
z.looseObject({
|
|
966
|
+
...roleEnvelope("admission.released"),
|
|
967
|
+
ticket: z.string(),
|
|
968
|
+
seq: countSchema
|
|
969
|
+
}),
|
|
970
|
+
z.looseObject({
|
|
971
|
+
...roleEnvelope("admission.cancelled"),
|
|
972
|
+
ticket: z.string(),
|
|
973
|
+
seq: countSchema
|
|
974
|
+
}),
|
|
975
|
+
z.looseObject({
|
|
976
|
+
...roleEnvelope("task.started"),
|
|
977
|
+
taskId: z.string(),
|
|
978
|
+
title: z.string()
|
|
979
|
+
}),
|
|
980
|
+
z.looseObject({
|
|
981
|
+
...roleEnvelope("task.finished"),
|
|
982
|
+
taskId: z.string(),
|
|
983
|
+
title: z.string(),
|
|
984
|
+
ok: z.boolean(),
|
|
985
|
+
durationMs: countSchema,
|
|
986
|
+
costUsd: costUsdSchema.optional(),
|
|
987
|
+
numTurns: countSchema.optional(),
|
|
988
|
+
willRetry: z.boolean().optional(),
|
|
989
|
+
attempt: countSchema.optional(),
|
|
990
|
+
maxAttempts: countSchema.optional(),
|
|
991
|
+
sessionId: z.string().optional(),
|
|
992
|
+
error: z.string().optional()
|
|
993
|
+
}),
|
|
994
|
+
z.looseObject({
|
|
995
|
+
...roleEnvelope("task.retryScheduled"),
|
|
996
|
+
taskId: z.string(),
|
|
997
|
+
resumeAt: instantSchema$2
|
|
998
|
+
}),
|
|
999
|
+
z.looseObject(roleEnvelope("executor.waiting")),
|
|
1000
|
+
z.looseObject({
|
|
1001
|
+
...roleEnvelope("executor.limitWait"),
|
|
1002
|
+
resumeAt: instantSchema$2
|
|
1003
|
+
}),
|
|
1004
|
+
z.looseObject({
|
|
1005
|
+
...roleEnvelope("executor.authBlocked"),
|
|
1006
|
+
reason: z.string()
|
|
1007
|
+
}),
|
|
1008
|
+
z.looseObject({
|
|
1009
|
+
...roleEnvelope("summary.started"),
|
|
1010
|
+
taskId: z.string()
|
|
1011
|
+
}),
|
|
1012
|
+
z.looseObject({
|
|
1013
|
+
...roleEnvelope("summary.finished"),
|
|
1014
|
+
taskId: z.string(),
|
|
1015
|
+
ok: z.boolean(),
|
|
1016
|
+
durationMs: countSchema,
|
|
1017
|
+
costUsd: costUsdSchema.optional(),
|
|
1018
|
+
sessionId: z.string().optional(),
|
|
1019
|
+
error: z.string().optional()
|
|
1020
|
+
}),
|
|
1021
|
+
z.looseObject({
|
|
1022
|
+
...roleEnvelope("session.init"),
|
|
1023
|
+
model: z.string(),
|
|
1024
|
+
sessionId: z.string(),
|
|
1025
|
+
taskId: z.string().optional(),
|
|
1026
|
+
cycle: countSchema.optional()
|
|
1027
|
+
}),
|
|
1028
|
+
z.looseObject({
|
|
1029
|
+
...roleEnvelope("session.stderr"),
|
|
1030
|
+
line: z.string()
|
|
1031
|
+
}),
|
|
1032
|
+
z.looseObject({
|
|
1033
|
+
...roleEnvelope("session.procError"),
|
|
1034
|
+
message: z.string()
|
|
1035
|
+
}),
|
|
1036
|
+
z.looseObject({
|
|
1037
|
+
...roleEnvelope("session.killed"),
|
|
1038
|
+
reason: z.string()
|
|
1039
|
+
}),
|
|
1040
|
+
z.looseObject({
|
|
1041
|
+
...roleEnvelope("session.text"),
|
|
1042
|
+
text: z.string()
|
|
1043
|
+
}),
|
|
1044
|
+
z.looseObject({
|
|
1045
|
+
...roleEnvelope("session.tool"),
|
|
1046
|
+
tool: z.string(),
|
|
1047
|
+
detail: z.string()
|
|
1048
|
+
}),
|
|
1049
|
+
z.looseObject({
|
|
1050
|
+
...roleEnvelope("session.toolError"),
|
|
1051
|
+
output: z.string()
|
|
1052
|
+
})
|
|
1053
|
+
]);
|
|
1054
|
+
z.looseObject({
|
|
1055
|
+
ts: instantSchema$2,
|
|
1056
|
+
level: agentEventLevelSchema,
|
|
1057
|
+
agent: agentRoleSchema.optional(),
|
|
1058
|
+
event: z.string().min(1).max(64)
|
|
1059
|
+
});
|
|
1060
|
+
agentEventSchema.options.map((option) => option.shape.event.value);
|
|
1061
|
+
({
|
|
1062
|
+
rawLog: "log.raw",
|
|
1063
|
+
rejectedLog: "log.rejected",
|
|
1064
|
+
throttledLog: "log.throttled",
|
|
1065
|
+
stateChanged: "platform.stateChanged",
|
|
1066
|
+
crashLooping: "platform.crashLooping",
|
|
1067
|
+
provisioned: "platform.provisioned",
|
|
1068
|
+
sandboxStarted: "platform.sandboxStarted",
|
|
1069
|
+
sandboxStopped: "platform.sandboxStopped"
|
|
1070
|
+
}).rawLog;
|
|
1071
|
+
const agentStateSchema = z.enum([
|
|
1072
|
+
"created",
|
|
1073
|
+
"provisioning",
|
|
1074
|
+
"stopped",
|
|
1075
|
+
"starting",
|
|
1076
|
+
"running",
|
|
1077
|
+
"pausing",
|
|
1078
|
+
"paused",
|
|
1079
|
+
"stopping",
|
|
1080
|
+
"updating",
|
|
1044
1081
|
"resetting",
|
|
1045
1082
|
"deleting",
|
|
1046
1083
|
"error"
|
|
@@ -1244,6 +1281,7 @@ const agentSessionSchema = z.object({
|
|
|
1244
1281
|
endedAt: z.iso.datetime().nullable(),
|
|
1245
1282
|
ok: z.boolean().nullable(),
|
|
1246
1283
|
killedReason: z.string().nullable(),
|
|
1284
|
+
failureReason: z.string().nullable(),
|
|
1247
1285
|
error: z.string().nullable(),
|
|
1248
1286
|
costUsd: z.number().nullable(),
|
|
1249
1287
|
durationMs: z.int().nullable(),
|
|
@@ -1402,23 +1440,123 @@ pageSchema(z.object({
|
|
|
1402
1440
|
finishedAt: z.iso.datetime().nullable()
|
|
1403
1441
|
}).meta({ id: "TaskCost" })).meta({ id: "TaskCostPage" });
|
|
1404
1442
|
costQuerySchema.extend({ cursor: cursorSchema.optional() });
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1443
|
+
z.enum(["monitor", "executor"]);
|
|
1444
|
+
const sessionSlotCountSchema = z.int().min(1).max(64);
|
|
1445
|
+
const sessionQueueEntrySchema = z.object({
|
|
1446
|
+
agentId: idSchema,
|
|
1447
|
+
agentName: z.string().nullable(),
|
|
1448
|
+
projectId: idSchema.nullable(),
|
|
1449
|
+
projectName: z.string().nullable(),
|
|
1450
|
+
position: z.int().positive(),
|
|
1451
|
+
since: z.iso.datetime(),
|
|
1452
|
+
taskId: z.string().nullable()
|
|
1453
|
+
}).meta({ id: "SessionQueueEntry" });
|
|
1454
|
+
const sessionPoolSchema = z.object({
|
|
1455
|
+
slots: sessionSlotCountSchema.nullable(),
|
|
1456
|
+
effective: z.int().nonnegative(),
|
|
1457
|
+
inUse: z.int().nonnegative(),
|
|
1458
|
+
waiting: z.array(sessionQueueEntrySchema)
|
|
1459
|
+
}).meta({ id: "SessionPool" });
|
|
1460
|
+
z.object({
|
|
1461
|
+
version: z.int().nonnegative(),
|
|
1462
|
+
ready: z.boolean(),
|
|
1463
|
+
monitor: sessionPoolSchema,
|
|
1464
|
+
executor: sessionPoolSchema
|
|
1465
|
+
}).meta({ id: "SessionSlots" });
|
|
1466
|
+
z.object({
|
|
1467
|
+
version: z.int().nonnegative(),
|
|
1468
|
+
monitor: sessionSlotCountSchema.nullable(),
|
|
1469
|
+
executor: sessionSlotCountSchema.nullable()
|
|
1470
|
+
}).meta({ id: "UpdateSessionSlotsBody" });
|
|
1471
|
+
const agentAdmissionStateSchema = z.discriminatedUnion("state", [z.object({
|
|
1472
|
+
state: z.literal("waiting"),
|
|
1473
|
+
since: z.iso.datetime(),
|
|
1474
|
+
position: z.int().positive()
|
|
1475
|
+
}), z.object({ state: z.literal("holding") })]).meta({ id: "AgentAdmissionState" });
|
|
1476
|
+
const agentAdmissionSchema = z.object({
|
|
1477
|
+
monitor: agentAdmissionStateSchema.nullable(),
|
|
1478
|
+
executor: agentAdmissionStateSchema.nullable()
|
|
1479
|
+
}).meta({ id: "AgentAdmission" });
|
|
1480
|
+
const nodeStateSchema = z.enum([
|
|
1481
|
+
"pending",
|
|
1482
|
+
"online",
|
|
1483
|
+
"offline"
|
|
1484
|
+
]);
|
|
1485
|
+
const nodeProviderSchema = z.enum(["self_hosted"]);
|
|
1486
|
+
const nodeNameSchema = z.string().trim().regex(slugPattern);
|
|
1487
|
+
const nodeHostSchema = z.object({
|
|
1413
1488
|
cpus: z.number().nonnegative(),
|
|
1414
1489
|
memoryBytes: z.number().nonnegative(),
|
|
1415
1490
|
dataRoot: z.string(),
|
|
1416
1491
|
serverVersion: z.string()
|
|
1417
1492
|
}).meta({ id: "NodeHost" });
|
|
1493
|
+
const nodeCapabilityNames = [
|
|
1494
|
+
"linux",
|
|
1495
|
+
"windows",
|
|
1496
|
+
"docker",
|
|
1497
|
+
"isolation",
|
|
1498
|
+
"gpu",
|
|
1499
|
+
"desktop"
|
|
1500
|
+
];
|
|
1501
|
+
const nodeCapabilitySchema = z.enum(nodeCapabilityNames).meta({ id: "NodeCapability" });
|
|
1418
1502
|
const nodeCapabilitiesSchema = z.object({
|
|
1419
1503
|
sandbox: z.boolean(),
|
|
1420
|
-
uidIsolation: z.boolean()
|
|
1504
|
+
uidIsolation: z.boolean(),
|
|
1505
|
+
offered: z.array(nodeCapabilitySchema).optional()
|
|
1421
1506
|
}).meta({ id: "NodeCapabilities" });
|
|
1507
|
+
const minPoolCpus = .1;
|
|
1508
|
+
const maxSplitCpus = 1024;
|
|
1509
|
+
const maxSplitMemoryMb = 16777216;
|
|
1510
|
+
const hundredths = (value) => Math.abs(Math.round(value * 100) - value * 100) < 1e-9;
|
|
1511
|
+
const splitCpusSchema = (min) => z.number().min(min).max(maxSplitCpus).refine(hundredths, { message: "expected at most two decimal places" });
|
|
1512
|
+
const splitMemorySchema = (min) => z.int().min(min).max(maxSplitMemoryMb);
|
|
1513
|
+
const nodeReserveSchema = z.object({
|
|
1514
|
+
cpus: splitCpusSchema(0),
|
|
1515
|
+
memoryMb: splitMemorySchema(0)
|
|
1516
|
+
}).meta({ id: "NodeReserve" });
|
|
1517
|
+
const nodePoolSplitSchema = z.object({
|
|
1518
|
+
cpus: splitCpusSchema(minPoolCpus),
|
|
1519
|
+
memoryMb: splitMemorySchema(256),
|
|
1520
|
+
slots: z.int().min(1).max(64)
|
|
1521
|
+
}).meta({ id: "NodePoolSplit" });
|
|
1522
|
+
const nodeSplitSchema = z.object({
|
|
1523
|
+
reserve: nodeReserveSchema,
|
|
1524
|
+
monitor: nodePoolSplitSchema,
|
|
1525
|
+
executor: nodePoolSplitSchema
|
|
1526
|
+
}).meta({ id: "NodeSplit" });
|
|
1527
|
+
const poolLimitSchema = z.object({
|
|
1528
|
+
enforced: z.boolean(),
|
|
1529
|
+
reason: z.enum([
|
|
1530
|
+
"release",
|
|
1531
|
+
"restart",
|
|
1532
|
+
"node"
|
|
1533
|
+
]).nullable()
|
|
1534
|
+
}).meta({ id: "PoolLimit" });
|
|
1535
|
+
const poolEnforcementSchema = z.object({
|
|
1536
|
+
state: z.enum([
|
|
1537
|
+
"enforced",
|
|
1538
|
+
"unsplit",
|
|
1539
|
+
"unsupported"
|
|
1540
|
+
]),
|
|
1541
|
+
reason: z.string().nullable()
|
|
1542
|
+
}).meta({ id: "PoolEnforcement" });
|
|
1543
|
+
const poolUsageSchema = z.object({
|
|
1544
|
+
memoryBytes: z.int().nonnegative(),
|
|
1545
|
+
memoryMaxBytes: z.int().nonnegative().nullable(),
|
|
1546
|
+
cpus: z.number().nonnegative().nullable(),
|
|
1547
|
+
memoryKills: z.int().nonnegative()
|
|
1548
|
+
}).meta({ id: "PoolUsage" });
|
|
1549
|
+
const nodePoolSchema = z.object({
|
|
1550
|
+
slots: z.int().nonnegative(),
|
|
1551
|
+
inUse: z.int().nonnegative(),
|
|
1552
|
+
waiting: z.array(sessionQueueEntrySchema),
|
|
1553
|
+
usage: poolUsageSchema.nullable()
|
|
1554
|
+
}).meta({ id: "NodePool" });
|
|
1555
|
+
const nodePoolsSchema = z.object({
|
|
1556
|
+
enforcement: poolEnforcementSchema,
|
|
1557
|
+
monitor: nodePoolSchema,
|
|
1558
|
+
executor: nodePoolSchema
|
|
1559
|
+
}).meta({ id: "NodePools" });
|
|
1422
1560
|
const nodeSchema = z.object({
|
|
1423
1561
|
id: idSchema,
|
|
1424
1562
|
name: nodeNameSchema,
|
|
@@ -1428,19 +1566,46 @@ const nodeSchema = z.object({
|
|
|
1428
1566
|
version: z.string().nullable(),
|
|
1429
1567
|
host: nodeHostSchema.nullable(),
|
|
1430
1568
|
capabilities: nodeCapabilitiesSchema.nullable(),
|
|
1569
|
+
offered: z.array(nodeCapabilitySchema),
|
|
1570
|
+
split: nodeSplitSchema.nullable(),
|
|
1571
|
+
revision: z.int().nonnegative(),
|
|
1431
1572
|
agentCount: z.int().nonnegative(),
|
|
1432
1573
|
connectedAt: z.iso.datetime().nullable(),
|
|
1433
1574
|
lastSeenAt: z.iso.datetime().nullable(),
|
|
1575
|
+
offlineSince: z.iso.datetime().nullable(),
|
|
1434
1576
|
registrationExpiresAt: z.iso.datetime().nullable(),
|
|
1435
1577
|
createdAt: z.iso.datetime()
|
|
1436
1578
|
}).meta({ id: "Node" });
|
|
1579
|
+
const nodeUpdateSchema = z.object({
|
|
1580
|
+
state: z.enum([
|
|
1581
|
+
"current",
|
|
1582
|
+
"waiting",
|
|
1583
|
+
"updating",
|
|
1584
|
+
"failed",
|
|
1585
|
+
"unmanaged"
|
|
1586
|
+
]),
|
|
1587
|
+
target: z.string().nullable(),
|
|
1588
|
+
sessions: z.int().nonnegative(),
|
|
1589
|
+
since: z.iso.datetime().nullable(),
|
|
1590
|
+
last: z.object({
|
|
1591
|
+
outcome: z.enum([
|
|
1592
|
+
"updated",
|
|
1593
|
+
"rolledBack",
|
|
1594
|
+
"failed"
|
|
1595
|
+
]),
|
|
1596
|
+
version: z.string().nullable(),
|
|
1597
|
+
reason: z.string().nullable(),
|
|
1598
|
+
at: z.iso.datetime()
|
|
1599
|
+
}).nullable()
|
|
1600
|
+
}).meta({ id: "NodeUpdate" });
|
|
1437
1601
|
const nodeAgentSchema = z.object({
|
|
1438
1602
|
id: idSchema,
|
|
1439
1603
|
name: z.string(),
|
|
1440
1604
|
slug: z.string(),
|
|
1441
1605
|
projectId: idSchema,
|
|
1442
1606
|
projectName: z.string(),
|
|
1443
|
-
state: agentStateSchema
|
|
1607
|
+
state: agentStateSchema,
|
|
1608
|
+
poolLimit: poolLimitSchema.nullable()
|
|
1444
1609
|
}).meta({ id: "NodeAgent" });
|
|
1445
1610
|
const nodeRuntimeSchema = z.object({
|
|
1446
1611
|
reportedAt: z.iso.datetime(),
|
|
@@ -1473,11 +1638,13 @@ const nodeRuntimeSchema = z.object({
|
|
|
1473
1638
|
effective: z.int().nonnegative(),
|
|
1474
1639
|
inUse: z.int().nonnegative()
|
|
1475
1640
|
})
|
|
1476
|
-
})
|
|
1641
|
+
}),
|
|
1642
|
+
pools: nodePoolsSchema
|
|
1477
1643
|
}).meta({ id: "NodeRuntime" });
|
|
1478
1644
|
nodeSchema.extend({
|
|
1479
1645
|
agents: z.array(nodeAgentSchema),
|
|
1480
|
-
runtime: nodeRuntimeSchema.nullable()
|
|
1646
|
+
runtime: nodeRuntimeSchema.nullable(),
|
|
1647
|
+
update: nodeUpdateSchema.nullable()
|
|
1481
1648
|
}).meta({ id: "NodeDetail" });
|
|
1482
1649
|
z.object({ items: z.array(nodeSchema) }).meta({ id: "NodeList" });
|
|
1483
1650
|
z.object({ nodeId: idSchema });
|
|
@@ -1485,6 +1652,10 @@ z.object({
|
|
|
1485
1652
|
organizationId: idSchema,
|
|
1486
1653
|
nodeId: idSchema
|
|
1487
1654
|
});
|
|
1655
|
+
z.object({
|
|
1656
|
+
revision: z.int().nonnegative(),
|
|
1657
|
+
split: nodeSplitSchema.nullable()
|
|
1658
|
+
}).meta({ id: "UpdateNodeSplitBody" });
|
|
1488
1659
|
const createNodeBodySchema = z.object({ name: nodeNameSchema }).meta({ id: "CreateNodeBody" });
|
|
1489
1660
|
const nodeRegistrationSchema = z.object({
|
|
1490
1661
|
token: z.string(),
|
|
@@ -1505,6 +1676,21 @@ z.object({
|
|
|
1505
1676
|
nodeId: idSchema,
|
|
1506
1677
|
credential: z.string()
|
|
1507
1678
|
}).meta({ id: "RegisteredNode" });
|
|
1679
|
+
const bytesPerMegabyte = 1048576;
|
|
1680
|
+
const inHundredths = (value) => Math.round(value * 100);
|
|
1681
|
+
const hostMemoryMbOf = (host) => Math.floor(host.memoryBytes / bytesPerMegabyte);
|
|
1682
|
+
const splitOverflowOf = (split, host) => {
|
|
1683
|
+
const cpus = inHundredths(split.reserve.cpus) + inHundredths(split.monitor.cpus) + inHundredths(split.executor.cpus) - inHundredths(host.cpus);
|
|
1684
|
+
const memoryMb = split.reserve.memoryMb + split.monitor.memoryMb + split.executor.memoryMb - hostMemoryMbOf(host);
|
|
1685
|
+
return {
|
|
1686
|
+
cpus: cpus / 100,
|
|
1687
|
+
memoryMb
|
|
1688
|
+
};
|
|
1689
|
+
};
|
|
1690
|
+
const splitFits = (split, host) => {
|
|
1691
|
+
const overflow = splitOverflowOf(split, host);
|
|
1692
|
+
return overflow.cpus <= 0 && overflow.memoryMb <= 0;
|
|
1693
|
+
};
|
|
1508
1694
|
//#endregion
|
|
1509
1695
|
//#region ../../packages/contracts/src/identity.ts
|
|
1510
1696
|
const emailSchema = z.email().max(254);
|
|
@@ -1674,6 +1860,8 @@ const auditActionSchema = z.enum([
|
|
|
1674
1860
|
"agent.updated",
|
|
1675
1861
|
"agent.deleted",
|
|
1676
1862
|
"agent.action",
|
|
1863
|
+
"agent.backup_requested",
|
|
1864
|
+
"agent.restored",
|
|
1677
1865
|
"platform.settings_updated",
|
|
1678
1866
|
"runtime.sessions_updated",
|
|
1679
1867
|
"platform.upgrade_requested",
|
|
@@ -1692,6 +1880,7 @@ const auditActionSchema = z.enum([
|
|
|
1692
1880
|
"node.registered",
|
|
1693
1881
|
"node.registration_renewed",
|
|
1694
1882
|
"node.deleted",
|
|
1883
|
+
"node.split_changed",
|
|
1695
1884
|
"master_key.reencrypted",
|
|
1696
1885
|
"dead_letter.retried",
|
|
1697
1886
|
"dead_letter.discarded"
|
|
@@ -1785,43 +1974,6 @@ z.object({
|
|
|
1785
1974
|
id: idSchema,
|
|
1786
1975
|
userId: idSchema
|
|
1787
1976
|
});
|
|
1788
|
-
z.enum(["monitor", "executor"]);
|
|
1789
|
-
const sessionSlotCountSchema = z.int().min(1).max(64);
|
|
1790
|
-
const sessionQueueEntrySchema = z.object({
|
|
1791
|
-
agentId: idSchema,
|
|
1792
|
-
agentName: z.string().nullable(),
|
|
1793
|
-
projectId: idSchema.nullable(),
|
|
1794
|
-
projectName: z.string().nullable(),
|
|
1795
|
-
position: z.int().positive(),
|
|
1796
|
-
since: z.iso.datetime(),
|
|
1797
|
-
taskId: z.string().nullable()
|
|
1798
|
-
}).meta({ id: "SessionQueueEntry" });
|
|
1799
|
-
const sessionPoolSchema = z.object({
|
|
1800
|
-
slots: sessionSlotCountSchema.nullable(),
|
|
1801
|
-
effective: z.int().nonnegative(),
|
|
1802
|
-
inUse: z.int().nonnegative(),
|
|
1803
|
-
waiting: z.array(sessionQueueEntrySchema)
|
|
1804
|
-
}).meta({ id: "SessionPool" });
|
|
1805
|
-
z.object({
|
|
1806
|
-
version: z.int().nonnegative(),
|
|
1807
|
-
ready: z.boolean(),
|
|
1808
|
-
monitor: sessionPoolSchema,
|
|
1809
|
-
executor: sessionPoolSchema
|
|
1810
|
-
}).meta({ id: "SessionSlots" });
|
|
1811
|
-
z.object({
|
|
1812
|
-
version: z.int().nonnegative(),
|
|
1813
|
-
monitor: sessionSlotCountSchema.nullable(),
|
|
1814
|
-
executor: sessionSlotCountSchema.nullable()
|
|
1815
|
-
}).meta({ id: "UpdateSessionSlotsBody" });
|
|
1816
|
-
const agentAdmissionStateSchema = z.discriminatedUnion("state", [z.object({
|
|
1817
|
-
state: z.literal("waiting"),
|
|
1818
|
-
since: z.iso.datetime(),
|
|
1819
|
-
position: z.int().positive()
|
|
1820
|
-
}), z.object({ state: z.literal("holding") })]).meta({ id: "AgentAdmissionState" });
|
|
1821
|
-
const agentAdmissionSchema = z.object({
|
|
1822
|
-
monitor: agentAdmissionStateSchema.nullable(),
|
|
1823
|
-
executor: agentAdmissionStateSchema.nullable()
|
|
1824
|
-
}).meta({ id: "AgentAdmission" });
|
|
1825
1977
|
//#endregion
|
|
1826
1978
|
//#region ../../packages/contracts/src/releases.ts
|
|
1827
1979
|
const kikimoraVersionSchema = z.string().regex(versionTagPattern);
|
|
@@ -2215,7 +2367,8 @@ const agentRuntimeSchema = z.object({
|
|
|
2215
2367
|
provision: agentProvisionSchema.nullable(),
|
|
2216
2368
|
stopMode: agentStopModeSchema.nullable(),
|
|
2217
2369
|
stopDeadlineAt: z.iso.datetime().nullable(),
|
|
2218
|
-
admission: agentAdmissionSchema.nullable()
|
|
2370
|
+
admission: agentAdmissionSchema.nullable(),
|
|
2371
|
+
poolLimit: poolLimitSchema.nullable()
|
|
2219
2372
|
}).meta({ id: "AgentRuntime" });
|
|
2220
2373
|
const agentListItemSchema = z.object({
|
|
2221
2374
|
id: idSchema,
|
|
@@ -2254,6 +2407,7 @@ agentListItemSchema.extend({
|
|
|
2254
2407
|
release: releaseSummarySchema.nullable(),
|
|
2255
2408
|
runtime: agentRuntimeSchema,
|
|
2256
2409
|
node: agentNodeSchema.nullable(),
|
|
2410
|
+
requiredCapabilities: z.array(nodeCapabilitySchema),
|
|
2257
2411
|
runtimeUid: z.int().positive().nullable(),
|
|
2258
2412
|
sandbox: agentSandboxSchema.nullable(),
|
|
2259
2413
|
createdAt: z.iso.datetime()
|
|
@@ -2271,12 +2425,15 @@ const distinctCredentials = {
|
|
|
2271
2425
|
const hasUniqueTools = (assignments) => new Set(assignments.map((assignment) => assignment.toolId)).size === assignments.length;
|
|
2272
2426
|
const agentRepositoryLinksSchema = z.array(agentRepositoryLinkSchema).min(1).max(32);
|
|
2273
2427
|
const agentToolAssignmentsSchema = z.array(toolAssignmentSchema).max(32).refine(hasUniqueTools, { message: "expected every tool to be assigned once" });
|
|
2428
|
+
const hasUniqueCapabilities = (names) => new Set(names).size === names.length;
|
|
2429
|
+
const requiredCapabilitiesSchema = z.array(nodeCapabilitySchema).max(nodeCapabilityNames.length).refine(hasUniqueCapabilities, { message: "expected every capability once" });
|
|
2274
2430
|
const createAgentBodySchema = z.object({
|
|
2275
2431
|
name: agentNameSchema,
|
|
2276
2432
|
slug: slugSchema.optional(),
|
|
2277
2433
|
description: agentDescriptionSchema,
|
|
2278
2434
|
kikimoraVersion: kikimoraVersionSchema,
|
|
2279
2435
|
nodeId: idSchema,
|
|
2436
|
+
requiredCapabilities: requiredCapabilitiesSchema.optional(),
|
|
2280
2437
|
claudeCredentialId: idSchema,
|
|
2281
2438
|
githubCredentialId: idSchema,
|
|
2282
2439
|
repositories: agentRepositoryLinksSchema,
|
|
@@ -2344,6 +2501,43 @@ z.object({
|
|
|
2344
2501
|
z.object({ code: errorCodeSchema })
|
|
2345
2502
|
])
|
|
2346
2503
|
}).meta({ id: "AgentEventFrame" });
|
|
2504
|
+
//#endregion
|
|
2505
|
+
//#region ../../packages/contracts/src/agent-backups.ts
|
|
2506
|
+
const agentBackupTriggers = ["nightly", "manual"];
|
|
2507
|
+
const agentBackupSchema = z.object({
|
|
2508
|
+
id: idSchema,
|
|
2509
|
+
agentId: idSchema,
|
|
2510
|
+
nodeId: idSchema,
|
|
2511
|
+
nodeName: z.string().nullable(),
|
|
2512
|
+
trigger: z.enum(agentBackupTriggers),
|
|
2513
|
+
kikimoraVersion: z.string(),
|
|
2514
|
+
entries: z.int().nonnegative(),
|
|
2515
|
+
sizeBytes: z.int().nonnegative(),
|
|
2516
|
+
createdAt: z.iso.datetime(),
|
|
2517
|
+
expiresAt: z.iso.datetime()
|
|
2518
|
+
}).meta({ id: "AgentBackup" });
|
|
2519
|
+
z.object({
|
|
2520
|
+
items: z.array(agentBackupSchema),
|
|
2521
|
+
retentionDays: z.int().positive()
|
|
2522
|
+
}).meta({ id: "AgentBackupList" });
|
|
2523
|
+
z.object({ state: z.literal("requested") }).meta({ id: "AgentBackupRequested" });
|
|
2524
|
+
z.object({
|
|
2525
|
+
id: idSchema,
|
|
2526
|
+
backupId: idSchema
|
|
2527
|
+
});
|
|
2528
|
+
z.object({
|
|
2529
|
+
name: agentNameSchema,
|
|
2530
|
+
slug: slugSchema.optional(),
|
|
2531
|
+
nodeId: idSchema
|
|
2532
|
+
}).meta({ id: "RestoreAgentBody" });
|
|
2533
|
+
z.object({ agentId: idSchema }).meta({ id: "RestoredAgent" });
|
|
2534
|
+
z.object({
|
|
2535
|
+
agentId: idSchema,
|
|
2536
|
+
trigger: z.enum(agentBackupTriggers),
|
|
2537
|
+
kikimoraVersion: z.string().min(1).max(64),
|
|
2538
|
+
entries: z.coerce.number().int().nonnegative()
|
|
2539
|
+
});
|
|
2540
|
+
z.object({ backupId: idSchema });
|
|
2347
2541
|
[
|
|
2348
2542
|
"application/vnd.oci.image.index.v1+json",
|
|
2349
2543
|
"application/vnd.oci.image.manifest.v1+json",
|
|
@@ -3524,6 +3718,116 @@ const createWhoamiCommand = (adaptersOf) => defineCommand({
|
|
|
3524
3718
|
}
|
|
3525
3719
|
});
|
|
3526
3720
|
//#endregion
|
|
3721
|
+
//#region src/render/body.ts
|
|
3722
|
+
const isTerminal$1 = (stream) => stream.isTTY === true;
|
|
3723
|
+
const pipedText$1 = async (stream) => {
|
|
3724
|
+
const chunks = [];
|
|
3725
|
+
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
3726
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3727
|
+
};
|
|
3728
|
+
const fileOptions = { file: { type: "string" } };
|
|
3729
|
+
const fileUsage = (what) => [["--file <path>", `read ${what} from a JSON file, or from stdin with -`]];
|
|
3730
|
+
const documentOf = async (run, file) => {
|
|
3731
|
+
const text = file === "-" ? isTerminal$1(run.context.io.stdin) ? "" : await pipedText$1(run.context.io.stdin) : await readFileOrRefuse(resolve(run.context.workingDirectory, file));
|
|
3732
|
+
if (text.trim() === "") throw new UsageError(file === "-" ? "Nothing is piped on stdin." : `${file} is empty.`);
|
|
3733
|
+
try {
|
|
3734
|
+
return JSON.parse(text);
|
|
3735
|
+
} catch {
|
|
3736
|
+
throw new CliError(`${file === "-" ? "stdin" : file} is not JSON.`);
|
|
3737
|
+
}
|
|
3738
|
+
};
|
|
3739
|
+
const readFileOrRefuse = async (path) => {
|
|
3740
|
+
try {
|
|
3741
|
+
return await readFile(path, "utf8");
|
|
3742
|
+
} catch {
|
|
3743
|
+
throw new CliError(`${path} could not be read.`);
|
|
3744
|
+
}
|
|
3745
|
+
};
|
|
3746
|
+
const validated = (schema, document, what) => {
|
|
3747
|
+
const parsed = schema.safeParse(document);
|
|
3748
|
+
if (!parsed.success) throw new CliError(`That is not ${what}.`, z.prettifyError(parsed.error));
|
|
3749
|
+
};
|
|
3750
|
+
//#endregion
|
|
3751
|
+
//#region src/commands/nodes/shared.ts
|
|
3752
|
+
const nodeHints = {
|
|
3753
|
+
...organizationHints,
|
|
3754
|
+
"node.not_found": "kcloud nodes list shows the nodes of the organization.",
|
|
3755
|
+
"node.name_taken": "Another node of the organization has that name.",
|
|
3756
|
+
"node.in_use": "Delete the agents placed on it first; kcloud nodes show <node> lists them.",
|
|
3757
|
+
"node.registration_used": "The node has joined and holds its own credential; it needs no token."
|
|
3758
|
+
};
|
|
3759
|
+
const splitHints = {
|
|
3760
|
+
...nodeHints,
|
|
3761
|
+
"auth.forbidden": "Splitting a node takes an admin of its organization.",
|
|
3762
|
+
"node.host_unknown": "The node reports its CPUs and memory once its service runs; kcloud nodes show <node> shows its state.",
|
|
3763
|
+
"node.split_exceeds_host": "The split asks for more than the host has; kcloud nodes split <node> shows the host.",
|
|
3764
|
+
"concurrency.conflict": "Someone changed the node meanwhile; run the command again."
|
|
3765
|
+
};
|
|
3766
|
+
const nodesOf = async (api, organizationId) => dataOf("list the nodes", await api.GET("/organizations/{organizationId}/nodes", { params: { path: { organizationId } } }), nodeHints).items;
|
|
3767
|
+
const namesOf = (nodes) => nodes.map(({ name }) => name).join(", ");
|
|
3768
|
+
const nodeIn = (nodes, reference, organization) => {
|
|
3769
|
+
const found = nodes.find((node) => node.id === reference.toLowerCase() || node.name === reference);
|
|
3770
|
+
if (found === void 0) throw new CliError(`There is no node ${reference} in ${organization}.`, nodes.length === 0 ? "kcloud nodes create <name> creates one." : `kcloud nodes list shows ${namesOf(nodes)}.`);
|
|
3771
|
+
return found;
|
|
3772
|
+
};
|
|
3773
|
+
const resolveNode = async (scope, reference) => {
|
|
3774
|
+
const organization = await scope.organization();
|
|
3775
|
+
if (isId(reference)) return {
|
|
3776
|
+
organizationId: organization.id,
|
|
3777
|
+
nodeId: reference
|
|
3778
|
+
};
|
|
3779
|
+
const node = nodeIn(await nodesOf(scope.api, organization.id), reference, organization.slug);
|
|
3780
|
+
return {
|
|
3781
|
+
organizationId: organization.id,
|
|
3782
|
+
nodeId: node.id
|
|
3783
|
+
};
|
|
3784
|
+
};
|
|
3785
|
+
const nodeDetailOf = async (api, { organizationId, nodeId }) => dataOf("describe the node", await api.GET("/organizations/{organizationId}/nodes/{nodeId}", { params: { path: {
|
|
3786
|
+
organizationId,
|
|
3787
|
+
nodeId
|
|
3788
|
+
} } }), nodeHints);
|
|
3789
|
+
const createdNode = async (api, organizationId, name) => {
|
|
3790
|
+
const body = { name };
|
|
3791
|
+
validated(createNodeBodySchema, body, "a node");
|
|
3792
|
+
return dataOf("create the node", await api.POST("/organizations/{organizationId}/nodes", {
|
|
3793
|
+
params: { path: { organizationId } },
|
|
3794
|
+
body
|
|
3795
|
+
}), nodeHints);
|
|
3796
|
+
};
|
|
3797
|
+
const renewedRegistration = async (api, { organizationId, nodeId }) => dataOf("issue a registration token for the node", await api.POST("/organizations/{organizationId}/nodes/{nodeId}/registration", { params: { path: {
|
|
3798
|
+
organizationId,
|
|
3799
|
+
nodeId
|
|
3800
|
+
} } }), nodeHints);
|
|
3801
|
+
const deleteNode = async (api, { organizationId, nodeId }) => {
|
|
3802
|
+
doneWith("delete the node", await api.DELETE("/organizations/{organizationId}/nodes/{nodeId}", { params: { path: {
|
|
3803
|
+
organizationId,
|
|
3804
|
+
nodeId
|
|
3805
|
+
} } }), nodeHints);
|
|
3806
|
+
};
|
|
3807
|
+
const splitNode = async (api, { organizationId, nodeId }, revision, split) => dataOf(split === null ? "clear the split of the node" : "split the node", await api.PUT("/organizations/{organizationId}/nodes/{nodeId}/split", {
|
|
3808
|
+
params: { path: {
|
|
3809
|
+
organizationId,
|
|
3810
|
+
nodeId
|
|
3811
|
+
} },
|
|
3812
|
+
body: {
|
|
3813
|
+
revision,
|
|
3814
|
+
split
|
|
3815
|
+
}
|
|
3816
|
+
}), splitHints);
|
|
3817
|
+
const nodeUsage = [["", "a node is named by its name or its id"]];
|
|
3818
|
+
//#endregion
|
|
3819
|
+
//#region src/commands/projects/shared.ts
|
|
3820
|
+
const defaultProjectColor = "#7c86ff";
|
|
3821
|
+
const projectHint = {
|
|
3822
|
+
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
3823
|
+
"project.slug_taken": "Another project of the organization has that slug.",
|
|
3824
|
+
"project.invalid_slug": "A slug holds lowercase letters, digits and hyphens.",
|
|
3825
|
+
"project.not_empty": "Delete its agents, credentials, repositories and tools first.",
|
|
3826
|
+
"project.member_not_found": "kcloud projects members list <project> shows them.",
|
|
3827
|
+
"project.member_must_be_active": "kcloud orgs members enable <member> enables the account first."
|
|
3828
|
+
};
|
|
3829
|
+
const projectOf = async (api, id) => dataOf("describe the project", await api.GET("/projects/{id}", { params: { path: { id } } }), projectHint);
|
|
3830
|
+
//#endregion
|
|
3527
3831
|
//#region src/commands/run.ts
|
|
3528
3832
|
const runOf = async (adaptersOf, { settings, context }) => {
|
|
3529
3833
|
const adapters = adaptersOf(context);
|
|
@@ -3554,6 +3858,7 @@ const agentHints = {
|
|
|
3554
3858
|
"agent.invalid_transition": "kcloud agents show <agent> lists the actions it takes now.",
|
|
3555
3859
|
"agent.busy": "The agent is working on another action; try again in a moment.",
|
|
3556
3860
|
"concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
|
|
3861
|
+
"agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
|
|
3557
3862
|
"node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen."
|
|
3558
3863
|
};
|
|
3559
3864
|
const agentFrom = async (run, reference, project) => {
|
|
@@ -3561,62 +3866,187 @@ const agentFrom = async (run, reference, project) => {
|
|
|
3561
3866
|
return resolveAgentId(run, reference, project);
|
|
3562
3867
|
};
|
|
3563
3868
|
//#endregion
|
|
3564
|
-
//#region src/commands/agents/
|
|
3565
|
-
const
|
|
3869
|
+
//#region src/commands/agents/backups.ts
|
|
3870
|
+
const backupsListHint = "kcloud agents backups list <agent> shows the backups of the agent and their ids.";
|
|
3871
|
+
const backupHints = {
|
|
3566
3872
|
...agentHints,
|
|
3567
|
-
"
|
|
3568
|
-
"
|
|
3569
|
-
"
|
|
3570
|
-
"
|
|
3571
|
-
"runtime.monitor_check_unsupported": "Move the agent to Kikimora 0.10.0 or newer with kcloud agents set, then kcloud agents update."
|
|
3873
|
+
"agent.backup_unavailable": "Backups need the Spaces of the platform and a node that can make them. An administrator of the platform configures the Spaces.",
|
|
3874
|
+
"agent.backup_without_export": "The Kikimora release of the agent cannot export its memory. Move it to 0.12.0 or newer with kcloud agents set, then kcloud agents update.",
|
|
3875
|
+
"agent.backup_in_progress": "A backup of the agent is running already. kcloud agents backups list <agent> shows it once the node has sent it.",
|
|
3876
|
+
"node.offline": "The node of the agent is not connected, so it cannot make a backup. kcloud nodes show <node> tells when it was last seen."
|
|
3572
3877
|
};
|
|
3573
|
-
const
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3878
|
+
const restoreHints = {
|
|
3879
|
+
...agentHints,
|
|
3880
|
+
"node.not_found": "kcloud nodes list shows the nodes of the organization.",
|
|
3881
|
+
"node.offline": "The node is not connected. kcloud nodes show <node> tells when it was last seen.",
|
|
3882
|
+
"agent.backup_not_found": `The backup does not exist or has expired. ${backupsListHint}`,
|
|
3883
|
+
"agent.slug_taken": "Another agent of the project has that slug. Pass another with --slug.",
|
|
3884
|
+
"agent.invalid_slug": "A slug holds lowercase letters, digits and hyphens."
|
|
3885
|
+
};
|
|
3886
|
+
const backupsOf = async (api, agentId) => dataOf("list the backups of the agent", await api.GET("/agents/{id}/backups", { params: { path: { id: agentId } } }), backupHints);
|
|
3887
|
+
const requestedBackup = async (api, agentId) => dataOf("back the agent up", await api.POST("/agents/{id}/backups", { params: { path: { id: agentId } } }), backupHints);
|
|
3888
|
+
const nodeIdFor = async (api, agentId, node) => {
|
|
3889
|
+
if (isId(node)) return node;
|
|
3890
|
+
const agent = dataOf("describe the agent", await api.GET("/agents/{id}", { params: { path: { id: agentId } } }), agentHints);
|
|
3891
|
+
const project = await projectOf(api, agent.projectId);
|
|
3892
|
+
const nodes = await nodesOf(api, project.organizationId);
|
|
3893
|
+
return nodeIn(nodes, node, `the organization of ${project.slug}`).id;
|
|
3894
|
+
};
|
|
3895
|
+
const restoredAgent = async (api, agentId, { backupId, name, node, slug }) => {
|
|
3896
|
+
const nodeId = await nodeIdFor(api, agentId, node);
|
|
3897
|
+
return dataOf("restore the agent from the backup", await api.POST("/agents/{id}/backups/{backupId}/restore", {
|
|
3898
|
+
params: { path: {
|
|
3899
|
+
id: agentId,
|
|
3900
|
+
backupId
|
|
3901
|
+
} },
|
|
3902
|
+
body: {
|
|
3903
|
+
name,
|
|
3904
|
+
nodeId,
|
|
3905
|
+
...slug === void 0 ? {} : { slug }
|
|
3906
|
+
}
|
|
3907
|
+
}), restoreHints);
|
|
3908
|
+
};
|
|
3909
|
+
const keptFor = (days) => `A backup is kept for ${String(days)} ${days === 1 ? "day" : "days"}.`;
|
|
3910
|
+
const backupsTable = ({ items, retentionDays }) => items.length === 0 ? `The agent has no backup yet. ${keptFor(retentionDays)}` : [renderTable([
|
|
3911
|
+
{ header: "CREATED" },
|
|
3912
|
+
{ header: "TRIGGER" },
|
|
3913
|
+
{ header: "NODE" },
|
|
3914
|
+
{ header: "KIKIMORA" },
|
|
3915
|
+
{
|
|
3916
|
+
header: "ENTRIES",
|
|
3917
|
+
align: "right"
|
|
3582
3918
|
},
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3919
|
+
{
|
|
3920
|
+
header: "SIZE",
|
|
3921
|
+
align: "right"
|
|
3922
|
+
},
|
|
3923
|
+
{ header: "EXPIRES" },
|
|
3924
|
+
{ header: "ID" }
|
|
3925
|
+
], items.map((backup) => [
|
|
3926
|
+
backup.createdAt,
|
|
3927
|
+
backup.trigger,
|
|
3928
|
+
backup.nodeName ?? backup.nodeId,
|
|
3929
|
+
backup.kikimoraVersion,
|
|
3930
|
+
String(backup.entries),
|
|
3931
|
+
bytesOf(backup.sizeBytes),
|
|
3932
|
+
backup.expiresAt,
|
|
3933
|
+
backup.id
|
|
3934
|
+
])), keptFor(retentionDays)].join("\n");
|
|
3935
|
+
const restoreRequestOf = ({ backup, name, node, slug }) => {
|
|
3936
|
+
if (backup === void 0 || name === void 0 || node === void 0 || node.trim() === "") {
|
|
3937
|
+
const missing = [
|
|
3938
|
+
...backup === void 0 ? ["--backup <id>"] : [],
|
|
3939
|
+
...name === void 0 ? ["--name <name>"] : [],
|
|
3940
|
+
...node === void 0 || node.trim() === "" ? ["--node <node>"] : []
|
|
3941
|
+
];
|
|
3942
|
+
throw new UsageError(`agents restore needs ${listOf(missing)}.`, backupsListHint);
|
|
3588
3943
|
}
|
|
3589
|
-
});
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3944
|
+
if (!isId(backup)) throw new UsageError(`--backup ${backup} is not the id of a backup.`, backupsListHint);
|
|
3945
|
+
const named = agentNameSchema.safeParse(name);
|
|
3946
|
+
if (!named.success) throw new UsageError(`--name needs from 1 to ${String(80)} characters.`);
|
|
3947
|
+
if (slug !== void 0 && !slugSchema.safeParse(slug).success) throw new UsageError(`--slug ${slug} is not a slug.`, "A slug holds lowercase letters, digits and hyphens, at most 40 of them.");
|
|
3948
|
+
return {
|
|
3949
|
+
backupId: backup,
|
|
3950
|
+
name: named.data,
|
|
3951
|
+
node: node.trim(),
|
|
3952
|
+
slug
|
|
3953
|
+
};
|
|
3597
3954
|
};
|
|
3598
|
-
const
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3955
|
+
const backupsCommand = (adaptersOf) => defineGroup({
|
|
3956
|
+
name: "backups",
|
|
3957
|
+
summary: "The backups of the memory of an agent.",
|
|
3958
|
+
usage: "agents backups <command>",
|
|
3959
|
+
subcommands: [defineCommand({
|
|
3960
|
+
name: "list",
|
|
3961
|
+
summary: "List the backups of an agent, the newest first.",
|
|
3962
|
+
usage: "agents backups list <agent> [--project <name>]",
|
|
3963
|
+
optionsUsage: agentUsage,
|
|
3964
|
+
options: agentOption,
|
|
3965
|
+
positionals: {
|
|
3966
|
+
min: 1,
|
|
3967
|
+
max: 1
|
|
3968
|
+
},
|
|
3969
|
+
run: async (invocation) => {
|
|
3970
|
+
const run = await runOf(adaptersOf, invocation);
|
|
3971
|
+
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
3972
|
+
return printed(run, await backupsOf(run.api, agentId), backupsTable);
|
|
3973
|
+
}
|
|
3974
|
+
}), defineCommand({
|
|
3975
|
+
name: "create",
|
|
3976
|
+
summary: "Ask the node of an agent for a backup now.",
|
|
3977
|
+
usage: "agents backups create <agent> [--project <name>]",
|
|
3978
|
+
optionsUsage: agentUsage,
|
|
3979
|
+
options: agentOption,
|
|
3980
|
+
positionals: {
|
|
3981
|
+
min: 1,
|
|
3982
|
+
max: 1
|
|
3983
|
+
},
|
|
3984
|
+
run: async (invocation) => {
|
|
3985
|
+
const run = await runOf(adaptersOf, invocation);
|
|
3986
|
+
const reference = invocation.positionals[0] ?? "";
|
|
3987
|
+
const agentId = await agentFrom(run, reference, invocation.values.project);
|
|
3988
|
+
return printed(run, await requestedBackup(run.api, agentId), () => `Backup of ${reference} requested. kcloud agents backups list ${reference} shows it once the node has sent it.`);
|
|
3989
|
+
}
|
|
3990
|
+
})]
|
|
3991
|
+
});
|
|
3992
|
+
const restoreCommand = (adaptersOf) => defineCommand({
|
|
3993
|
+
name: "restore",
|
|
3994
|
+
summary: "Create a new agent from a backup of an agent.",
|
|
3995
|
+
usage: "agents restore <agent> --backup <id> --name <name> --node <node> [--slug <slug>] [--project <name>]",
|
|
3996
|
+
optionsUsage: [
|
|
3997
|
+
...agentUsage,
|
|
3998
|
+
["--backup <id>", "the backup, by its id from kcloud agents backups list"],
|
|
3999
|
+
["--name <name>", "the name of the new agent"],
|
|
4000
|
+
["--node <node>", "the node the new agent runs on, by name or id"],
|
|
4001
|
+
["--slug <slug>", "the slug of the new agent (default: one made from its name)"],
|
|
4002
|
+
["", "the new agent joins the project of the agent with its configuration and the memory of the backup"]
|
|
4003
|
+
],
|
|
4004
|
+
options: {
|
|
4005
|
+
...agentOption,
|
|
4006
|
+
backup: { type: "string" },
|
|
4007
|
+
name: { type: "string" },
|
|
4008
|
+
node: { type: "string" },
|
|
4009
|
+
slug: { type: "string" }
|
|
4010
|
+
},
|
|
4011
|
+
positionals: {
|
|
4012
|
+
min: 1,
|
|
4013
|
+
max: 1
|
|
4014
|
+
},
|
|
4015
|
+
run: async (invocation) => {
|
|
4016
|
+
const request = restoreRequestOf(invocation.values);
|
|
4017
|
+
const run = await runOf(adaptersOf, invocation);
|
|
4018
|
+
const reference = invocation.positionals[0] ?? "";
|
|
4019
|
+
const agentId = await agentFrom(run, reference, invocation.values.project);
|
|
4020
|
+
return printed(run, await restoredAgent(run.api, agentId, request), (restored) => [`Restored ${request.name} as ${restored.agentId} from backup ${request.backupId} of ${reference}.`, `kcloud agents start ${restored.agentId} starts it once it is provisioned.`].join("\n"));
|
|
3607
4021
|
}
|
|
4022
|
+
});
|
|
4023
|
+
//#endregion
|
|
4024
|
+
//#region src/commands/agents/check.ts
|
|
4025
|
+
const checkHints = {
|
|
4026
|
+
...agentHints,
|
|
4027
|
+
"runtime.control_unreachable": "The worker is not answering; kcloud agents show <agent> tells whether it runs.",
|
|
4028
|
+
"runtime.monitor_paused": "kcloud agents resume <agent> lets the agent work again.",
|
|
4029
|
+
"runtime.monitor_draining": "The agent stops after its current session; start it again to run a cycle.",
|
|
4030
|
+
"runtime.monitor_limited": "The Claude usage limit holds; the monitor runs its next cycle once it resets.",
|
|
4031
|
+
"runtime.monitor_check_unsupported": "Move the agent to Kikimora 0.10.0 or newer with kcloud agents set, then kcloud agents update."
|
|
3608
4032
|
};
|
|
3609
|
-
const
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
4033
|
+
const checkCommand = (adaptersOf) => defineCommand({
|
|
4034
|
+
name: "check",
|
|
4035
|
+
summary: "Run a monitor cycle now instead of waiting for the interval.",
|
|
4036
|
+
usage: "agents check <agent> [--project <name>]",
|
|
4037
|
+
optionsUsage: agentUsage,
|
|
4038
|
+
options: agentOption,
|
|
4039
|
+
positionals: {
|
|
4040
|
+
min: 1,
|
|
4041
|
+
max: 1
|
|
4042
|
+
},
|
|
4043
|
+
run: async (invocation) => {
|
|
4044
|
+
const run = await runOf(adaptersOf, invocation);
|
|
4045
|
+
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
4046
|
+
const check = dataOf("check the monitor", await run.api.POST("/agents/{id}/monitor/check", { params: { path: { id: agentId } } }), checkHints);
|
|
4047
|
+
return printed(run, check, (answer) => answer.state === "running" ? `Monitor cycle ${String(answer.cycle)} is already running.` : "Monitor cycle requested; it starts now, also outside the active hours.");
|
|
3614
4048
|
}
|
|
3615
|
-
};
|
|
3616
|
-
const validated = (schema, document, what) => {
|
|
3617
|
-
const parsed = schema.safeParse(document);
|
|
3618
|
-
if (!parsed.success) throw new CliError(`That is not ${what}.`, z.prettifyError(parsed.error));
|
|
3619
|
-
};
|
|
4049
|
+
});
|
|
3620
4050
|
//#endregion
|
|
3621
4051
|
//#region src/render/confirm.ts
|
|
3622
4052
|
const confirmed = async ({ prompt, settings, question }) => {
|
|
@@ -3627,6 +4057,86 @@ const confirmed = async ({ prompt, settings, question }) => {
|
|
|
3627
4057
|
})) throw new CancelledError("Nothing was changed.");
|
|
3628
4058
|
};
|
|
3629
4059
|
//#endregion
|
|
4060
|
+
//#region src/commands/nodes/pools.ts
|
|
4061
|
+
const poolLimitReasons = {
|
|
4062
|
+
release: "its Kikimora release predates pool limits",
|
|
4063
|
+
restart: "it started before the limits applied; kcloud agents restart applies them",
|
|
4064
|
+
node: "its node holds no pool to a limit"
|
|
4065
|
+
};
|
|
4066
|
+
const poolLimitText = (limit) => {
|
|
4067
|
+
if (limit === null) return "-";
|
|
4068
|
+
if (limit.enforced) return "enforced";
|
|
4069
|
+
return limit.reason === null ? "none" : `none: ${poolLimitReasons[limit.reason]}`;
|
|
4070
|
+
};
|
|
4071
|
+
const sessionFailures = {
|
|
4072
|
+
poolMemoryLimit: "killed by the pool memory limit",
|
|
4073
|
+
launcherRefused: "refused by the session starter"
|
|
4074
|
+
};
|
|
4075
|
+
const sessionFailureText = (reason) => reason === null ? "-" : sessionFailures[reason] ?? reason;
|
|
4076
|
+
const unsupportedReasons = { node_outdated: "the node runs a version that reports no pools" };
|
|
4077
|
+
const enforcementText = ({ state, reason }) => {
|
|
4078
|
+
switch (state) {
|
|
4079
|
+
case "enforced": return "The node holds each pool to the CPU and memory of its split.";
|
|
4080
|
+
case "unsplit": return "The node has no split, so no pool has a hard limit on CPU or memory.";
|
|
4081
|
+
case "unsupported": return `The node holds no pool to a limit: ${reason === null ? "it cannot" : unsupportedReasons[reason] ?? reason}.`;
|
|
4082
|
+
}
|
|
4083
|
+
};
|
|
4084
|
+
const memoryOf = (pool) => {
|
|
4085
|
+
if (pool.usage === null) return "-";
|
|
4086
|
+
const { memoryBytes, memoryMaxBytes } = pool.usage;
|
|
4087
|
+
return memoryMaxBytes === null ? bytesOf(memoryBytes) : `${bytesOf(memoryBytes)} of ${bytesOf(memoryMaxBytes)}`;
|
|
4088
|
+
};
|
|
4089
|
+
const roles = ["monitor", "executor"];
|
|
4090
|
+
const queueOf = (pools) => {
|
|
4091
|
+
const waiting = roles.flatMap((role) => pools[role].waiting.map((entry) => [
|
|
4092
|
+
role,
|
|
4093
|
+
String(entry.position),
|
|
4094
|
+
entry.projectName ?? "-",
|
|
4095
|
+
entry.agentName ?? entry.agentId,
|
|
4096
|
+
entry.since,
|
|
4097
|
+
entry.taskId ?? "-"
|
|
4098
|
+
]));
|
|
4099
|
+
return waiting.length === 0 ? "No session waits for a slot." : renderTable([
|
|
4100
|
+
{ header: "WAITS FOR" },
|
|
4101
|
+
{
|
|
4102
|
+
header: "PLACE",
|
|
4103
|
+
align: "right"
|
|
4104
|
+
},
|
|
4105
|
+
{ header: "PROJECT" },
|
|
4106
|
+
{ header: "AGENT" },
|
|
4107
|
+
{ header: "SINCE" },
|
|
4108
|
+
{ header: "TASK" }
|
|
4109
|
+
], waiting);
|
|
4110
|
+
};
|
|
4111
|
+
const poolsText = (pools) => [
|
|
4112
|
+
enforcementText(pools.enforcement),
|
|
4113
|
+
renderTable([
|
|
4114
|
+
{ header: "POOL" },
|
|
4115
|
+
{ header: "SLOTS IN USE" },
|
|
4116
|
+
{
|
|
4117
|
+
header: "CPU IN USE",
|
|
4118
|
+
align: "right"
|
|
4119
|
+
},
|
|
4120
|
+
{ header: "MEMORY IN USE" },
|
|
4121
|
+
{
|
|
4122
|
+
header: "MEMORY KILLS",
|
|
4123
|
+
align: "right"
|
|
4124
|
+
}
|
|
4125
|
+
], roles.map((role) => {
|
|
4126
|
+
const pool = pools[role];
|
|
4127
|
+
const cpus = pool.usage?.cpus ?? null;
|
|
4128
|
+
return [
|
|
4129
|
+
role,
|
|
4130
|
+
`${pool.inUse} of ${pool.slots}`,
|
|
4131
|
+
cpus === null ? "-" : cpus.toFixed(2),
|
|
4132
|
+
memoryOf(pool),
|
|
4133
|
+
pool.usage === null ? "-" : String(pool.usage.memoryKills)
|
|
4134
|
+
];
|
|
4135
|
+
})),
|
|
4136
|
+
"",
|
|
4137
|
+
queueOf(pools)
|
|
4138
|
+
].join("\n");
|
|
4139
|
+
//#endregion
|
|
3630
4140
|
//#region src/commands/agents/plan.ts
|
|
3631
4141
|
const agentPlanOf = (agent) => ({
|
|
3632
4142
|
name: agent.name,
|
|
@@ -3634,6 +4144,7 @@ const agentPlanOf = (agent) => ({
|
|
|
3634
4144
|
description: agent.description,
|
|
3635
4145
|
kikimoraVersion: agent.kikimoraVersion,
|
|
3636
4146
|
...agent.node === null ? {} : { nodeId: agent.node.id },
|
|
4147
|
+
requiredCapabilities: agent.requiredCapabilities,
|
|
3637
4148
|
claudeCredentialId: agent.claudeCredential.id,
|
|
3638
4149
|
githubCredentialId: agent.githubCredential.id,
|
|
3639
4150
|
repositories: agent.repositories.map(({ repositoryId, directory }) => ({
|
|
@@ -3757,328 +4268,88 @@ const configCommands = (adaptersOf) => [
|
|
|
3757
4268
|
run: async (invocation) => {
|
|
3758
4269
|
const run = await runOf(adaptersOf, invocation);
|
|
3759
4270
|
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
3760
|
-
const file = invocation.values.file;
|
|
3761
|
-
if (file === void 0) throw new UsageError("agents set needs --file <path>.", "The file holds the sections to change; kcloud agents show <agent> --json shows the current ones.");
|
|
3762
|
-
const agent = await changedAgent(run.api, agentId, await documentOf(run, file));
|
|
3763
|
-
return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs kcloud agents update to take the change.` : `Changed ${changed.name}.`);
|
|
3764
|
-
}
|
|
3765
|
-
}),
|
|
3766
|
-
defineCommand({
|
|
3767
|
-
name: "delete",
|
|
3768
|
-
summary: "Delete an agent and its container.",
|
|
3769
|
-
usage: "agents delete <agent> [--project <name>] [--now]",
|
|
3770
|
-
optionsUsage: [...agentUsage, ["--now", "delete it at once instead of after its current session"]],
|
|
3771
|
-
options: {
|
|
3772
|
-
...agentOption,
|
|
3773
|
-
now: {
|
|
3774
|
-
type: "boolean",
|
|
3775
|
-
default: false
|
|
3776
|
-
}
|
|
3777
|
-
},
|
|
3778
|
-
positionals: {
|
|
3779
|
-
min: 1,
|
|
3780
|
-
max: 1
|
|
3781
|
-
},
|
|
3782
|
-
run: async (invocation) => {
|
|
3783
|
-
const run = await runOf(adaptersOf, invocation);
|
|
3784
|
-
const reference = invocation.positionals[0] ?? "";
|
|
3785
|
-
const agentId = await agentFrom(run, reference, invocation.values.project);
|
|
3786
|
-
await confirmed({
|
|
3787
|
-
prompt: run.prompt,
|
|
3788
|
-
settings: run.settings,
|
|
3789
|
-
question: `Delete ${reference}? Its workspace and its history go with it.`
|
|
3790
|
-
});
|
|
3791
|
-
doneWith("delete the agent", await run.api.DELETE("/agents/{id}", { params: {
|
|
3792
|
-
path: { id: agentId },
|
|
3793
|
-
...invocation.values.now ? { query: { force: "true" } } : {}
|
|
3794
|
-
} }), agentHints);
|
|
3795
|
-
return said(run, invocation.values.now ? `Deleting ${reference} now.` : `${reference} is deleted after its current session.`);
|
|
3796
|
-
}
|
|
3797
|
-
})
|
|
3798
|
-
];
|
|
3799
|
-
const slotOf = (state) => {
|
|
3800
|
-
if (state === null) return "-";
|
|
3801
|
-
return state.state === "holding" ? "holding a slot" : `place ${String(state.position)} in the queue since ${state.since}`;
|
|
3802
|
-
};
|
|
3803
|
-
const admissionRowsOf = (agent) => {
|
|
3804
|
-
const { admission } = agent.runtime;
|
|
3805
|
-
return admission === null ? [] : [["monitor slot", slotOf(admission.monitor)], ["executor slot", slotOf(admission.executor)]];
|
|
3806
|
-
};
|
|
3807
|
-
const detailsOf = (agent) => [
|
|
3808
|
-
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
3809
|
-
["name", agent.name],
|
|
3810
|
-
["slug", agent.slug],
|
|
3811
|
-
["state", agent.state],
|
|
3812
|
-
["wanted", agent.desiredState],
|
|
3813
|
-
["observed", agent.observedState],
|
|
3814
|
-
["release", agent.kikimoraVersion],
|
|
3815
|
-
["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
|
|
3816
|
-
["timezone", agent.timezone],
|
|
3817
|
-
["machine", `${agent.machine.cpus} vCPU, ${agent.machine.memoryMb} MB`],
|
|
3818
|
-
["docker", agent.machine.docker ? "yes" : "no"],
|
|
3819
|
-
["browser", agent.machine.browser ? "yes" : "no"],
|
|
3820
|
-
["container", agent.runtime.containerId ?? "none"],
|
|
3821
|
-
["health", agent.runtime.health],
|
|
3822
|
-
...admissionRowsOf(agent),
|
|
3823
|
-
["pending", agent.restartRequired ? agent.restartReasons.join(", ") : "-"],
|
|
3824
|
-
["actions", agent.availableActions.join(", ")],
|
|
3825
|
-
["version", String(agent.version)],
|
|
3826
|
-
["id", agent.id]
|
|
3827
|
-
]),
|
|
3828
|
-
"",
|
|
3829
|
-
agent.repositories.length === 0 ? "No repository is cloned into the workspace." : renderTable([
|
|
3830
|
-
{ header: "REPOSITORY" },
|
|
3831
|
-
{ header: "DIRECTORY" },
|
|
3832
|
-
{ header: "STATUS" }
|
|
3833
|
-
], agent.repositories.map((repository) => [
|
|
3834
|
-
`${repository.owner}/${repository.name}`,
|
|
3835
|
-
repository.directory,
|
|
3836
|
-
repository.status
|
|
3837
|
-
]))
|
|
3838
|
-
].join("\n");
|
|
3839
|
-
//#endregion
|
|
3840
|
-
//#region src/commands/nodes.ts
|
|
3841
|
-
const nodeHints = {
|
|
3842
|
-
...organizationHints,
|
|
3843
|
-
"node.not_found": "kcloud nodes list shows the nodes of the organization.",
|
|
3844
|
-
"node.name_taken": "Another node of the organization has that name.",
|
|
3845
|
-
"node.in_use": "Delete the agents placed on it first; kcloud nodes show <node> lists them.",
|
|
3846
|
-
"node.registration_used": "The node has joined and holds its own credential; it needs no token."
|
|
3847
|
-
};
|
|
3848
|
-
const nodesOf = async (api, organizationId) => dataOf("list the nodes", await api.GET("/organizations/{organizationId}/nodes", { params: { path: { organizationId } } }), nodeHints).items;
|
|
3849
|
-
const namesOf = (nodes) => nodes.map(({ name }) => name).join(", ");
|
|
3850
|
-
const nodeIn = (nodes, reference, organization) => {
|
|
3851
|
-
const found = nodes.find((node) => node.id === reference.toLowerCase() || node.name === reference);
|
|
3852
|
-
if (found === void 0) throw new CliError(`There is no node ${reference} in ${organization}.`, nodes.length === 0 ? "kcloud nodes create <name> creates one." : `kcloud nodes list shows ${namesOf(nodes)}.`);
|
|
3853
|
-
return found;
|
|
3854
|
-
};
|
|
3855
|
-
const resolveNode = async (scope, reference) => {
|
|
3856
|
-
const organization = await scope.organization();
|
|
3857
|
-
if (isId(reference)) return {
|
|
3858
|
-
organizationId: organization.id,
|
|
3859
|
-
nodeId: reference
|
|
3860
|
-
};
|
|
3861
|
-
const node = nodeIn(await nodesOf(scope.api, organization.id), reference, organization.slug);
|
|
3862
|
-
return {
|
|
3863
|
-
organizationId: organization.id,
|
|
3864
|
-
nodeId: node.id
|
|
3865
|
-
};
|
|
3866
|
-
};
|
|
3867
|
-
const nodeDetailOf = async (api, { organizationId, nodeId }) => dataOf("describe the node", await api.GET("/organizations/{organizationId}/nodes/{nodeId}", { params: { path: {
|
|
3868
|
-
organizationId,
|
|
3869
|
-
nodeId
|
|
3870
|
-
} } }), nodeHints);
|
|
3871
|
-
const createdNode = async (api, organizationId, name) => {
|
|
3872
|
-
const body = { name };
|
|
3873
|
-
validated(createNodeBodySchema, body, "a node");
|
|
3874
|
-
return dataOf("create the node", await api.POST("/organizations/{organizationId}/nodes", {
|
|
3875
|
-
params: { path: { organizationId } },
|
|
3876
|
-
body
|
|
3877
|
-
}), nodeHints);
|
|
3878
|
-
};
|
|
3879
|
-
const renewedRegistration = async (api, { organizationId, nodeId }) => dataOf("issue a registration token for the node", await api.POST("/organizations/{organizationId}/nodes/{nodeId}/registration", { params: { path: {
|
|
3880
|
-
organizationId,
|
|
3881
|
-
nodeId
|
|
3882
|
-
} } }), nodeHints);
|
|
3883
|
-
const deleteNode = async (api, { organizationId, nodeId }) => {
|
|
3884
|
-
doneWith("delete the node", await api.DELETE("/organizations/{organizationId}/nodes/{nodeId}", { params: { path: {
|
|
3885
|
-
organizationId,
|
|
3886
|
-
nodeId
|
|
3887
|
-
} } }), nodeHints);
|
|
3888
|
-
};
|
|
3889
|
-
const gibibytes = (bytes) => `${(bytes / 1024 ** 3).toFixed(1)} GiB`;
|
|
3890
|
-
const hostOf = (node) => node.host === null ? "-" : `${node.host.cpus} CPU, ${gibibytes(node.host.memoryBytes)} of memory`;
|
|
3891
|
-
const capabilitiesOf = (node) => {
|
|
3892
|
-
if (node.capabilities === null) return "-";
|
|
3893
|
-
const { sandbox, uidIsolation } = node.capabilities;
|
|
3894
|
-
return `sandbox ${sandbox ? "yes" : "no"}, uid isolation ${uidIsolation ? "yes" : "no"}`;
|
|
3895
|
-
};
|
|
3896
|
-
const registrationOf = (node) => node.registrationExpiresAt === null ? "-" : `a token waits until ${node.registrationExpiresAt}`;
|
|
3897
|
-
const storageOf = ({ state, freeBytes, totalBytes, path }) => `${state}, ${gibibytes(freeBytes)} free of ${gibibytes(totalBytes)} at ${path}`;
|
|
3898
|
-
const runtimeRowsOf = (runtime) => [
|
|
3899
|
-
["reported", runtime.reportedAt],
|
|
3900
|
-
["docker", runtime.docker.lastError === null ? runtime.docker.state : `${runtime.docker.state}: ${runtime.docker.lastError}`],
|
|
3901
|
-
["data storage", storageOf(runtime.storage.data)],
|
|
3902
|
-
["engine storage", storageOf(runtime.storage.engine)],
|
|
3903
|
-
["last reconcile", runtime.reconcile.lastTickError === null ? runtime.reconcile.lastTickAt ?? "never" : `${runtime.reconcile.lastTickAt ?? "never"}: ${runtime.reconcile.lastTickError}`],
|
|
3904
|
-
["agents", `${runtime.reconcile.agentsActive} active, ${runtime.reconcile.agentsQueued} queued`],
|
|
3905
|
-
["monitor slots", `${runtime.sessions.monitor.inUse} of ${runtime.sessions.monitor.effective} in use`],
|
|
3906
|
-
["executor slots", `${runtime.sessions.executor.inUse} of ${runtime.sessions.executor.effective} in use`],
|
|
3907
|
-
["log readers", String(runtime.ingest.readers)]
|
|
3908
|
-
];
|
|
3909
|
-
const nodeDetailsText = (node) => [
|
|
3910
|
-
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
3911
|
-
["name", node.name],
|
|
3912
|
-
["state", node.state],
|
|
3913
|
-
["provider", node.provider],
|
|
3914
|
-
["engine", node.engine ?? "-"],
|
|
3915
|
-
["version", node.version ?? "-"],
|
|
3916
|
-
["host", hostOf(node)],
|
|
3917
|
-
["server", node.host?.serverVersion ?? "-"],
|
|
3918
|
-
["data root", node.host?.dataRoot ?? "-"],
|
|
3919
|
-
["capabilities", capabilitiesOf(node)],
|
|
3920
|
-
["registration", registrationOf(node)],
|
|
3921
|
-
["connected", node.connectedAt ?? "never"],
|
|
3922
|
-
["last seen", node.lastSeenAt ?? "never"],
|
|
3923
|
-
["created", node.createdAt],
|
|
3924
|
-
["id", node.id]
|
|
3925
|
-
]),
|
|
3926
|
-
"",
|
|
3927
|
-
node.agents.length === 0 ? "No agent is placed on the node." : renderTable([
|
|
3928
|
-
{ header: "PROJECT" },
|
|
3929
|
-
{ header: "AGENT" },
|
|
3930
|
-
{ header: "STATE" },
|
|
3931
|
-
{ header: "ID" }
|
|
3932
|
-
], node.agents.map((agent) => [
|
|
3933
|
-
agent.projectName,
|
|
3934
|
-
agent.slug,
|
|
3935
|
-
agent.state,
|
|
3936
|
-
agent.id
|
|
3937
|
-
])),
|
|
3938
|
-
"",
|
|
3939
|
-
node.runtime === null ? "The node has reported no runtime yet." : renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime))
|
|
3940
|
-
].join("\n");
|
|
3941
|
-
const startHintOf = (origin) => [
|
|
3942
|
-
"Start the node with it as docs/install.md of kikimora-cloud describes. On a Linux host, after read -rs token:",
|
|
3943
|
-
` printf '%s\\n' "$token" | /opt/kikimora-cloud/infra/node/install.sh --platform-url ${origin} --image ghcr.io/kikimoradev/kikimora-cloud/node:<version> --registration-token -`,
|
|
3944
|
-
"On a Windows PC:",
|
|
3945
|
-
` C:\\kikimora-cloud\\src\\infra\\windows\\install.ps1 -PlatformUrl ${origin} -RegistrationToken <token>`
|
|
3946
|
-
].join("\n");
|
|
3947
|
-
const printedRegistration = (run, created) => {
|
|
3948
|
-
if (run.settings.json) {
|
|
3949
|
-
run.context.terminal.json(created);
|
|
3950
|
-
return exitCodes.ok;
|
|
3951
|
-
}
|
|
3952
|
-
run.context.terminal.line(created.registration.token);
|
|
3953
|
-
run.context.terminal.error([`The registration token of ${created.node.name} is shown only here and works once, until ${created.registration.expiresAt}.`, startHintOf(run.origin)].join("\n"));
|
|
3954
|
-
return exitCodes.ok;
|
|
3955
|
-
};
|
|
3956
|
-
const nodeUsage = [["", "a node is named by its name or its id"]];
|
|
3957
|
-
const createNodesCommand = (adaptersOf) => defineGroup({
|
|
3958
|
-
name: "nodes",
|
|
3959
|
-
summary: "The nodes of the organization, the hosts its agents run on.",
|
|
3960
|
-
usage: "nodes <command>",
|
|
3961
|
-
subcommands: [
|
|
3962
|
-
defineCommand({
|
|
3963
|
-
name: "list",
|
|
3964
|
-
summary: "List the nodes of the organization.",
|
|
3965
|
-
usage: "nodes list",
|
|
3966
|
-
options: {},
|
|
3967
|
-
positionals: {
|
|
3968
|
-
min: 0,
|
|
3969
|
-
max: 0
|
|
3970
|
-
},
|
|
3971
|
-
run: async (invocation) => {
|
|
3972
|
-
const run = await runOf(adaptersOf, invocation);
|
|
3973
|
-
const organization = await run.organization();
|
|
3974
|
-
const items = await nodesOf(run.api, organization.id);
|
|
3975
|
-
return printed(run, items, (nodes) => nodes.length === 0 ? `${organization.slug} has no node yet; kcloud nodes create <name> creates one.` : renderTable([
|
|
3976
|
-
{ header: "NAME" },
|
|
3977
|
-
{ header: "STATE" },
|
|
3978
|
-
{ header: "ENGINE" },
|
|
3979
|
-
{ header: "VERSION" },
|
|
3980
|
-
{
|
|
3981
|
-
header: "AGENTS",
|
|
3982
|
-
align: "right"
|
|
3983
|
-
},
|
|
3984
|
-
{ header: "LAST SEEN" },
|
|
3985
|
-
{ header: "ID" }
|
|
3986
|
-
], nodes.map((node) => [
|
|
3987
|
-
node.name,
|
|
3988
|
-
node.state,
|
|
3989
|
-
node.engine ?? "-",
|
|
3990
|
-
node.version ?? "-",
|
|
3991
|
-
String(node.agentCount),
|
|
3992
|
-
node.lastSeenAt ?? "never",
|
|
3993
|
-
node.id
|
|
3994
|
-
])));
|
|
3995
|
-
}
|
|
3996
|
-
}),
|
|
3997
|
-
defineCommand({
|
|
3998
|
-
name: "show",
|
|
3999
|
-
summary: "Show a node with its host, the agents placed on it and its runtime.",
|
|
4000
|
-
usage: "nodes show <node>",
|
|
4001
|
-
optionsUsage: nodeUsage,
|
|
4002
|
-
options: {},
|
|
4003
|
-
positionals: {
|
|
4004
|
-
min: 1,
|
|
4005
|
-
max: 1
|
|
4006
|
-
},
|
|
4007
|
-
run: async (invocation) => {
|
|
4008
|
-
const run = await runOf(adaptersOf, invocation);
|
|
4009
|
-
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
4010
|
-
return printed(run, await nodeDetailOf(run.api, reference), nodeDetailsText);
|
|
4011
|
-
}
|
|
4012
|
-
}),
|
|
4013
|
-
defineCommand({
|
|
4014
|
-
name: "create",
|
|
4015
|
-
summary: "Create a node and print its one-time registration token.",
|
|
4016
|
-
usage: "nodes create <name>",
|
|
4017
|
-
optionsUsage: [["", "the name holds lowercase letters, digits and hyphens"], ["", "the token goes to stdout alone, how to start the node to stderr"]],
|
|
4018
|
-
options: {},
|
|
4019
|
-
positionals: {
|
|
4020
|
-
min: 1,
|
|
4021
|
-
max: 1
|
|
4022
|
-
},
|
|
4023
|
-
run: async (invocation) => {
|
|
4024
|
-
const run = await runOf(adaptersOf, invocation);
|
|
4025
|
-
const organization = await run.organization();
|
|
4026
|
-
return printedRegistration(run, await createdNode(run.api, organization.id, invocation.positionals[0] ?? ""));
|
|
4027
|
-
}
|
|
4028
|
-
}),
|
|
4029
|
-
defineCommand({
|
|
4030
|
-
name: "token",
|
|
4031
|
-
summary: "Issue a new registration token for a node that has not joined yet.",
|
|
4032
|
-
usage: "nodes token <node>",
|
|
4033
|
-
optionsUsage: [...nodeUsage, ["", "the token replaces the one issued before, which stops working"]],
|
|
4034
|
-
options: {},
|
|
4035
|
-
positionals: {
|
|
4036
|
-
min: 1,
|
|
4037
|
-
max: 1
|
|
4038
|
-
},
|
|
4039
|
-
run: async (invocation) => {
|
|
4040
|
-
const run = await runOf(adaptersOf, invocation);
|
|
4041
|
-
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
4042
|
-
return printedRegistration(run, await renewedRegistration(run.api, reference));
|
|
4043
|
-
}
|
|
4044
|
-
}),
|
|
4045
|
-
defineCommand({
|
|
4046
|
-
name: "delete",
|
|
4047
|
-
summary: "Delete a node that runs no agent.",
|
|
4048
|
-
usage: "nodes delete <node>",
|
|
4049
|
-
optionsUsage: nodeUsage,
|
|
4050
|
-
options: {},
|
|
4051
|
-
positionals: {
|
|
4052
|
-
min: 1,
|
|
4053
|
-
max: 1
|
|
4054
|
-
},
|
|
4055
|
-
run: async (invocation) => {
|
|
4056
|
-
const run = await runOf(adaptersOf, invocation);
|
|
4057
|
-
const given = invocation.positionals[0] ?? "";
|
|
4058
|
-
const reference = await resolveNode(run, given);
|
|
4059
|
-
await confirmed({
|
|
4060
|
-
prompt: run.prompt,
|
|
4061
|
-
settings: run.settings,
|
|
4062
|
-
question: `Delete the node ${given}? Its host can no longer reach the platform.`
|
|
4063
|
-
});
|
|
4064
|
-
await deleteNode(run.api, reference);
|
|
4065
|
-
return said(run, `Deleted the node ${given}.`);
|
|
4271
|
+
const file = invocation.values.file;
|
|
4272
|
+
if (file === void 0) throw new UsageError("agents set needs --file <path>.", "The file holds the sections to change; kcloud agents show <agent> --json shows the current ones.");
|
|
4273
|
+
const agent = await changedAgent(run.api, agentId, await documentOf(run, file));
|
|
4274
|
+
return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs kcloud agents update to take the change.` : `Changed ${changed.name}.`);
|
|
4275
|
+
}
|
|
4276
|
+
}),
|
|
4277
|
+
defineCommand({
|
|
4278
|
+
name: "delete",
|
|
4279
|
+
summary: "Delete an agent and its container.",
|
|
4280
|
+
usage: "agents delete <agent> [--project <name>] [--now]",
|
|
4281
|
+
optionsUsage: [...agentUsage, ["--now", "delete it at once instead of after its current session"]],
|
|
4282
|
+
options: {
|
|
4283
|
+
...agentOption,
|
|
4284
|
+
now: {
|
|
4285
|
+
type: "boolean",
|
|
4286
|
+
default: false
|
|
4066
4287
|
}
|
|
4067
|
-
}
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
const
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4288
|
+
},
|
|
4289
|
+
positionals: {
|
|
4290
|
+
min: 1,
|
|
4291
|
+
max: 1
|
|
4292
|
+
},
|
|
4293
|
+
run: async (invocation) => {
|
|
4294
|
+
const run = await runOf(adaptersOf, invocation);
|
|
4295
|
+
const reference = invocation.positionals[0] ?? "";
|
|
4296
|
+
const agentId = await agentFrom(run, reference, invocation.values.project);
|
|
4297
|
+
await confirmed({
|
|
4298
|
+
prompt: run.prompt,
|
|
4299
|
+
settings: run.settings,
|
|
4300
|
+
question: `Delete ${reference}? Its workspace and its history go with it.`
|
|
4301
|
+
});
|
|
4302
|
+
doneWith("delete the agent", await run.api.DELETE("/agents/{id}", { params: {
|
|
4303
|
+
path: { id: agentId },
|
|
4304
|
+
...invocation.values.now ? { query: { force: "true" } } : {}
|
|
4305
|
+
} }), agentHints);
|
|
4306
|
+
return said(run, invocation.values.now ? `Deleting ${reference} now.` : `${reference} is deleted after its current session.`);
|
|
4307
|
+
}
|
|
4308
|
+
})
|
|
4309
|
+
];
|
|
4310
|
+
const slotOf = (state) => {
|
|
4311
|
+
if (state === null) return "-";
|
|
4312
|
+
return state.state === "holding" ? "holding a slot" : `place ${String(state.position)} in the queue since ${state.since}`;
|
|
4080
4313
|
};
|
|
4081
|
-
const
|
|
4314
|
+
const admissionRowsOf = (agent) => {
|
|
4315
|
+
const { admission } = agent.runtime;
|
|
4316
|
+
return admission === null ? [] : [["monitor slot", slotOf(admission.monitor)], ["executor slot", slotOf(admission.executor)]];
|
|
4317
|
+
};
|
|
4318
|
+
const poolLimitRowsOf = ({ runtime }) => runtime.poolLimit === null ? [] : [["pool limit", poolLimitText(runtime.poolLimit)]];
|
|
4319
|
+
const detailsOf = (agent) => [
|
|
4320
|
+
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
4321
|
+
["name", agent.name],
|
|
4322
|
+
["slug", agent.slug],
|
|
4323
|
+
["state", agent.state],
|
|
4324
|
+
["wanted", agent.desiredState],
|
|
4325
|
+
["observed", agent.observedState],
|
|
4326
|
+
["release", agent.kikimoraVersion],
|
|
4327
|
+
["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
|
|
4328
|
+
["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
|
|
4329
|
+
["timezone", agent.timezone],
|
|
4330
|
+
["machine", `${agent.machine.cpus} vCPU, ${agent.machine.memoryMb} MB`],
|
|
4331
|
+
["docker", agent.machine.docker ? "yes" : "no"],
|
|
4332
|
+
["browser", agent.machine.browser ? "yes" : "no"],
|
|
4333
|
+
["container", agent.runtime.containerId ?? "none"],
|
|
4334
|
+
["health", agent.runtime.health],
|
|
4335
|
+
...admissionRowsOf(agent),
|
|
4336
|
+
...poolLimitRowsOf(agent),
|
|
4337
|
+
["pending", agent.restartRequired ? agent.restartReasons.join(", ") : "-"],
|
|
4338
|
+
["actions", agent.availableActions.join(", ")],
|
|
4339
|
+
["version", String(agent.version)],
|
|
4340
|
+
["id", agent.id]
|
|
4341
|
+
]),
|
|
4342
|
+
"",
|
|
4343
|
+
agent.repositories.length === 0 ? "No repository is cloned into the workspace." : renderTable([
|
|
4344
|
+
{ header: "REPOSITORY" },
|
|
4345
|
+
{ header: "DIRECTORY" },
|
|
4346
|
+
{ header: "STATUS" }
|
|
4347
|
+
], agent.repositories.map((repository) => [
|
|
4348
|
+
`${repository.owner}/${repository.name}`,
|
|
4349
|
+
repository.directory,
|
|
4350
|
+
repository.status
|
|
4351
|
+
]))
|
|
4352
|
+
].join("\n");
|
|
4082
4353
|
//#endregion
|
|
4083
4354
|
//#region src/commands/agents/placement.ts
|
|
4084
4355
|
const placedNodeId = async (run, { projectId, given, ask }) => {
|
|
@@ -4204,8 +4475,16 @@ const promptText = async (run, file, question) => {
|
|
|
4204
4475
|
validate: (value) => value.trim() === "" ? "cannot be empty" : void 0
|
|
4205
4476
|
});
|
|
4206
4477
|
};
|
|
4478
|
+
const isCapability = (name) => nodeCapabilityNames.some((capability) => capability === name);
|
|
4479
|
+
const requiredCapabilitiesOf = (given) => {
|
|
4480
|
+
const names = given.flatMap((value) => value.split(",")).map((name) => name.trim()).filter((name) => name !== "");
|
|
4481
|
+
const unknown = names.filter((name) => !isCapability(name));
|
|
4482
|
+
if (unknown.length > 0) throw new UsageError(`--require ${unknown.join(", ")} ${unknown.length === 1 ? "is not a capability" : "are not capabilities"} of a node.`, `The capabilities are ${nodeCapabilityNames.join(", ")}.`);
|
|
4483
|
+
return [...new Set(names.filter(isCapability))];
|
|
4484
|
+
};
|
|
4485
|
+
const requirementOf = (required) => required === void 0 ? {} : { requiredCapabilities: [...required] };
|
|
4207
4486
|
const localTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
4208
|
-
const askedPlan = async (run, projectId, values) => {
|
|
4487
|
+
const askedPlan = async (run, projectId, values, required) => {
|
|
4209
4488
|
const nodeId = await placedNodeId(run, {
|
|
4210
4489
|
projectId,
|
|
4211
4490
|
given: values.node,
|
|
@@ -4229,6 +4508,7 @@ const askedPlan = async (run, projectId, values) => {
|
|
|
4229
4508
|
description: "",
|
|
4230
4509
|
kikimoraVersion: release,
|
|
4231
4510
|
nodeId,
|
|
4511
|
+
...requirementOf(required),
|
|
4232
4512
|
claudeCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => claudeKinds.has(kind), "The Claude credential it works with"),
|
|
4233
4513
|
githubCredentialId: await chosenCredential(run.prompt, catalogue, (kind) => kind === "github_pat", "The GitHub credential it clones with"),
|
|
4234
4514
|
repositories: [...await chosenRepositories(run.prompt, catalogue)],
|
|
@@ -4248,7 +4528,7 @@ const askedPlan = async (run, projectId, values) => {
|
|
|
4248
4528
|
};
|
|
4249
4529
|
};
|
|
4250
4530
|
const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
|
|
4251
|
-
const fileOf = async (run, projectId, { file, node }) => {
|
|
4531
|
+
const fileOf = async (run, projectId, { file, node }, required) => {
|
|
4252
4532
|
const document = await documentOf(run, file);
|
|
4253
4533
|
const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
|
|
4254
4534
|
...document,
|
|
@@ -4258,16 +4538,21 @@ const fileOf = async (run, projectId, { file, node }) => {
|
|
|
4258
4538
|
ask: false
|
|
4259
4539
|
})
|
|
4260
4540
|
};
|
|
4261
|
-
|
|
4262
|
-
|
|
4541
|
+
const plan = isObject(placed) ? {
|
|
4542
|
+
...placed,
|
|
4543
|
+
...requirementOf(required)
|
|
4544
|
+
} : placed;
|
|
4545
|
+
validated(createAgentBodySchema, plan, "an agent");
|
|
4546
|
+
return plan;
|
|
4263
4547
|
};
|
|
4264
4548
|
const createCommand = (adaptersOf) => defineCommand({
|
|
4265
4549
|
name: "create",
|
|
4266
4550
|
summary: "Create an agent from a file, from flags, or by asking step by step.",
|
|
4267
|
-
usage: "agents create --project <name> [--node <node>] [--file <path>] [--name <name>]",
|
|
4551
|
+
usage: "agents create --project <name> [--node <node>] [--require <capability,...>] [--file <path>] [--name <name>]",
|
|
4268
4552
|
optionsUsage: [
|
|
4269
4553
|
["--project <name>", "the project that owns the agent"],
|
|
4270
4554
|
["--node <node>", "the node it runs on, by name or id; needed when the organization has several"],
|
|
4555
|
+
["--require <capability,...>", `what its node has to offer: ${nodeCapabilityNames.join(", ")}`],
|
|
4271
4556
|
["--file <path>", "the whole agent as JSON, or - to read stdin"],
|
|
4272
4557
|
["--name <name>", "the name of the agent, instead of asking"],
|
|
4273
4558
|
["--release <x.y.z>", "the Kikimora release it runs, instead of asking"],
|
|
@@ -4277,12 +4562,16 @@ const createCommand = (adaptersOf) => defineCommand({
|
|
|
4277
4562
|
["--docker", "give the agent a Docker sidecar, when the project allows it"],
|
|
4278
4563
|
["--browser", "give the agent a browser"],
|
|
4279
4564
|
["", "kcloud agents show <agent> --as-file prints what --file takes"],
|
|
4280
|
-
["", "--node
|
|
4565
|
+
["", "--node and --require replace the nodeId and requiredCapabilities of the file"]
|
|
4281
4566
|
],
|
|
4282
4567
|
options: {
|
|
4283
4568
|
...fileOptions,
|
|
4284
4569
|
project: { type: "string" },
|
|
4285
4570
|
node: { type: "string" },
|
|
4571
|
+
require: {
|
|
4572
|
+
type: "string",
|
|
4573
|
+
multiple: true
|
|
4574
|
+
},
|
|
4286
4575
|
name: { type: "string" },
|
|
4287
4576
|
release: { type: "string" },
|
|
4288
4577
|
"monitor-prompt-file": { type: "string" },
|
|
@@ -4303,13 +4592,14 @@ const createCommand = (adaptersOf) => defineCommand({
|
|
|
4303
4592
|
},
|
|
4304
4593
|
run: async (invocation) => {
|
|
4305
4594
|
const { values } = invocation;
|
|
4595
|
+
const required = values.require === void 0 ? void 0 : requiredCapabilitiesOf(values.require);
|
|
4306
4596
|
const run = await runOf(adaptersOf, invocation);
|
|
4307
4597
|
if (values.project === void 0) throw new UsageError("agents create needs --project <name>.", "kcloud projects list shows the projects you are a member of.");
|
|
4308
4598
|
const projectId = await resolveProjectId(run, values.project);
|
|
4309
|
-
const body = values.file === void 0 ? await askedPlan(run, projectId, values) : await fileOf(run, projectId, {
|
|
4599
|
+
const body = values.file === void 0 ? await askedPlan(run, projectId, values, required) : await fileOf(run, projectId, {
|
|
4310
4600
|
file: values.file,
|
|
4311
4601
|
node: values.node
|
|
4312
|
-
});
|
|
4602
|
+
}, required);
|
|
4313
4603
|
const agent = dataOf("create the agent", await run.api.POST("/projects/{id}/agents", {
|
|
4314
4604
|
params: { path: { id: projectId } },
|
|
4315
4605
|
body
|
|
@@ -4488,6 +4778,7 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
4488
4778
|
{ header: "ROLE" },
|
|
4489
4779
|
{ header: "MODEL" },
|
|
4490
4780
|
{ header: "OUTCOME" },
|
|
4781
|
+
{ header: "FAILURE" },
|
|
4491
4782
|
{
|
|
4492
4783
|
header: "COST",
|
|
4493
4784
|
align: "right"
|
|
@@ -4498,6 +4789,7 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
4498
4789
|
session.agentRole ?? "-",
|
|
4499
4790
|
session.model ?? "-",
|
|
4500
4791
|
session.ok === null ? "running" : session.ok ? "ok" : "failed",
|
|
4792
|
+
sessionFailureText(session.failureReason),
|
|
4501
4793
|
session.costUsd === null ? "-" : session.costUsd.toFixed(2),
|
|
4502
4794
|
session.sessionId
|
|
4503
4795
|
])));
|
|
@@ -5093,6 +5385,8 @@ const createAgentsCommand = (adaptersOf) => defineGroup({
|
|
|
5093
5385
|
...lifecycleCommands(adaptersOf),
|
|
5094
5386
|
checkCommand(adaptersOf),
|
|
5095
5387
|
tasksCommand(adaptersOf),
|
|
5388
|
+
backupsCommand(adaptersOf),
|
|
5389
|
+
restoreCommand(adaptersOf),
|
|
5096
5390
|
...observabilityCommands(adaptersOf),
|
|
5097
5391
|
...streamCommands(adaptersOf)
|
|
5098
5392
|
]
|
|
@@ -5629,7 +5923,7 @@ const agentTools = [
|
|
|
5629
5923
|
defineTool({
|
|
5630
5924
|
name: "agent_create",
|
|
5631
5925
|
title: "Create an agent",
|
|
5632
|
-
description: "Create an agent in a project. The node it runs on, the credentials, repositories and tools are named by the ids that nodes_list, credentials_list, repositories_list and tools_list return; agent_show with asPlan shows a complete example. The agent stays stopped until agent_action starts it.",
|
|
5926
|
+
description: "Create an agent in a project. The node it runs on, the credentials, repositories and tools are named by the ids that nodes_list, credentials_list, repositories_list and tools_list return; agent_show with asPlan shows a complete example. requiredCapabilities names what the node has to offer, from the offered list of nodes_list; the instance refuses a node that lacks one. The agent stays stopped until agent_action starts it.",
|
|
5633
5927
|
operations: ["createAgent"],
|
|
5634
5928
|
safety: "write",
|
|
5635
5929
|
input: z.object({
|
|
@@ -5680,48 +5974,475 @@ const agentTools = [
|
|
|
5680
5974
|
}
|
|
5681
5975
|
}),
|
|
5682
5976
|
defineTool({
|
|
5683
|
-
name: "agent_action",
|
|
5684
|
-
title: "Start, pause, resume, restart or update an agent",
|
|
5685
|
-
description: "start lets the agent work, pause holds it between sessions, resume lets a paused agent work again, restart restarts its container, update replaces the container to apply pending changes. The agent returns with the actions it takes next.",
|
|
5686
|
-
operations: ["runAgentAction"],
|
|
5687
|
-
safety: "write",
|
|
5688
|
-
input: z.object({
|
|
5689
|
-
...agentFields,
|
|
5690
|
-
action: z.enum([
|
|
5691
|
-
"start",
|
|
5692
|
-
"pause",
|
|
5693
|
-
"resume",
|
|
5694
|
-
"restart",
|
|
5695
|
-
"update"
|
|
5696
|
-
])
|
|
5697
|
-
}),
|
|
5698
|
-
run: async ({ action, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, false)
|
|
5977
|
+
name: "agent_action",
|
|
5978
|
+
title: "Start, pause, resume, restart or update an agent",
|
|
5979
|
+
description: "start lets the agent work, pause holds it between sessions, resume lets a paused agent work again, restart restarts its container, update replaces the container to apply pending changes. The agent returns with the actions it takes next.",
|
|
5980
|
+
operations: ["runAgentAction"],
|
|
5981
|
+
safety: "write",
|
|
5982
|
+
input: z.object({
|
|
5983
|
+
...agentFields,
|
|
5984
|
+
action: z.enum([
|
|
5985
|
+
"start",
|
|
5986
|
+
"pause",
|
|
5987
|
+
"resume",
|
|
5988
|
+
"restart",
|
|
5989
|
+
"update"
|
|
5990
|
+
])
|
|
5991
|
+
}),
|
|
5992
|
+
run: async ({ action, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, false)
|
|
5993
|
+
}),
|
|
5994
|
+
defineTool({
|
|
5995
|
+
name: "agent_check",
|
|
5996
|
+
title: "Run a monitor cycle now",
|
|
5997
|
+
description: "Ask the monitor of an agent to look for work now instead of waiting for its interval, also outside its active hours. state requested means a cycle starts at once; running names the cycle already in progress. A paused, stopping or usage-limited monitor refuses with an error.",
|
|
5998
|
+
operations: ["checkAgentMonitor"],
|
|
5999
|
+
safety: "write",
|
|
6000
|
+
input: z.object(agentFields),
|
|
6001
|
+
run: async (reference, run) => dataOf("check the monitor", await run.api.POST("/agents/{id}/monitor/check", { params: { path: { id: await agentIdOf(run, reference) } } }), checkHints)
|
|
6002
|
+
}),
|
|
6003
|
+
defineTool({
|
|
6004
|
+
name: "agent_interrupt",
|
|
6005
|
+
title: "Stop, reset or cancel an agent",
|
|
6006
|
+
description: "stop stops the agent after its current session, or at once with now; reset throws its workspace away and provisions it again; cancel cancels what it is doing.",
|
|
6007
|
+
operations: ["runAgentAction"],
|
|
6008
|
+
safety: "destructive",
|
|
6009
|
+
input: z.object({
|
|
6010
|
+
...agentFields,
|
|
6011
|
+
action: z.enum([
|
|
6012
|
+
"stop",
|
|
6013
|
+
"reset",
|
|
6014
|
+
"cancel"
|
|
6015
|
+
]),
|
|
6016
|
+
now: z.boolean().default(false).describe("For stop: stop the container at once.")
|
|
6017
|
+
}),
|
|
6018
|
+
run: async ({ action, now, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, action === "stop" && now)
|
|
6019
|
+
})
|
|
6020
|
+
];
|
|
6021
|
+
//#endregion
|
|
6022
|
+
//#region src/commands/nodes/split.ts
|
|
6023
|
+
const splitFields = [
|
|
6024
|
+
"reserve.cpus",
|
|
6025
|
+
"reserve.memoryMb",
|
|
6026
|
+
"monitor.cpus",
|
|
6027
|
+
"monitor.memoryMb",
|
|
6028
|
+
"monitor.slots",
|
|
6029
|
+
"executor.cpus",
|
|
6030
|
+
"executor.memoryMb",
|
|
6031
|
+
"executor.slots"
|
|
6032
|
+
];
|
|
6033
|
+
const valuesOf = (split) => ({
|
|
6034
|
+
"reserve.cpus": split.reserve.cpus,
|
|
6035
|
+
"reserve.memoryMb": split.reserve.memoryMb,
|
|
6036
|
+
"monitor.cpus": split.monitor.cpus,
|
|
6037
|
+
"monitor.memoryMb": split.monitor.memoryMb,
|
|
6038
|
+
"monitor.slots": split.monitor.slots,
|
|
6039
|
+
"executor.cpus": split.executor.cpus,
|
|
6040
|
+
"executor.memoryMb": split.executor.memoryMb,
|
|
6041
|
+
"executor.slots": split.executor.slots
|
|
6042
|
+
});
|
|
6043
|
+
const splitOf = (values) => ({
|
|
6044
|
+
reserve: {
|
|
6045
|
+
cpus: values["reserve.cpus"],
|
|
6046
|
+
memoryMb: values["reserve.memoryMb"]
|
|
6047
|
+
},
|
|
6048
|
+
monitor: {
|
|
6049
|
+
cpus: values["monitor.cpus"],
|
|
6050
|
+
memoryMb: values["monitor.memoryMb"],
|
|
6051
|
+
slots: values["monitor.slots"]
|
|
6052
|
+
},
|
|
6053
|
+
executor: {
|
|
6054
|
+
cpus: values["executor.cpus"],
|
|
6055
|
+
memoryMb: values["executor.memoryMb"],
|
|
6056
|
+
slots: values["executor.slots"]
|
|
6057
|
+
}
|
|
6058
|
+
});
|
|
6059
|
+
const isComplete = (values) => splitFields.every((field) => values[field] !== void 0);
|
|
6060
|
+
const changeOfParts = ({ reserve, monitor, executor }) => {
|
|
6061
|
+
const given = {
|
|
6062
|
+
"reserve.cpus": reserve?.cpus,
|
|
6063
|
+
"reserve.memoryMb": reserve?.memoryMb,
|
|
6064
|
+
"monitor.cpus": monitor?.cpus,
|
|
6065
|
+
"monitor.memoryMb": monitor?.memoryMb,
|
|
6066
|
+
"monitor.slots": monitor?.slots,
|
|
6067
|
+
"executor.cpus": executor?.cpus,
|
|
6068
|
+
"executor.memoryMb": executor?.memoryMb,
|
|
6069
|
+
"executor.slots": executor?.slots
|
|
6070
|
+
};
|
|
6071
|
+
return Object.fromEntries(Object.entries(given).filter((entry) => entry[1] !== void 0));
|
|
6072
|
+
};
|
|
6073
|
+
const cpusText = (cpus) => String(Math.round(cpus * 100) / 100);
|
|
6074
|
+
const hostText = (host) => `${cpusText(host.cpus)} CPU and ${hostMemoryMbOf(host)} MB`;
|
|
6075
|
+
const excessText = ({ cpus, memoryMb }) => [...cpus > 0 ? [`${cpusText(cpus)} CPU`] : [], ...memoryMb > 0 ? [`${memoryMb} MB`] : []].join(" and ");
|
|
6076
|
+
const fittingSplit = (node, change, nameOf) => {
|
|
6077
|
+
const merged = {
|
|
6078
|
+
...node.split === null ? {} : valuesOf(node.split),
|
|
6079
|
+
...change
|
|
6080
|
+
};
|
|
6081
|
+
if (!isComplete(merged)) {
|
|
6082
|
+
const missing = splitFields.filter((field) => merged[field] === void 0);
|
|
6083
|
+
throw new UsageError(`${node.name} has no split yet, so a split needs every part; ${missing.map(nameOf).join(", ")} ${missing.length === 1 ? "is" : "are"} missing.`, "Once the node has a split, a part left out keeps its value.");
|
|
6084
|
+
}
|
|
6085
|
+
const split = splitOf(merged);
|
|
6086
|
+
validated(nodeSplitSchema, split, "a split of a node");
|
|
6087
|
+
if (node.host === null) throw new CliError(`${node.name} has not reported its CPUs and memory yet, so it cannot be split.`, "The node reports them once its service runs; kcloud nodes show <node> shows its state.");
|
|
6088
|
+
if (!splitFits(split, node.host)) throw new CliError(`The split asks for ${excessText(splitOverflowOf(split, node.host))} more than ${node.name} has.`, `${node.name} has ${hostText(node.host)}; shrink a pool or the reserve.`);
|
|
6089
|
+
return split;
|
|
6090
|
+
};
|
|
6091
|
+
const splitViewOf = (node) => {
|
|
6092
|
+
const overflow = node.split === null || node.host === null ? null : splitOverflowOf(node.split, node.host);
|
|
6093
|
+
return {
|
|
6094
|
+
id: node.id,
|
|
6095
|
+
name: node.name,
|
|
6096
|
+
revision: node.revision,
|
|
6097
|
+
host: node.host === null ? null : {
|
|
6098
|
+
cpus: node.host.cpus,
|
|
6099
|
+
memoryMb: hostMemoryMbOf(node.host)
|
|
6100
|
+
},
|
|
6101
|
+
split: node.split,
|
|
6102
|
+
free: overflow === null ? null : {
|
|
6103
|
+
cpus: -overflow.cpus,
|
|
6104
|
+
memoryMb: -overflow.memoryMb
|
|
6105
|
+
}
|
|
6106
|
+
};
|
|
6107
|
+
};
|
|
6108
|
+
const resourceCells = ({ cpus, memoryMb }) => [cpusText(cpus), `${memoryMb} MB`];
|
|
6109
|
+
const oneSlotOf = ({ cpus, memoryMb, slots }) => `${cpusText(cpus / slots)} CPU, ${Math.floor(memoryMb / slots)} MB`;
|
|
6110
|
+
const hostLineOf = (view) => view.host === null ? `${view.name} has not reported its CPUs and memory yet.` : `The host has ${cpusText(view.host.cpus)} CPU and ${view.host.memoryMb} MB.`;
|
|
6111
|
+
const splitTableOf = (view, split) => {
|
|
6112
|
+
const poolRow = (role) => [
|
|
6113
|
+
role,
|
|
6114
|
+
...resourceCells(split[role]),
|
|
6115
|
+
String(split[role].slots),
|
|
6116
|
+
oneSlotOf(split[role])
|
|
6117
|
+
];
|
|
6118
|
+
const rows = [
|
|
6119
|
+
[
|
|
6120
|
+
"reserve",
|
|
6121
|
+
...resourceCells(split.reserve),
|
|
6122
|
+
"-",
|
|
6123
|
+
"-"
|
|
6124
|
+
],
|
|
6125
|
+
poolRow("monitor"),
|
|
6126
|
+
poolRow("executor"),
|
|
6127
|
+
...view.free === null ? [] : [[
|
|
6128
|
+
"free",
|
|
6129
|
+
...resourceCells(view.free),
|
|
6130
|
+
"-",
|
|
6131
|
+
"-"
|
|
6132
|
+
]],
|
|
6133
|
+
...view.host === null ? [] : [[
|
|
6134
|
+
"host",
|
|
6135
|
+
...resourceCells(view.host),
|
|
6136
|
+
"-",
|
|
6137
|
+
"-"
|
|
6138
|
+
]]
|
|
6139
|
+
];
|
|
6140
|
+
const over = view.free === null ? "" : excessText({
|
|
6141
|
+
cpus: -view.free.cpus,
|
|
6142
|
+
memoryMb: -view.free.memoryMb
|
|
6143
|
+
});
|
|
6144
|
+
return [
|
|
6145
|
+
renderTable([
|
|
6146
|
+
{ header: "PART" },
|
|
6147
|
+
{
|
|
6148
|
+
header: "CPU",
|
|
6149
|
+
align: "right"
|
|
6150
|
+
},
|
|
6151
|
+
{
|
|
6152
|
+
header: "MEMORY",
|
|
6153
|
+
align: "right"
|
|
6154
|
+
},
|
|
6155
|
+
{
|
|
6156
|
+
header: "SLOTS",
|
|
6157
|
+
align: "right"
|
|
6158
|
+
},
|
|
6159
|
+
{ header: "ONE SLOT" }
|
|
6160
|
+
], rows),
|
|
6161
|
+
...view.host === null ? ["", hostLineOf(view)] : [],
|
|
6162
|
+
...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`]
|
|
6163
|
+
].join("\n");
|
|
6164
|
+
};
|
|
6165
|
+
const splitText = (view) => view.split === null ? [
|
|
6166
|
+
`${view.name} has no split, so its slots are counted from its host and no pool has a hard limit on CPU or memory.`,
|
|
6167
|
+
hostLineOf(view),
|
|
6168
|
+
`kcloud nodes split set ${view.name} with every part splits it.`
|
|
6169
|
+
].join("\n") : splitTableOf(view, view.split);
|
|
6170
|
+
const splitSummaryOf = (split) => {
|
|
6171
|
+
if (split === null) return "none, slots counted from the host without hard limits";
|
|
6172
|
+
const { reserve, monitor, executor } = split;
|
|
6173
|
+
const pool = (role, { cpus, memoryMb, slots }) => `${role} ${cpusText(cpus)} CPU, ${memoryMb} MB, ${slots} slots`;
|
|
6174
|
+
return [
|
|
6175
|
+
`reserve ${cpusText(reserve.cpus)} CPU, ${reserve.memoryMb} MB`,
|
|
6176
|
+
pool("monitor", monitor),
|
|
6177
|
+
pool("executor", executor)
|
|
6178
|
+
].join("; ");
|
|
6179
|
+
};
|
|
6180
|
+
const cpusPattern = /^\d+(?:\.\d+)?$/u;
|
|
6181
|
+
const slotsPattern = /^\d+$/u;
|
|
6182
|
+
const memoryPattern = /^(\d+(?:\.\d+)?)\s*(m|mb|mib|g|gb|gib)?$/iu;
|
|
6183
|
+
const megabytesPerGigabyte = 1024;
|
|
6184
|
+
const memoryMbOf = (given) => {
|
|
6185
|
+
const match = memoryPattern.exec(given.trim());
|
|
6186
|
+
if (match === null) return null;
|
|
6187
|
+
const [, amount = "", unit = "m"] = match;
|
|
6188
|
+
const megabytes = Number(amount) * (unit.toLowerCase().startsWith("g") ? megabytesPerGigabyte : 1);
|
|
6189
|
+
return Number.isSafeInteger(megabytes) ? megabytes : null;
|
|
6190
|
+
};
|
|
6191
|
+
const readCpus = (flag, given) => {
|
|
6192
|
+
if (!cpusPattern.test(given)) throw new UsageError(`--${flag} ${given} is not a number of CPUs.`, "Give a decimal number, as in 2 or 0.5.");
|
|
6193
|
+
return Number(given);
|
|
6194
|
+
};
|
|
6195
|
+
const readMemory = (flag, given) => {
|
|
6196
|
+
const megabytes = memoryMbOf(given);
|
|
6197
|
+
if (megabytes === null) throw new UsageError(`--${flag} ${given} is not a whole number of megabytes.`, "Give megabytes, or a number with M or G, as in 2048, 2048M or 2G; G is 1024 MB.");
|
|
6198
|
+
return megabytes;
|
|
6199
|
+
};
|
|
6200
|
+
const readSlots = (flag, given) => {
|
|
6201
|
+
if (!slotsPattern.test(given)) throw new UsageError(`--${flag} ${given} is not a whole number of slots.`);
|
|
6202
|
+
return Number(given);
|
|
6203
|
+
};
|
|
6204
|
+
const splitOptions = {
|
|
6205
|
+
"reserve-cpus": { type: "string" },
|
|
6206
|
+
"reserve-memory": { type: "string" },
|
|
6207
|
+
"monitor-cpus": { type: "string" },
|
|
6208
|
+
"monitor-memory": { type: "string" },
|
|
6209
|
+
"monitor-slots": { type: "string" },
|
|
6210
|
+
"executor-cpus": { type: "string" },
|
|
6211
|
+
"executor-memory": { type: "string" },
|
|
6212
|
+
"executor-slots": { type: "string" }
|
|
6213
|
+
};
|
|
6214
|
+
const splitOptionOf = {
|
|
6215
|
+
"reserve.cpus": {
|
|
6216
|
+
flag: "reserve-cpus",
|
|
6217
|
+
read: readCpus
|
|
6218
|
+
},
|
|
6219
|
+
"reserve.memoryMb": {
|
|
6220
|
+
flag: "reserve-memory",
|
|
6221
|
+
read: readMemory
|
|
6222
|
+
},
|
|
6223
|
+
"monitor.cpus": {
|
|
6224
|
+
flag: "monitor-cpus",
|
|
6225
|
+
read: readCpus
|
|
6226
|
+
},
|
|
6227
|
+
"monitor.memoryMb": {
|
|
6228
|
+
flag: "monitor-memory",
|
|
6229
|
+
read: readMemory
|
|
6230
|
+
},
|
|
6231
|
+
"monitor.slots": {
|
|
6232
|
+
flag: "monitor-slots",
|
|
6233
|
+
read: readSlots
|
|
6234
|
+
},
|
|
6235
|
+
"executor.cpus": {
|
|
6236
|
+
flag: "executor-cpus",
|
|
6237
|
+
read: readCpus
|
|
6238
|
+
},
|
|
6239
|
+
"executor.memoryMb": {
|
|
6240
|
+
flag: "executor-memory",
|
|
6241
|
+
read: readMemory
|
|
6242
|
+
},
|
|
6243
|
+
"executor.slots": {
|
|
6244
|
+
flag: "executor-slots",
|
|
6245
|
+
read: readSlots
|
|
6246
|
+
}
|
|
6247
|
+
};
|
|
6248
|
+
const flagOf = (field) => `--${splitOptionOf[field].flag}`;
|
|
6249
|
+
const changeOf = (values) => Object.fromEntries(splitFields.flatMap((field) => {
|
|
6250
|
+
const { flag, read } = splitOptionOf[field];
|
|
6251
|
+
const given = values[flag];
|
|
6252
|
+
return given === void 0 ? [] : [[field, read(flag, given)]];
|
|
6253
|
+
}));
|
|
6254
|
+
const createSplitCommand = (adaptersOf) => defineGroup({
|
|
6255
|
+
name: "split",
|
|
6256
|
+
summary: "How a node is split into a reserve and two pools, and changing it.",
|
|
6257
|
+
usage: "nodes split [set|clear] <node>",
|
|
6258
|
+
fallback: defineCommand({
|
|
6259
|
+
name: "split",
|
|
6260
|
+
summary: "Show the split of a node, its host and what one slot of each pool gets.",
|
|
6261
|
+
usage: "nodes split <node>",
|
|
6262
|
+
optionsUsage: nodeUsage,
|
|
6263
|
+
options: {},
|
|
6264
|
+
positionals: {
|
|
6265
|
+
min: 1,
|
|
6266
|
+
max: 1
|
|
6267
|
+
},
|
|
6268
|
+
run: async (invocation) => {
|
|
6269
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6270
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
6271
|
+
return printed(run, splitViewOf(await nodeDetailOf(run.api, reference)), splitText);
|
|
6272
|
+
}
|
|
6273
|
+
}),
|
|
6274
|
+
subcommands: [defineCommand({
|
|
6275
|
+
name: "set",
|
|
6276
|
+
summary: "Split a node into a reserve and two pools, or change parts of its split.",
|
|
6277
|
+
usage: "nodes split set <node> [--<part>-cpus <n>] [--<part>-memory <size>] [--<pool>-slots <n>]",
|
|
6278
|
+
optionsUsage: [
|
|
6279
|
+
...nodeUsage,
|
|
6280
|
+
["--reserve-cpus <n>", "the CPUs kept for the host itself"],
|
|
6281
|
+
["--reserve-memory <size>", "the memory kept for the host itself"],
|
|
6282
|
+
["--monitor-cpus <n>", "the CPUs the monitor sessions share"],
|
|
6283
|
+
["--monitor-memory <size>", "the memory the monitor sessions share"],
|
|
6284
|
+
["--monitor-slots <n>", "how many monitor sessions run at once"],
|
|
6285
|
+
["--executor-cpus <n>", "the CPUs the executor sessions share"],
|
|
6286
|
+
["--executor-memory <size>", "the memory the executor sessions share"],
|
|
6287
|
+
["--executor-slots <n>", "how many executor sessions run at once"],
|
|
6288
|
+
["", "a size is megabytes, or a number with M or G, as in 2048 or 2G"],
|
|
6289
|
+
["", "a part left out keeps its value; a node without a split needs all eight"]
|
|
6290
|
+
],
|
|
6291
|
+
options: splitOptions,
|
|
6292
|
+
positionals: {
|
|
6293
|
+
min: 1,
|
|
6294
|
+
max: 1
|
|
6295
|
+
},
|
|
6296
|
+
run: async (invocation) => {
|
|
6297
|
+
const change = changeOf(invocation.values);
|
|
6298
|
+
if (Object.keys(change).length === 0) throw new UsageError("nodes split set needs at least one part of the split.", "kcloud nodes split set --help lists the parts.");
|
|
6299
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6300
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
6301
|
+
const node = await nodeDetailOf(run.api, reference);
|
|
6302
|
+
const split = fittingSplit(node, change, flagOf);
|
|
6303
|
+
const changed = await splitNode(run.api, reference, node.revision, split);
|
|
6304
|
+
return printed(run, splitViewOf(changed), splitText);
|
|
6305
|
+
}
|
|
6306
|
+
}), defineCommand({
|
|
6307
|
+
name: "clear",
|
|
6308
|
+
summary: "Remove the split of a node, so its slots are counted from its host.",
|
|
6309
|
+
usage: "nodes split clear <node>",
|
|
6310
|
+
optionsUsage: nodeUsage,
|
|
6311
|
+
options: {},
|
|
6312
|
+
positionals: {
|
|
6313
|
+
min: 1,
|
|
6314
|
+
max: 1
|
|
6315
|
+
},
|
|
6316
|
+
run: async (invocation) => {
|
|
6317
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6318
|
+
const given = invocation.positionals[0] ?? "";
|
|
6319
|
+
const reference = await resolveNode(run, given);
|
|
6320
|
+
const node = await nodeDetailOf(run.api, reference);
|
|
6321
|
+
if (node.split === null) return printed(run, splitViewOf(node), () => `${node.name} has no split; nothing was changed.`);
|
|
6322
|
+
await confirmed({
|
|
6323
|
+
prompt: run.prompt,
|
|
6324
|
+
settings: run.settings,
|
|
6325
|
+
question: `Clear the split of ${given}? Its pools lose their limits on CPU and memory.`
|
|
6326
|
+
});
|
|
6327
|
+
const cleared = await splitNode(run.api, reference, node.revision, null);
|
|
6328
|
+
return printed(run, splitViewOf(cleared), splitText);
|
|
6329
|
+
}
|
|
6330
|
+
})]
|
|
6331
|
+
});
|
|
6332
|
+
//#endregion
|
|
6333
|
+
//#region src/mcp/tools/nodes.ts
|
|
6334
|
+
const nodeField = z.string().trim().min(1).describe("The node, by its name or its id, from nodes_list.");
|
|
6335
|
+
const poolPart = (role) => nodePoolSplitSchema.partial().optional().describe(`The CPUs, the memory in MB and the slots the ${role} sessions share; a field left out keeps its value.`);
|
|
6336
|
+
const nodeTools = [
|
|
6337
|
+
defineTool({
|
|
6338
|
+
name: "nodes_list",
|
|
6339
|
+
title: "List nodes",
|
|
6340
|
+
description: "List the nodes of the organization this server acts in, the hosts its agents run on, with their state, engine, version, capabilities, split and agent count.",
|
|
6341
|
+
operations: ["listNodes"],
|
|
6342
|
+
safety: "read",
|
|
6343
|
+
input: z.object({}),
|
|
6344
|
+
run: async (_, run) => nodesOf(run.api, (await run.organization()).id)
|
|
6345
|
+
}),
|
|
6346
|
+
defineTool({
|
|
6347
|
+
name: "node_show",
|
|
6348
|
+
title: "Show a node",
|
|
6349
|
+
description: "Show one node with its host, its capabilities, its split, the agents placed on it with their pool limits, and the runtime and pools it reported last.",
|
|
6350
|
+
operations: ["getNode"],
|
|
6351
|
+
safety: "read",
|
|
6352
|
+
input: z.object({ node: nodeField }),
|
|
6353
|
+
run: async ({ node }, run) => nodeDetailOf(run.api, await resolveNode(run, node))
|
|
6354
|
+
}),
|
|
6355
|
+
defineTool({
|
|
6356
|
+
name: "node_split_set",
|
|
6357
|
+
title: "Split a node into pools",
|
|
6358
|
+
description: "Split a node into a reserve kept for the host, a monitor pool and an executor pool, each with CPUs and memory in MB, the pools also with slots. A part left out keeps its value; a node without a split needs every part. The split has to fit the host. Returns the split with the host and what is left free. Takes an admin of the organization.",
|
|
6359
|
+
operations: ["getNode", "updateNodeSplit"],
|
|
6360
|
+
safety: "write",
|
|
6361
|
+
idempotent: true,
|
|
6362
|
+
input: z.object({
|
|
6363
|
+
node: nodeField,
|
|
6364
|
+
reserve: nodeReserveSchema.partial().optional().describe("The CPUs and the memory in MB kept for the host itself; a field left out keeps its value."),
|
|
6365
|
+
monitor: poolPart("monitor"),
|
|
6366
|
+
executor: poolPart("executor")
|
|
6367
|
+
}),
|
|
6368
|
+
run: async ({ node, ...parts }, run) => {
|
|
6369
|
+
const change = changeOfParts(parts);
|
|
6370
|
+
if (Object.keys(change).length === 0) throw new CliError("Name at least one part of the split to change.");
|
|
6371
|
+
const reference = await resolveNode(run, node);
|
|
6372
|
+
const detail = await nodeDetailOf(run.api, reference);
|
|
6373
|
+
const split = fittingSplit(detail, change, (field) => field);
|
|
6374
|
+
return splitViewOf(await splitNode(run.api, reference, detail.revision, split));
|
|
6375
|
+
}
|
|
6376
|
+
}),
|
|
6377
|
+
defineTool({
|
|
6378
|
+
name: "node_split_clear",
|
|
6379
|
+
title: "Clear the split of a node",
|
|
6380
|
+
description: "Remove the split of a node, so its slots are counted from its host again and its pools lose their limits on CPU and memory. Takes an admin of the organization.",
|
|
6381
|
+
operations: ["getNode", "updateNodeSplit"],
|
|
6382
|
+
safety: "destructive",
|
|
6383
|
+
idempotent: true,
|
|
6384
|
+
input: z.object({ node: nodeField }),
|
|
6385
|
+
run: async ({ node }, run) => {
|
|
6386
|
+
const reference = await resolveNode(run, node);
|
|
6387
|
+
const detail = await nodeDetailOf(run.api, reference);
|
|
6388
|
+
if (detail.split === null) return splitViewOf(detail);
|
|
6389
|
+
return splitViewOf(await splitNode(run.api, reference, detail.revision, null));
|
|
6390
|
+
}
|
|
6391
|
+
}),
|
|
6392
|
+
defineTool({
|
|
6393
|
+
name: "node_delete",
|
|
6394
|
+
title: "Delete a node",
|
|
6395
|
+
description: "Delete a node, after which its host can no longer reach the platform. The instance refuses while agents are placed on it.",
|
|
6396
|
+
operations: ["deleteNode"],
|
|
6397
|
+
safety: "destructive",
|
|
6398
|
+
input: z.object({ node: nodeField }),
|
|
6399
|
+
run: async ({ node }, run) => {
|
|
6400
|
+
const reference = await resolveNode(run, node);
|
|
6401
|
+
await deleteNode(run.api, reference);
|
|
6402
|
+
return { deleted: reference.nodeId };
|
|
6403
|
+
}
|
|
6404
|
+
})
|
|
6405
|
+
];
|
|
6406
|
+
//#endregion
|
|
6407
|
+
//#region src/mcp/tools/backups.ts
|
|
6408
|
+
const agentBackupTools = [
|
|
6409
|
+
defineTool({
|
|
6410
|
+
name: "agent_backups_list",
|
|
6411
|
+
title: "List the backups of an agent",
|
|
6412
|
+
description: "List the backups of the memory of an agent, the newest first, with their trigger, node, Kikimora release, entries, size and expiry, and the days a backup is kept.",
|
|
6413
|
+
operations: ["listAgentBackups"],
|
|
6414
|
+
safety: "read",
|
|
6415
|
+
input: z.object(agentFields),
|
|
6416
|
+
run: async (reference, run) => backupsOf(run.api, await agentIdOf(run, reference))
|
|
5699
6417
|
}),
|
|
5700
6418
|
defineTool({
|
|
5701
|
-
name: "
|
|
5702
|
-
title: "
|
|
5703
|
-
description: "Ask the
|
|
5704
|
-
operations: ["
|
|
6419
|
+
name: "agent_backup_request",
|
|
6420
|
+
title: "Back an agent up now",
|
|
6421
|
+
description: "Ask the node of an agent for a backup of its memory now. state requested means the node makes it, and agent_backups_list shows it once the node has sent it.",
|
|
6422
|
+
operations: ["requestAgentBackup"],
|
|
5705
6423
|
safety: "write",
|
|
5706
6424
|
input: z.object(agentFields),
|
|
5707
|
-
run: async (reference, run) =>
|
|
6425
|
+
run: async (reference, run) => requestedBackup(run.api, await agentIdOf(run, reference))
|
|
5708
6426
|
}),
|
|
5709
6427
|
defineTool({
|
|
5710
|
-
name: "
|
|
5711
|
-
title: "
|
|
5712
|
-
description: "
|
|
5713
|
-
operations: ["
|
|
5714
|
-
safety: "
|
|
6428
|
+
name: "agent_restore",
|
|
6429
|
+
title: "Restore an agent from a backup",
|
|
6430
|
+
description: "Create a new agent in the project of an agent, with its configuration and the memory of one of its backups, on the node named. Returns the id of the new agent, which stays stopped until agent_action starts it.",
|
|
6431
|
+
operations: ["restoreAgentFromBackup"],
|
|
6432
|
+
safety: "write",
|
|
5715
6433
|
input: z.object({
|
|
5716
6434
|
...agentFields,
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
]),
|
|
5722
|
-
now: z.boolean().default(false).describe("For stop: stop the container at once.")
|
|
6435
|
+
backup: idSchema.describe("The id of the backup, from agent_backups_list."),
|
|
6436
|
+
name: agentNameSchema.describe("The name of the new agent."),
|
|
6437
|
+
slug: slugSchema.optional().describe("The slug of the new agent, instead of one made from its name."),
|
|
6438
|
+
node: nodeField
|
|
5723
6439
|
}),
|
|
5724
|
-
run: async ({
|
|
6440
|
+
run: async ({ backup, name, slug, node, ...reference }, run) => restoredAgent(run.api, await agentIdOf(run, reference), {
|
|
6441
|
+
backupId: backup,
|
|
6442
|
+
name,
|
|
6443
|
+
node,
|
|
6444
|
+
slug
|
|
6445
|
+
})
|
|
5725
6446
|
})
|
|
5726
6447
|
];
|
|
5727
6448
|
//#endregion
|
|
@@ -5794,42 +6515,6 @@ const credentialTools = [
|
|
|
5794
6515
|
})
|
|
5795
6516
|
];
|
|
5796
6517
|
//#endregion
|
|
5797
|
-
//#region src/mcp/tools/nodes.ts
|
|
5798
|
-
const nodeField = z.string().trim().min(1).describe("The node, by its name or its id, from nodes_list.");
|
|
5799
|
-
const nodeTools = [
|
|
5800
|
-
defineTool({
|
|
5801
|
-
name: "nodes_list",
|
|
5802
|
-
title: "List nodes",
|
|
5803
|
-
description: "List the nodes of the organization this server acts in, the hosts its agents run on, with their state, engine, version and agent count.",
|
|
5804
|
-
operations: ["listNodes"],
|
|
5805
|
-
safety: "read",
|
|
5806
|
-
input: z.object({}),
|
|
5807
|
-
run: async (_, run) => nodesOf(run.api, (await run.organization()).id)
|
|
5808
|
-
}),
|
|
5809
|
-
defineTool({
|
|
5810
|
-
name: "node_show",
|
|
5811
|
-
title: "Show a node",
|
|
5812
|
-
description: "Show one node with its host, its capabilities, the agents placed on it and the runtime it reported last.",
|
|
5813
|
-
operations: ["getNode"],
|
|
5814
|
-
safety: "read",
|
|
5815
|
-
input: z.object({ node: nodeField }),
|
|
5816
|
-
run: async ({ node }, run) => nodeDetailOf(run.api, await resolveNode(run, node))
|
|
5817
|
-
}),
|
|
5818
|
-
defineTool({
|
|
5819
|
-
name: "node_delete",
|
|
5820
|
-
title: "Delete a node",
|
|
5821
|
-
description: "Delete a node, after which its host can no longer reach the platform. The instance refuses while agents are placed on it.",
|
|
5822
|
-
operations: ["deleteNode"],
|
|
5823
|
-
safety: "destructive",
|
|
5824
|
-
input: z.object({ node: nodeField }),
|
|
5825
|
-
run: async ({ node }, run) => {
|
|
5826
|
-
const reference = await resolveNode(run, node);
|
|
5827
|
-
await deleteNode(run.api, reference);
|
|
5828
|
-
return { deleted: reference.nodeId };
|
|
5829
|
-
}
|
|
5830
|
-
})
|
|
5831
|
-
];
|
|
5832
|
-
//#endregion
|
|
5833
6518
|
//#region src/commands/notifications.ts
|
|
5834
6519
|
const notificationHints = { "notification.not_found": "kcloud notifications list shows them." };
|
|
5835
6520
|
const createNotificationsCommand = (adaptersOf) => defineGroup({
|
|
@@ -7851,6 +8536,7 @@ const kcloudTools = [
|
|
|
7851
8536
|
...projectToolTools,
|
|
7852
8537
|
...nodeTools,
|
|
7853
8538
|
...agentTools,
|
|
8539
|
+
...agentBackupTools,
|
|
7854
8540
|
...taskTools,
|
|
7855
8541
|
...observabilityTools,
|
|
7856
8542
|
...notificationTools,
|
|
@@ -7928,6 +8614,214 @@ const createMcpCommand = (adaptersOf, cliVersion) => defineCommand({
|
|
|
7928
8614
|
}
|
|
7929
8615
|
});
|
|
7930
8616
|
//#endregion
|
|
8617
|
+
//#region src/commands/nodes/index.ts
|
|
8618
|
+
const gibibytes = (bytes) => `${(bytes / 1024 ** 3).toFixed(1)} GiB`;
|
|
8619
|
+
const hostOf = (node) => node.host === null ? "-" : `${node.host.cpus} CPU, ${gibibytes(node.host.memoryBytes)} of memory`;
|
|
8620
|
+
const capabilitiesOf = (node) => {
|
|
8621
|
+
if (node.capabilities === null) return "-";
|
|
8622
|
+
const { sandbox, uidIsolation } = node.capabilities;
|
|
8623
|
+
return `sandbox ${sandbox ? "yes" : "no"}, uid isolation ${uidIsolation ? "yes" : "no"}`;
|
|
8624
|
+
};
|
|
8625
|
+
const registrationOf = (node) => node.registrationExpiresAt === null ? "-" : `a token waits until ${node.registrationExpiresAt}`;
|
|
8626
|
+
const storageOf = ({ state, freeBytes, totalBytes, path }) => `${state}, ${gibibytes(freeBytes)} free of ${gibibytes(totalBytes)} at ${path}`;
|
|
8627
|
+
const runtimeRowsOf = (runtime) => [
|
|
8628
|
+
["reported", runtime.reportedAt],
|
|
8629
|
+
["docker", runtime.docker.lastError === null ? runtime.docker.state : `${runtime.docker.state}: ${runtime.docker.lastError}`],
|
|
8630
|
+
["data storage", storageOf(runtime.storage.data)],
|
|
8631
|
+
["engine storage", storageOf(runtime.storage.engine)],
|
|
8632
|
+
["last reconcile", runtime.reconcile.lastTickError === null ? runtime.reconcile.lastTickAt ?? "never" : `${runtime.reconcile.lastTickAt ?? "never"}: ${runtime.reconcile.lastTickError}`],
|
|
8633
|
+
["agents", `${runtime.reconcile.agentsActive} active, ${runtime.reconcile.agentsQueued} queued`],
|
|
8634
|
+
["log readers", String(runtime.ingest.readers)]
|
|
8635
|
+
];
|
|
8636
|
+
const updateOf = ({ update }) => {
|
|
8637
|
+
if (update === null) return "not reported";
|
|
8638
|
+
switch (update.state) {
|
|
8639
|
+
case "current": return "current";
|
|
8640
|
+
case "waiting": return `waits for ${String(update.sessions)} ${update.sessions === 1 ? "session" : "sessions"} to end before ${update.target ?? "-"}`;
|
|
8641
|
+
case "updating": return `updating to ${update.target ?? "-"}`;
|
|
8642
|
+
case "failed": return `failed to reach ${update.target ?? "-"}, runs its previous version`;
|
|
8643
|
+
case "unmanaged": return "updated with its platform";
|
|
8644
|
+
}
|
|
8645
|
+
};
|
|
8646
|
+
const lastUpdateOf = ({ update }) => {
|
|
8647
|
+
const last = update?.last ?? null;
|
|
8648
|
+
if (last === null) return null;
|
|
8649
|
+
const outcome = `${last.outcome} ${last.version ?? "-"} at ${last.at}`;
|
|
8650
|
+
return last.reason === null ? outcome : `${outcome}: ${last.reason}`;
|
|
8651
|
+
};
|
|
8652
|
+
const nodeDetailsText = (node) => [
|
|
8653
|
+
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
8654
|
+
["name", node.name],
|
|
8655
|
+
["state", node.state],
|
|
8656
|
+
["provider", node.provider],
|
|
8657
|
+
["engine", node.engine ?? "-"],
|
|
8658
|
+
["version", node.version ?? "-"],
|
|
8659
|
+
["host", hostOf(node)],
|
|
8660
|
+
["server", node.host?.serverVersion ?? "-"],
|
|
8661
|
+
["data root", node.host?.dataRoot ?? "-"],
|
|
8662
|
+
["capabilities", capabilitiesOf(node)],
|
|
8663
|
+
["offers", node.offered.length === 0 ? "-" : node.offered.join(", ")],
|
|
8664
|
+
["split", splitSummaryOf(node.split)],
|
|
8665
|
+
["registration", registrationOf(node)],
|
|
8666
|
+
["connected", node.connectedAt ?? "never"],
|
|
8667
|
+
["last seen", node.lastSeenAt ?? "never"],
|
|
8668
|
+
...node.offlineSince === null ? [] : [["offline since", node.offlineSince]],
|
|
8669
|
+
["update", updateOf(node)],
|
|
8670
|
+
...lastUpdateOf(node) === null ? [] : [["last update", lastUpdateOf(node) ?? ""]],
|
|
8671
|
+
["created", node.createdAt],
|
|
8672
|
+
["id", node.id]
|
|
8673
|
+
]),
|
|
8674
|
+
"",
|
|
8675
|
+
node.agents.length === 0 ? "No agent is placed on the node." : renderTable([
|
|
8676
|
+
{ header: "PROJECT" },
|
|
8677
|
+
{ header: "AGENT" },
|
|
8678
|
+
{ header: "STATE" },
|
|
8679
|
+
{ header: "POOL LIMIT" },
|
|
8680
|
+
{ header: "ID" }
|
|
8681
|
+
], node.agents.map((agent) => [
|
|
8682
|
+
agent.projectName,
|
|
8683
|
+
agent.slug,
|
|
8684
|
+
agent.state,
|
|
8685
|
+
poolLimitText(agent.poolLimit),
|
|
8686
|
+
agent.id
|
|
8687
|
+
])),
|
|
8688
|
+
"",
|
|
8689
|
+
...node.runtime === null ? ["The node has reported no runtime yet."] : [
|
|
8690
|
+
renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime)),
|
|
8691
|
+
"",
|
|
8692
|
+
poolsText(node.runtime.pools)
|
|
8693
|
+
]
|
|
8694
|
+
].join("\n");
|
|
8695
|
+
const startHintOf = (origin) => [
|
|
8696
|
+
"Start the node with it as docs/install.md of kikimora-cloud describes. On a Linux host, after read -rs token:",
|
|
8697
|
+
` printf '%s\\n' "$token" | /opt/kikimora-cloud/infra/node/install.sh --platform-url ${origin} --image ghcr.io/kikimoradev/kikimora-cloud/node:<version> --registration-token -`,
|
|
8698
|
+
"On a Windows PC:",
|
|
8699
|
+
` C:\\kikimora-cloud\\src\\infra\\windows\\install.ps1 -PlatformUrl ${origin} -RegistrationToken <token>`
|
|
8700
|
+
].join("\n");
|
|
8701
|
+
const printedRegistration = (run, created) => {
|
|
8702
|
+
if (run.settings.json) {
|
|
8703
|
+
run.context.terminal.json(created);
|
|
8704
|
+
return exitCodes.ok;
|
|
8705
|
+
}
|
|
8706
|
+
run.context.terminal.line(created.registration.token);
|
|
8707
|
+
run.context.terminal.error([`The registration token of ${created.node.name} is shown only here and works once, until ${created.registration.expiresAt}.`, startHintOf(run.origin)].join("\n"));
|
|
8708
|
+
return exitCodes.ok;
|
|
8709
|
+
};
|
|
8710
|
+
const createNodesCommand = (adaptersOf) => defineGroup({
|
|
8711
|
+
name: "nodes",
|
|
8712
|
+
summary: "The nodes of the organization, the hosts its agents run on.",
|
|
8713
|
+
usage: "nodes <command>",
|
|
8714
|
+
subcommands: [
|
|
8715
|
+
defineCommand({
|
|
8716
|
+
name: "list",
|
|
8717
|
+
summary: "List the nodes of the organization.",
|
|
8718
|
+
usage: "nodes list",
|
|
8719
|
+
options: {},
|
|
8720
|
+
positionals: {
|
|
8721
|
+
min: 0,
|
|
8722
|
+
max: 0
|
|
8723
|
+
},
|
|
8724
|
+
run: async (invocation) => {
|
|
8725
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8726
|
+
const organization = await run.organization();
|
|
8727
|
+
const items = await nodesOf(run.api, organization.id);
|
|
8728
|
+
return printed(run, items, (nodes) => nodes.length === 0 ? `${organization.slug} has no node yet; kcloud nodes create <name> creates one.` : renderTable([
|
|
8729
|
+
{ header: "NAME" },
|
|
8730
|
+
{ header: "STATE" },
|
|
8731
|
+
{ header: "ENGINE" },
|
|
8732
|
+
{ header: "VERSION" },
|
|
8733
|
+
{
|
|
8734
|
+
header: "AGENTS",
|
|
8735
|
+
align: "right"
|
|
8736
|
+
},
|
|
8737
|
+
{ header: "LAST SEEN" },
|
|
8738
|
+
{ header: "ID" }
|
|
8739
|
+
], nodes.map((node) => [
|
|
8740
|
+
node.name,
|
|
8741
|
+
node.state,
|
|
8742
|
+
node.engine ?? "-",
|
|
8743
|
+
node.version ?? "-",
|
|
8744
|
+
String(node.agentCount),
|
|
8745
|
+
node.lastSeenAt ?? "never",
|
|
8746
|
+
node.id
|
|
8747
|
+
])));
|
|
8748
|
+
}
|
|
8749
|
+
}),
|
|
8750
|
+
defineCommand({
|
|
8751
|
+
name: "show",
|
|
8752
|
+
summary: "Show a node with its host, its split, the agents placed on it, its runtime and its pools.",
|
|
8753
|
+
usage: "nodes show <node>",
|
|
8754
|
+
optionsUsage: nodeUsage,
|
|
8755
|
+
options: {},
|
|
8756
|
+
positionals: {
|
|
8757
|
+
min: 1,
|
|
8758
|
+
max: 1
|
|
8759
|
+
},
|
|
8760
|
+
run: async (invocation) => {
|
|
8761
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8762
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
8763
|
+
return printed(run, await nodeDetailOf(run.api, reference), nodeDetailsText);
|
|
8764
|
+
}
|
|
8765
|
+
}),
|
|
8766
|
+
createSplitCommand(adaptersOf),
|
|
8767
|
+
defineCommand({
|
|
8768
|
+
name: "create",
|
|
8769
|
+
summary: "Create a node and print its one-time registration token.",
|
|
8770
|
+
usage: "nodes create <name>",
|
|
8771
|
+
optionsUsage: [["", "the name holds lowercase letters, digits and hyphens"], ["", "the token goes to stdout alone, how to start the node to stderr"]],
|
|
8772
|
+
options: {},
|
|
8773
|
+
positionals: {
|
|
8774
|
+
min: 1,
|
|
8775
|
+
max: 1
|
|
8776
|
+
},
|
|
8777
|
+
run: async (invocation) => {
|
|
8778
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8779
|
+
const organization = await run.organization();
|
|
8780
|
+
return printedRegistration(run, await createdNode(run.api, organization.id, invocation.positionals[0] ?? ""));
|
|
8781
|
+
}
|
|
8782
|
+
}),
|
|
8783
|
+
defineCommand({
|
|
8784
|
+
name: "token",
|
|
8785
|
+
summary: "Issue a new registration token for a node that has not joined yet.",
|
|
8786
|
+
usage: "nodes token <node>",
|
|
8787
|
+
optionsUsage: [...nodeUsage, ["", "the token replaces the one issued before, which stops working"]],
|
|
8788
|
+
options: {},
|
|
8789
|
+
positionals: {
|
|
8790
|
+
min: 1,
|
|
8791
|
+
max: 1
|
|
8792
|
+
},
|
|
8793
|
+
run: async (invocation) => {
|
|
8794
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8795
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
8796
|
+
return printedRegistration(run, await renewedRegistration(run.api, reference));
|
|
8797
|
+
}
|
|
8798
|
+
}),
|
|
8799
|
+
defineCommand({
|
|
8800
|
+
name: "delete",
|
|
8801
|
+
summary: "Delete a node that runs no agent.",
|
|
8802
|
+
usage: "nodes delete <node>",
|
|
8803
|
+
optionsUsage: nodeUsage,
|
|
8804
|
+
options: {},
|
|
8805
|
+
positionals: {
|
|
8806
|
+
min: 1,
|
|
8807
|
+
max: 1
|
|
8808
|
+
},
|
|
8809
|
+
run: async (invocation) => {
|
|
8810
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8811
|
+
const given = invocation.positionals[0] ?? "";
|
|
8812
|
+
const reference = await resolveNode(run, given);
|
|
8813
|
+
await confirmed({
|
|
8814
|
+
prompt: run.prompt,
|
|
8815
|
+
settings: run.settings,
|
|
8816
|
+
question: `Delete the node ${given}? Its host can no longer reach the platform.`
|
|
8817
|
+
});
|
|
8818
|
+
await deleteNode(run.api, reference);
|
|
8819
|
+
return said(run, `Deleted the node ${given}.`);
|
|
8820
|
+
}
|
|
8821
|
+
})
|
|
8822
|
+
]
|
|
8823
|
+
});
|
|
8824
|
+
//#endregion
|
|
7931
8825
|
//#region src/commands/releases.ts
|
|
7932
8826
|
const createReleasesCommand = (adaptersOf) => defineGroup({
|
|
7933
8827
|
name: "releases",
|