@kikimoradev/kcloud 1.1.0 → 1.3.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 +878 -296
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -518,7 +518,9 @@ const runCli = dispatcherOf(spec);
|
|
|
518
518
|
//#endregion
|
|
519
519
|
//#region ../../packages/contracts/src/patterns.ts
|
|
520
520
|
const slugPattern = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;
|
|
521
|
+
const uuidShapePattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
521
522
|
const hexColorPattern = /^#[0-9a-f]{6}$/;
|
|
523
|
+
const logoHashPattern = /^[0-9a-f]{64}$/;
|
|
522
524
|
const userCodePattern = /^[23456789BCDFGHJKLMNPQRSTVWXZ]{4}-[23456789BCDFGHJKLMNPQRSTVWXZ]{4}$/;
|
|
523
525
|
const versionTagPattern = /^(?:0|[1-9]\d{0,8})\.(?:0|[1-9]\d{0,8})\.(?:0|[1-9]\d{0,8})$/;
|
|
524
526
|
const effortLevelSchema = z.enum([
|
|
@@ -661,6 +663,22 @@ const credentialMetadataSchema = z.object({
|
|
|
661
663
|
checkedAt: z.iso.datetime().optional(),
|
|
662
664
|
failureReason: z.string().optional()
|
|
663
665
|
}).meta({ id: "CredentialMetadata" });
|
|
666
|
+
const usageWindowStatusSchema = z.enum([
|
|
667
|
+
"allowed",
|
|
668
|
+
"allowed_warning",
|
|
669
|
+
"rejected"
|
|
670
|
+
]);
|
|
671
|
+
const usageWindowSchema = z.object({
|
|
672
|
+
utilization: z.number().nonnegative(),
|
|
673
|
+
resetsAt: z.iso.datetime().nullable(),
|
|
674
|
+
status: usageWindowStatusSchema.nullable()
|
|
675
|
+
}).meta({ id: "UsageWindow" });
|
|
676
|
+
const subscriptionUsageSchema = z.object({
|
|
677
|
+
fiveHour: usageWindowSchema.nullable(),
|
|
678
|
+
sevenDay: usageWindowSchema.nullable(),
|
|
679
|
+
readAt: z.iso.datetime(),
|
|
680
|
+
stale: z.boolean()
|
|
681
|
+
}).meta({ id: "SubscriptionUsage" });
|
|
664
682
|
const credentialSchema = z.object({
|
|
665
683
|
id: idSchema,
|
|
666
684
|
projectId: idSchema,
|
|
@@ -671,6 +689,8 @@ const credentialSchema = z.object({
|
|
|
671
689
|
verification: verificationStatusSchema,
|
|
672
690
|
metadata: credentialMetadataSchema,
|
|
673
691
|
expiresAt: z.iso.datetime().nullable(),
|
|
692
|
+
expiresAtComputed: z.boolean(),
|
|
693
|
+
usage: subscriptionUsageSchema.nullable(),
|
|
674
694
|
createdAt: z.iso.datetime(),
|
|
675
695
|
updatedAt: z.iso.datetime()
|
|
676
696
|
}).meta({ id: "Credential" });
|
|
@@ -739,6 +759,7 @@ const errorCodes = Object.keys({
|
|
|
739
759
|
"credential.in_use": "conflict",
|
|
740
760
|
"credential.kind_mismatch": "rule_violation",
|
|
741
761
|
"credential.rejected": "rule_violation",
|
|
762
|
+
"credential.usage_unavailable": "rule_violation",
|
|
742
763
|
"repository.not_found": "not_found",
|
|
743
764
|
"repository.already_registered": "conflict",
|
|
744
765
|
"repository.in_use": "conflict",
|
|
@@ -805,7 +826,6 @@ const errorCodes = Object.keys({
|
|
|
805
826
|
"node.size_unavailable": "rule_violation",
|
|
806
827
|
"node.size_not_larger": "rule_violation",
|
|
807
828
|
"node.region_unavailable": "rule_violation",
|
|
808
|
-
"node.import_refused": "rule_violation",
|
|
809
829
|
"provider.not_found": "not_found",
|
|
810
830
|
"provider.name_taken": "conflict",
|
|
811
831
|
"provider.in_use": "conflict",
|
|
@@ -819,20 +839,17 @@ const errorCodes = Object.keys({
|
|
|
819
839
|
"infrastructure.provider_error": "internal",
|
|
820
840
|
"infrastructure.operation_in_progress": "conflict",
|
|
821
841
|
"infrastructure.host_ops_disabled": "rule_violation",
|
|
822
|
-
"infrastructure.backups_unavailable": "rule_violation",
|
|
823
|
-
"infrastructure.maintenance_in_progress": "conflict",
|
|
824
|
-
"infrastructure.maintenance_not_in_progress": "conflict",
|
|
825
842
|
"backup.not_found": "not_found",
|
|
826
843
|
"backup.in_progress": "conflict",
|
|
827
844
|
"backup.unavailable": "rule_violation",
|
|
828
845
|
"backup.corrupt": "rule_violation",
|
|
829
846
|
"backup.undecryptable": "rule_violation",
|
|
830
|
-
"
|
|
831
|
-
"
|
|
832
|
-
"
|
|
833
|
-
"
|
|
834
|
-
"
|
|
835
|
-
"
|
|
847
|
+
"notification.not_found": "not_found",
|
|
848
|
+
"logo.not_found": "not_found",
|
|
849
|
+
"logo.unreadable": "rule_violation",
|
|
850
|
+
"logo.too_small": "rule_violation",
|
|
851
|
+
"logo.too_large": "rule_violation",
|
|
852
|
+
"logo.type_mismatch": "rule_violation"
|
|
836
853
|
});
|
|
837
854
|
const errorCodeSchema = z.enum(errorCodes);
|
|
838
855
|
const errorDetailsSchema = z.record(z.string(), z.union([
|
|
@@ -1083,7 +1100,8 @@ agentEventSchema.options.map((option) => option.shape.event.value);
|
|
|
1083
1100
|
crashLooping: "platform.crashLooping",
|
|
1084
1101
|
provisioned: "platform.provisioned",
|
|
1085
1102
|
sandboxStarted: "platform.sandboxStarted",
|
|
1086
|
-
sandboxStopped: "platform.sandboxStopped"
|
|
1103
|
+
sandboxStopped: "platform.sandboxStopped",
|
|
1104
|
+
restartedByNode: "platform.restartedByNode"
|
|
1087
1105
|
}).rawLog;
|
|
1088
1106
|
const agentStateSchema = z.enum([
|
|
1089
1107
|
"created",
|
|
@@ -1180,46 +1198,11 @@ const degradedAgentSchema = z.object({
|
|
|
1180
1198
|
z.object({
|
|
1181
1199
|
status: z.enum(runtimeHealthStates),
|
|
1182
1200
|
database: z.object({ ok: z.boolean() }),
|
|
1183
|
-
engine: z.enum(runtimeEngineKinds),
|
|
1184
|
-
docker: z.object({
|
|
1185
|
-
state: z.enum(dockerStates),
|
|
1186
|
-
lastError: z.string().nullable(),
|
|
1187
|
-
checkedAt: z.iso.datetime().nullable()
|
|
1188
|
-
}),
|
|
1189
|
-
storage: z.object({
|
|
1190
|
-
data: storageStateSchema,
|
|
1191
|
-
engine: storageStateSchema
|
|
1192
|
-
}),
|
|
1193
1201
|
partitions: z.object({
|
|
1194
1202
|
defaultRows: z.int().nonnegative(),
|
|
1195
1203
|
ensuredThrough: z.string().nullable(),
|
|
1196
1204
|
lastMaintenanceAt: z.iso.datetime().nullable()
|
|
1197
1205
|
}),
|
|
1198
|
-
reconcile: z.object({
|
|
1199
|
-
lastTickAt: z.iso.datetime().nullable(),
|
|
1200
|
-
lastTickError: z.string().nullable(),
|
|
1201
|
-
agentsQueued: z.int().nonnegative(),
|
|
1202
|
-
agentsActive: z.int().nonnegative()
|
|
1203
|
-
}),
|
|
1204
|
-
ingest: z.object({
|
|
1205
|
-
readers: z.int().nonnegative(),
|
|
1206
|
-
oldestCursorLagSeconds: z.number().nonnegative().nullable()
|
|
1207
|
-
}),
|
|
1208
|
-
sandbox: z.object({
|
|
1209
|
-
enabled: z.boolean(),
|
|
1210
|
-
image: z.object({
|
|
1211
|
-
tag: z.string(),
|
|
1212
|
-
digest: z.string()
|
|
1213
|
-
}),
|
|
1214
|
-
agentsWithSandbox: z.int().nonnegative(),
|
|
1215
|
-
sidecarsRunning: z.int().nonnegative(),
|
|
1216
|
-
volumeBytes: z.int().nonnegative().nullable()
|
|
1217
|
-
}),
|
|
1218
|
-
sessionLogs: z.object({
|
|
1219
|
-
prunedDirectories: z.int().nonnegative(),
|
|
1220
|
-
lastPrunedAt: z.iso.datetime().nullable(),
|
|
1221
|
-
failures: z.int().nonnegative()
|
|
1222
|
-
}),
|
|
1223
1206
|
metrics: runtimeMetricsSchema,
|
|
1224
1207
|
deadLetters: z.array(deadLetterQueueSchema),
|
|
1225
1208
|
degradedAgents: z.array(degradedAgentSchema)
|
|
@@ -1442,17 +1425,21 @@ const memorySummarySchema = z.object({
|
|
|
1442
1425
|
sessionId: z.string().nullable(),
|
|
1443
1426
|
createdAt: z.iso.datetime()
|
|
1444
1427
|
}).meta({ id: "MemorySummary" });
|
|
1445
|
-
|
|
1446
|
-
|
|
1428
|
+
const memoryOutcomes = ["succeeded", "failed"];
|
|
1429
|
+
const memoryOutcomeSchema = z.enum(memoryOutcomes);
|
|
1430
|
+
pageSchema(memorySummarySchema).extend({
|
|
1447
1431
|
source: z.enum(["worker", "unavailable"]),
|
|
1448
1432
|
staleReason: z.enum([
|
|
1449
1433
|
"unreachable",
|
|
1450
1434
|
"timeout",
|
|
1451
1435
|
"stopped"
|
|
1452
|
-
]).nullable()
|
|
1453
|
-
|
|
1436
|
+
]).nullable(),
|
|
1437
|
+
truncated: z.boolean()
|
|
1438
|
+
}).meta({ id: "AgentMemoryPage" });
|
|
1454
1439
|
z.object({
|
|
1455
1440
|
q: z.string().max(200).optional(),
|
|
1441
|
+
outcome: memoryOutcomeSchema.optional(),
|
|
1442
|
+
cursor: cursorSchema.optional(),
|
|
1456
1443
|
limit: z.coerce.number().int().min(1).max(100).default(20)
|
|
1457
1444
|
});
|
|
1458
1445
|
const costPointSchema = z.object({
|
|
@@ -1565,7 +1552,7 @@ z.object({
|
|
|
1565
1552
|
organizationId: idSchema,
|
|
1566
1553
|
accountId: idSchema
|
|
1567
1554
|
});
|
|
1568
|
-
z.enum(["monitor", "executor"]);
|
|
1555
|
+
const sessionRoleSchema = z.enum(["monitor", "executor"]);
|
|
1569
1556
|
const sessionSlotCountSchema = z.int().min(1).max(64);
|
|
1570
1557
|
const sessionQueueEntrySchema = z.object({
|
|
1571
1558
|
agentId: idSchema,
|
|
@@ -1574,8 +1561,21 @@ const sessionQueueEntrySchema = z.object({
|
|
|
1574
1561
|
projectName: z.string().nullable(),
|
|
1575
1562
|
position: z.int().positive(),
|
|
1576
1563
|
since: z.iso.datetime(),
|
|
1577
|
-
taskId: z.string().nullable()
|
|
1564
|
+
taskId: z.string().nullable(),
|
|
1565
|
+
taskTitle: z.string().nullable(),
|
|
1566
|
+
readable: z.boolean()
|
|
1578
1567
|
}).meta({ id: "SessionQueueEntry" });
|
|
1568
|
+
const sessionHolderSchema = z.object({
|
|
1569
|
+
agentId: idSchema,
|
|
1570
|
+
agentName: z.string().nullable(),
|
|
1571
|
+
projectId: idSchema.nullable(),
|
|
1572
|
+
projectName: z.string().nullable(),
|
|
1573
|
+
since: z.iso.datetime().nullable(),
|
|
1574
|
+
taskId: z.string().nullable(),
|
|
1575
|
+
taskTitle: z.string().nullable(),
|
|
1576
|
+
readable: z.boolean(),
|
|
1577
|
+
pid: z.int().positive().nullable()
|
|
1578
|
+
}).meta({ id: "SessionHolder" });
|
|
1579
1579
|
const sessionPoolSchema = z.object({
|
|
1580
1580
|
slots: sessionSlotCountSchema.nullable(),
|
|
1581
1581
|
effective: z.int().nonnegative(),
|
|
@@ -1602,6 +1602,12 @@ const agentAdmissionSchema = z.object({
|
|
|
1602
1602
|
monitor: agentAdmissionStateSchema.nullable(),
|
|
1603
1603
|
executor: agentAdmissionStateSchema.nullable()
|
|
1604
1604
|
}).meta({ id: "AgentAdmission" });
|
|
1605
|
+
const admissionHoldSchema = z.object({
|
|
1606
|
+
pools: z.array(sessionRoleSchema),
|
|
1607
|
+
sessions: z.int().nonnegative(),
|
|
1608
|
+
target: z.string().nullable(),
|
|
1609
|
+
updating: z.boolean()
|
|
1610
|
+
}).meta({ id: "AdmissionHold" });
|
|
1605
1611
|
const nodeStateSchema = z.enum([
|
|
1606
1612
|
"pending",
|
|
1607
1613
|
"online",
|
|
@@ -1670,16 +1676,36 @@ const poolUsageSchema = z.object({
|
|
|
1670
1676
|
memoryBytes: z.int().nonnegative(),
|
|
1671
1677
|
memoryMaxBytes: z.int().nonnegative().nullable(),
|
|
1672
1678
|
cpus: z.number().nonnegative().nullable(),
|
|
1673
|
-
memoryKills: z.int().nonnegative()
|
|
1679
|
+
memoryKills: z.int().nonnegative(),
|
|
1680
|
+
processors: z.int().positive().nullable().optional()
|
|
1674
1681
|
}).meta({ id: "PoolUsage" });
|
|
1675
1682
|
const nodePoolSchema = z.object({
|
|
1676
1683
|
slots: z.int().nonnegative(),
|
|
1677
1684
|
inUse: z.int().nonnegative(),
|
|
1678
1685
|
waiting: z.array(sessionQueueEntrySchema),
|
|
1686
|
+
holders: z.array(sessionHolderSchema).nullable(),
|
|
1679
1687
|
usage: poolUsageSchema.nullable()
|
|
1680
1688
|
}).meta({ id: "NodePool" });
|
|
1689
|
+
const sessionRefusalsSchema = z.object({
|
|
1690
|
+
count: z.int().positive(),
|
|
1691
|
+
lastAt: z.iso.datetime(),
|
|
1692
|
+
pool: sessionRoleSchema,
|
|
1693
|
+
reason: z.string()
|
|
1694
|
+
}).meta({ id: "SessionRefusals" });
|
|
1695
|
+
const poolLayoutSchema = z.object({
|
|
1696
|
+
cores: z.array(z.int().positive()).min(1),
|
|
1697
|
+
cpus: z.object({
|
|
1698
|
+
monitor: z.number().positive(),
|
|
1699
|
+
executor: z.number().positive()
|
|
1700
|
+
}).nullable(),
|
|
1701
|
+
topologyError: z.string().nullable(),
|
|
1702
|
+
clamped: z.boolean()
|
|
1703
|
+
}).meta({ id: "PoolLayout" });
|
|
1681
1704
|
const nodePoolsSchema = z.object({
|
|
1682
1705
|
enforcement: poolEnforcementSchema,
|
|
1706
|
+
agentsSlice: poolEnforcementSchema.nullable(),
|
|
1707
|
+
refused: sessionRefusalsSchema.nullable(),
|
|
1708
|
+
layout: poolLayoutSchema.nullable(),
|
|
1683
1709
|
monitor: nodePoolSchema,
|
|
1684
1710
|
executor: nodePoolSchema
|
|
1685
1711
|
}).meta({ id: "NodePools" });
|
|
@@ -1761,6 +1787,7 @@ const nodeUpdateSchema = z.object({
|
|
|
1761
1787
|
target: z.string().nullable(),
|
|
1762
1788
|
sessions: z.int().nonnegative(),
|
|
1763
1789
|
since: z.iso.datetime().nullable(),
|
|
1790
|
+
held: z.array(sessionRoleSchema),
|
|
1764
1791
|
last: z.object({
|
|
1765
1792
|
outcome: z.enum([
|
|
1766
1793
|
"updated",
|
|
@@ -1784,12 +1811,7 @@ const nodeAgentSchema = z.object({
|
|
|
1784
1811
|
const nodeRuntimeSchema = z.object({
|
|
1785
1812
|
reportedAt: z.iso.datetime(),
|
|
1786
1813
|
docker: z.object({
|
|
1787
|
-
state: z.enum(
|
|
1788
|
-
"ok",
|
|
1789
|
-
"unavailable",
|
|
1790
|
-
"disabled",
|
|
1791
|
-
"adopting"
|
|
1792
|
-
]),
|
|
1814
|
+
state: z.enum(dockerStates),
|
|
1793
1815
|
lastError: z.string().nullable()
|
|
1794
1816
|
}),
|
|
1795
1817
|
storage: z.object({
|
|
@@ -1802,7 +1824,20 @@ const nodeRuntimeSchema = z.object({
|
|
|
1802
1824
|
agentsQueued: z.int().nonnegative(),
|
|
1803
1825
|
agentsActive: z.int().nonnegative()
|
|
1804
1826
|
}),
|
|
1805
|
-
ingest: z.object({
|
|
1827
|
+
ingest: z.object({
|
|
1828
|
+
readers: z.int().nonnegative(),
|
|
1829
|
+
oldestCursorLagSeconds: z.number().nonnegative().nullable()
|
|
1830
|
+
}),
|
|
1831
|
+
sandbox: z.object({
|
|
1832
|
+
enabled: z.boolean(),
|
|
1833
|
+
image: z.object({
|
|
1834
|
+
tag: z.string(),
|
|
1835
|
+
digest: z.string()
|
|
1836
|
+
}),
|
|
1837
|
+
agentsWithSandbox: z.int().nonnegative(),
|
|
1838
|
+
sidecarsRunning: z.int().nonnegative(),
|
|
1839
|
+
volumeBytes: z.int().nonnegative().nullable()
|
|
1840
|
+
}),
|
|
1806
1841
|
sessions: z.object({
|
|
1807
1842
|
monitor: z.object({
|
|
1808
1843
|
effective: z.int().nonnegative(),
|
|
@@ -1821,6 +1856,28 @@ nodeSchema.extend({
|
|
|
1821
1856
|
update: nodeUpdateSchema.nullable()
|
|
1822
1857
|
}).meta({ id: "NodeDetail" });
|
|
1823
1858
|
z.object({ items: z.array(nodeSchema) }).meta({ id: "NodeList" });
|
|
1859
|
+
const adminNodeSessionsSchema = z.object({
|
|
1860
|
+
effective: z.int().nonnegative(),
|
|
1861
|
+
inUse: z.int().nonnegative()
|
|
1862
|
+
});
|
|
1863
|
+
const nodeHealthSchema = z.object({
|
|
1864
|
+
docker: z.enum(dockerStates),
|
|
1865
|
+
storage: z.enum(storageStates),
|
|
1866
|
+
reconcileError: z.string().nullable()
|
|
1867
|
+
}).meta({ id: "NodeHealth" });
|
|
1868
|
+
const adminNodeSchema = nodeSchema.extend({
|
|
1869
|
+
organization: z.object({
|
|
1870
|
+
id: idSchema,
|
|
1871
|
+
name: z.string().nullable()
|
|
1872
|
+
}),
|
|
1873
|
+
update: nodeUpdateSchema.nullable(),
|
|
1874
|
+
sessions: z.object({
|
|
1875
|
+
monitor: adminNodeSessionsSchema,
|
|
1876
|
+
executor: adminNodeSessionsSchema
|
|
1877
|
+
}).nullable(),
|
|
1878
|
+
health: nodeHealthSchema.nullable()
|
|
1879
|
+
}).meta({ id: "AdminNode" });
|
|
1880
|
+
z.object({ items: z.array(adminNodeSchema) }).meta({ id: "AdminNodeList" });
|
|
1824
1881
|
z.object({ nodeId: idSchema });
|
|
1825
1882
|
z.object({
|
|
1826
1883
|
organizationId: idSchema,
|
|
@@ -1894,17 +1951,51 @@ const splitFits = (split, host) => {
|
|
|
1894
1951
|
const overflow = splitOverflowOf(split, host);
|
|
1895
1952
|
return overflow.cpus <= 0 && overflow.memoryMb <= 0;
|
|
1896
1953
|
};
|
|
1954
|
+
const poolProcessorsOf = (cpus, hostCpus) => Math.max(1, Math.min(Math.round(cpus), Math.round(hostCpus)));
|
|
1955
|
+
const layoutAppliesSplit = (layout, split) => layout?.cpus != null && split !== null && layout.cpus.monitor === split.monitor.cpus && layout.cpus.executor === split.executor.cpus;
|
|
1956
|
+
const splitCoresOf = (split, cores) => {
|
|
1957
|
+
const processors = cores.reduce((total, size) => total + size, 0);
|
|
1958
|
+
const sizeAt = (index) => cores[Math.min(index, cores.length - 1)] ?? 1;
|
|
1959
|
+
const coresFrom = (first, cpus) => {
|
|
1960
|
+
const wanted = poolProcessorsOf(cpus, processors);
|
|
1961
|
+
let taken = sizeAt(first);
|
|
1962
|
+
let count = 1;
|
|
1963
|
+
while (taken + sizeAt(first + count) <= wanted) {
|
|
1964
|
+
taken += sizeAt(first + count);
|
|
1965
|
+
count += 1;
|
|
1966
|
+
}
|
|
1967
|
+
return count;
|
|
1968
|
+
};
|
|
1969
|
+
const executor = coresFrom(0, split.executor.cpus);
|
|
1970
|
+
const monitor = coresFrom(executor, split.monitor.cpus);
|
|
1971
|
+
return {
|
|
1972
|
+
cores: cores.length,
|
|
1973
|
+
monitor,
|
|
1974
|
+
executor,
|
|
1975
|
+
fits: executor + monitor < cores.length
|
|
1976
|
+
};
|
|
1977
|
+
};
|
|
1978
|
+
//#endregion
|
|
1979
|
+
//#region ../../packages/contracts/src/appearance.ts
|
|
1980
|
+
const colorSchema = z.string().regex(hexColorPattern);
|
|
1981
|
+
const logoMaxBytes = 524288;
|
|
1982
|
+
const logoHashSchema = z.string().regex(logoHashPattern);
|
|
1983
|
+
z.instanceof(Uint8Array).meta({ description: "The bytes of a PNG, JPEG or WebP image." });
|
|
1984
|
+
z.instanceof(Uint8Array).meta({ description: "The bytes of the WebP image." });
|
|
1985
|
+
z.object({ v: logoHashSchema.optional() });
|
|
1897
1986
|
//#endregion
|
|
1898
1987
|
//#region ../../packages/contracts/src/identity.ts
|
|
1899
1988
|
const emailSchema = z.email().max(254);
|
|
1900
1989
|
const passwordSchema = z.string().min(12).max(128);
|
|
1901
1990
|
const userStatusSchema = z.enum(["active", "disabled"]);
|
|
1902
1991
|
const organizationNameSchema = z.string().trim().min(2).max(80);
|
|
1903
|
-
const organizationSlugSchema = z.string().regex(slugPattern);
|
|
1992
|
+
const organizationSlugSchema = z.string().regex(slugPattern).refine((slug) => !uuidShapePattern.test(slug));
|
|
1904
1993
|
const organizationSchema = z.object({
|
|
1905
1994
|
id: idSchema,
|
|
1906
1995
|
name: z.string(),
|
|
1907
1996
|
slug: organizationSlugSchema,
|
|
1997
|
+
color: colorSchema,
|
|
1998
|
+
logoHash: logoHashSchema.nullable(),
|
|
1908
1999
|
createdAt: z.iso.datetime()
|
|
1909
2000
|
}).meta({ id: "Organization" });
|
|
1910
2001
|
z.object({ items: z.array(organizationSchema) }).meta({ id: "OrganizationList" });
|
|
@@ -1962,6 +2053,11 @@ z.object({
|
|
|
1962
2053
|
initialPassword: passwordSchema.optional()
|
|
1963
2054
|
}).optional()
|
|
1964
2055
|
}).meta({ id: "CreateOrganizationBody" });
|
|
2056
|
+
const updateOrganizationBodySchema = z.object({
|
|
2057
|
+
name: organizationNameSchema,
|
|
2058
|
+
slug: organizationSlugSchema,
|
|
2059
|
+
color: colorSchema
|
|
2060
|
+
}).meta({ id: "UpdateOrganizationBody" });
|
|
1965
2061
|
z.object({ temporaryPassword: z.string() }).meta({ id: "TemporaryPassword" });
|
|
1966
2062
|
z.object({ items: z.array(userSchema) }).meta({ id: "UserList" });
|
|
1967
2063
|
z.object({ id: idSchema });
|
|
@@ -2030,6 +2126,9 @@ const auditActionSchema = z.enum([
|
|
|
2030
2126
|
"platform.bootstrapped",
|
|
2031
2127
|
"organization.created",
|
|
2032
2128
|
"organization.renamed",
|
|
2129
|
+
"organization.updated",
|
|
2130
|
+
"organization.logo_set",
|
|
2131
|
+
"organization.logo_removed",
|
|
2033
2132
|
"user.created",
|
|
2034
2133
|
"user.role_changed",
|
|
2035
2134
|
"user.disabled",
|
|
@@ -2043,6 +2142,8 @@ const auditActionSchema = z.enum([
|
|
|
2043
2142
|
"token.revoked",
|
|
2044
2143
|
"project.created",
|
|
2045
2144
|
"project.updated",
|
|
2145
|
+
"project.logo_set",
|
|
2146
|
+
"project.logo_removed",
|
|
2046
2147
|
"project.member_added",
|
|
2047
2148
|
"project.member_role_changed",
|
|
2048
2149
|
"project.member_removed",
|
|
@@ -2151,7 +2252,6 @@ pageSchema(auditEntrySchema).meta({ id: "AuditPage" });
|
|
|
2151
2252
|
//#endregion
|
|
2152
2253
|
//#region ../../packages/contracts/src/projects.ts
|
|
2153
2254
|
const slugSchema = z.string().regex(slugPattern);
|
|
2154
|
-
const colorSchema = z.string().regex(hexColorPattern);
|
|
2155
2255
|
const projectSchema = z.object({
|
|
2156
2256
|
id: idSchema,
|
|
2157
2257
|
organizationId: idSchema,
|
|
@@ -2159,6 +2259,7 @@ const projectSchema = z.object({
|
|
|
2159
2259
|
slug: slugSchema,
|
|
2160
2260
|
description: z.string(),
|
|
2161
2261
|
color: colorSchema,
|
|
2262
|
+
logoHash: logoHashSchema.nullable(),
|
|
2162
2263
|
dockerAllowed: z.boolean(),
|
|
2163
2264
|
memberCount: z.int().nonnegative(),
|
|
2164
2265
|
createdAt: z.iso.datetime(),
|
|
@@ -2513,9 +2614,6 @@ const containerHealthSchema = z.enum([
|
|
|
2513
2614
|
]);
|
|
2514
2615
|
const maxAgentDescriptionLength = 2e3;
|
|
2515
2616
|
const maxAgentPromptLength = 64e3;
|
|
2516
|
-
const minAgentCpus = .25;
|
|
2517
|
-
const maxAgentMemoryMb = 16384;
|
|
2518
|
-
const defaultAgentMemoryMb = 4096;
|
|
2519
2617
|
const workspaceDirectorySchema = z.string().regex(/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/);
|
|
2520
2618
|
const timeZones = new Set(Intl.supportedValuesOf("timeZone"));
|
|
2521
2619
|
const timezoneSchema = z.string().trim().refine((value) => timeZones.has(value), { message: "expected an IANA time zone identifier" });
|
|
@@ -2526,11 +2624,13 @@ const agentDiskGbSchema = z.int().min(1).max(100);
|
|
|
2526
2624
|
const agentMachineSchema = z.object({
|
|
2527
2625
|
docker: z.boolean(),
|
|
2528
2626
|
browser: z.boolean(),
|
|
2529
|
-
cpus: z.number().min(minAgentCpus).max(8),
|
|
2530
|
-
memoryMb: z.int().min(512).max(maxAgentMemoryMb),
|
|
2531
2627
|
diskGb: agentDiskGbSchema
|
|
2532
|
-
})
|
|
2628
|
+
});
|
|
2533
2629
|
const agentMachineInputSchema = agentMachineSchema.extend({ diskGb: agentDiskGbSchema.optional() });
|
|
2630
|
+
const agentMachineDtoSchema = agentMachineSchema.extend({
|
|
2631
|
+
cpus: z.number().positive().optional().meta({ deprecated: true }),
|
|
2632
|
+
memoryMb: z.int().positive().optional().meta({ deprecated: true })
|
|
2633
|
+
}).meta({ id: "AgentMachine" });
|
|
2534
2634
|
const agentPromptsSchema = z.object({
|
|
2535
2635
|
monitor: agentPromptSchema,
|
|
2536
2636
|
executor: agentPromptSchema
|
|
@@ -2587,6 +2687,7 @@ const agentRuntimeSchema = z.object({
|
|
|
2587
2687
|
stopMode: agentStopModeSchema.nullable(),
|
|
2588
2688
|
stopDeadlineAt: z.iso.datetime().nullable(),
|
|
2589
2689
|
admission: agentAdmissionSchema.nullable(),
|
|
2690
|
+
admissionHold: admissionHoldSchema.nullable(),
|
|
2590
2691
|
poolLimit: poolLimitSchema.nullable()
|
|
2591
2692
|
}).meta({ id: "AgentRuntime" });
|
|
2592
2693
|
const agentListItemSchema = z.object({
|
|
@@ -2615,7 +2716,7 @@ agentListItemSchema.extend({
|
|
|
2615
2716
|
version: z.int().nonnegative(),
|
|
2616
2717
|
description: z.string(),
|
|
2617
2718
|
timezone: z.string(),
|
|
2618
|
-
machine:
|
|
2719
|
+
machine: agentMachineDtoSchema,
|
|
2619
2720
|
settings: kikimoraSettingsSchema,
|
|
2620
2721
|
effectiveSettings: workerSettingsSchema,
|
|
2621
2722
|
prompts: agentPromptsSchema,
|
|
@@ -2757,13 +2858,20 @@ z.object({
|
|
|
2757
2858
|
nodeId: idSchema
|
|
2758
2859
|
}).meta({ id: "RestoreAgentBody" });
|
|
2759
2860
|
z.object({ agentId: idSchema }).meta({ id: "RestoredAgent" });
|
|
2861
|
+
const agentBackupContentDigestSchema = z.string().regex(/^[0-9a-f]{64}$/u);
|
|
2760
2862
|
z.object({
|
|
2761
2863
|
agentId: idSchema,
|
|
2762
2864
|
trigger: z.enum(agentBackupTriggers),
|
|
2763
2865
|
kikimoraVersion: z.string().min(1).max(64),
|
|
2764
|
-
entries: z.coerce.number().int().nonnegative()
|
|
2866
|
+
entries: z.coerce.number().int().nonnegative(),
|
|
2867
|
+
contentDigest: agentBackupContentDigestSchema.optional()
|
|
2765
2868
|
});
|
|
2766
2869
|
z.object({ backupId: idSchema });
|
|
2870
|
+
z.object({
|
|
2871
|
+
agentId: idSchema,
|
|
2872
|
+
contentDigest: agentBackupContentDigestSchema
|
|
2873
|
+
});
|
|
2874
|
+
z.object({ backupId: idSchema.nullable() });
|
|
2767
2875
|
[
|
|
2768
2876
|
"application/vnd.oci.image.index.v1+json",
|
|
2769
2877
|
"application/vnd.oci.image.manifest.v1+json",
|
|
@@ -2771,21 +2879,12 @@ z.object({ backupId: idSchema });
|
|
|
2771
2879
|
"application/vnd.docker.distribution.manifest.v2+json"
|
|
2772
2880
|
].join(", ");
|
|
2773
2881
|
const pinnedPlatformImagePattern = /^[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?(?:\/[a-z0-9][a-z0-9._-]*)+@sha256:[0-9a-f]{64}$/;
|
|
2774
|
-
|
|
2775
|
-
//#region ../../packages/contracts/src/infrastructure.ts
|
|
2776
|
-
const volumeRoles = ["data", "agents"];
|
|
2777
|
-
const volumeRoleSchema = z.enum(volumeRoles);
|
|
2882
|
+
const volumeRoleSchema = z.enum(["data", "agents"]);
|
|
2778
2883
|
const filesystemNameSchema = z.enum([
|
|
2779
2884
|
"root",
|
|
2780
2885
|
"data",
|
|
2781
2886
|
"agents"
|
|
2782
2887
|
]);
|
|
2783
|
-
z.enum([
|
|
2784
|
-
"digitalOcean",
|
|
2785
|
-
"spaces",
|
|
2786
|
-
"ghcr",
|
|
2787
|
-
"backup"
|
|
2788
|
-
]);
|
|
2789
2888
|
const backupKindSchema = z.enum(["scheduled", "manual"]);
|
|
2790
2889
|
const backupStatusSchema = z.enum([
|
|
2791
2890
|
"running",
|
|
@@ -2829,65 +2928,42 @@ const sha256HexPattern = /^[0-9a-f]{64}$/;
|
|
|
2829
2928
|
const platformSecretPattern = /^[\x21-\x7e]+$/;
|
|
2830
2929
|
const instantSchema$1 = z.iso.datetime();
|
|
2831
2930
|
const byteCountSchema$1 = z.int().nonnegative();
|
|
2832
|
-
|
|
2931
|
+
z.string().trim().min(1).max(maxPlatformSecretLength).regex(platformSecretPattern, { message: "expected printable ASCII characters without spaces" });
|
|
2833
2932
|
const secretPresenceSchema = z.object({ present: z.boolean() }).meta({ id: "SecretPresence" });
|
|
2834
2933
|
const regionSchema = z.string().regex(regionSlugPattern);
|
|
2934
|
+
const bucketNameSchema = z.string().regex(bucketNamePattern);
|
|
2835
2935
|
const platformVolumeSchema = z.object({
|
|
2836
2936
|
role: volumeRoleSchema,
|
|
2837
2937
|
id: z.guid(),
|
|
2838
2938
|
name: z.string().regex(volumeNamePattern)
|
|
2839
2939
|
}).meta({ id: "PlatformVolume" });
|
|
2840
|
-
const
|
|
2841
|
-
const
|
|
2940
|
+
const countOfRole = (volumes, role) => volumes.filter((volume) => volume.role === role).length;
|
|
2941
|
+
const oneDataVolumeAndAtMostOneAgentsVolume = (volumes) => countOfRole(volumes, "data") === 1 && countOfRole(volumes, "agents") <= 1;
|
|
2942
|
+
const platformVolumesSchema = z.array(platformVolumeSchema).refine(oneDataVolumeAndAtMostOneAgentsVolume, { message: "expected one data volume and at most one agents volume" });
|
|
2842
2943
|
const isOrigin = (value) => {
|
|
2843
2944
|
if (!URL.canParse(value)) return false;
|
|
2844
2945
|
const url = new URL(value);
|
|
2845
2946
|
return url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
|
|
2846
2947
|
};
|
|
2847
2948
|
const spacesEndpointSchema = z.url({ protocol: /^https?$/ }).max(2e3).refine(isOrigin, { message: "expected an origin without a path, a query, a fragment or credentials" });
|
|
2848
|
-
const digitalOceanShape = {
|
|
2849
|
-
dropletId: z.int().positive(),
|
|
2850
|
-
region: regionSchema,
|
|
2851
|
-
reservedIp: z.ipv4(),
|
|
2852
|
-
volumes: platformVolumesSchema
|
|
2853
|
-
};
|
|
2854
|
-
const spacesShape = {
|
|
2855
|
-
endpoint: spacesEndpointSchema,
|
|
2856
|
-
region: regionSchema,
|
|
2857
|
-
bucket: z.string().regex(bucketNamePattern)
|
|
2858
|
-
};
|
|
2859
2949
|
const backupSettingsSchema = z.object({ retentionDays: z.int().min(1).max(maxBackupRetentionDays) }).meta({ id: "BackupSettings" });
|
|
2860
2950
|
z.object({
|
|
2861
|
-
version: z.int().nonnegative(),
|
|
2862
2951
|
instance: z.string(),
|
|
2863
2952
|
domain: z.string(),
|
|
2864
2953
|
digitalOcean: z.object({
|
|
2865
|
-
|
|
2866
|
-
|
|
2954
|
+
dropletId: z.int().positive(),
|
|
2955
|
+
region: regionSchema,
|
|
2956
|
+
reservedIp: z.ipv4(),
|
|
2957
|
+
volumes: platformVolumesSchema
|
|
2867
2958
|
}).nullable(),
|
|
2868
2959
|
spaces: z.object({
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2960
|
+
endpoint: spacesEndpointSchema,
|
|
2961
|
+
region: regionSchema,
|
|
2962
|
+
bucket: bucketNameSchema
|
|
2872
2963
|
}).nullable(),
|
|
2873
2964
|
ghcrToken: secretPresenceSchema,
|
|
2874
|
-
backup: backupSettingsSchema
|
|
2875
|
-
updatedAt: instantSchema$1
|
|
2965
|
+
backup: backupSettingsSchema
|
|
2876
2966
|
}).meta({ id: "PlatformSettings" });
|
|
2877
|
-
z.object({
|
|
2878
|
-
version: z.int().nonnegative(),
|
|
2879
|
-
digitalOcean: z.object({
|
|
2880
|
-
...digitalOceanShape,
|
|
2881
|
-
token: platformSecretSchema.nullable()
|
|
2882
|
-
}).nullable(),
|
|
2883
|
-
spaces: z.object({
|
|
2884
|
-
...spacesShape,
|
|
2885
|
-
accessKey: platformSecretSchema.nullable(),
|
|
2886
|
-
secretKey: platformSecretSchema.nullable()
|
|
2887
|
-
}).nullable(),
|
|
2888
|
-
ghcrToken: platformSecretSchema.nullable(),
|
|
2889
|
-
backup: backupSettingsSchema.nullable()
|
|
2890
|
-
}).meta({ id: "UpdatePlatformSettingsBody" });
|
|
2891
2967
|
const storageReadingSchema = z.discriminatedUnion("measured", [z.object({
|
|
2892
2968
|
measured: z.literal(true),
|
|
2893
2969
|
path: z.string(),
|
|
@@ -2935,19 +3011,6 @@ const nodeVolumesSchema = z.discriminatedUnion("source", [z.object({
|
|
|
2935
3011
|
source: z.literal("ok"),
|
|
2936
3012
|
items: z.array(nodeVolumeSchema)
|
|
2937
3013
|
}), unavailableBecause(["not_configured"])]).meta({ id: "NodeVolumes" });
|
|
2938
|
-
const dropletSizeSchema = z.object({
|
|
2939
|
-
slug: z.string(),
|
|
2940
|
-
vcpus: z.int().positive(),
|
|
2941
|
-
memoryMb: z.int().positive(),
|
|
2942
|
-
diskGb: z.int().positive(),
|
|
2943
|
-
priceMonthly: z.number().nonnegative(),
|
|
2944
|
-
command: z.string()
|
|
2945
|
-
}).meta({ id: "DropletSize" });
|
|
2946
|
-
const nodeSizesSchema = z.discriminatedUnion("source", [z.object({
|
|
2947
|
-
source: z.literal("ok"),
|
|
2948
|
-
items: z.array(dropletSizeSchema)
|
|
2949
|
-
}), unavailableBecause(providerUnavailableReasons)]).meta({ id: "NodeSizes" });
|
|
2950
|
-
const capacityForecastSchema = z.object({ byDisk: z.int().nonnegative().nullable() }).meta({ id: "CapacityForecast" });
|
|
2951
3014
|
z.object({
|
|
2952
3015
|
droplet: nodeDropletSchema,
|
|
2953
3016
|
volumes: nodeVolumesSchema,
|
|
@@ -2956,23 +3019,14 @@ z.object({
|
|
|
2956
3019
|
data: storageReadingSchema,
|
|
2957
3020
|
agents: storageReadingSchema
|
|
2958
3021
|
}),
|
|
2959
|
-
sizes: nodeSizesSchema,
|
|
2960
3022
|
platform: z.object({
|
|
2961
3023
|
version: z.string(),
|
|
2962
3024
|
instance: z.string(),
|
|
2963
3025
|
scriptsVersion: z.string(),
|
|
2964
3026
|
dockerVersion: z.string().nullable(),
|
|
2965
3027
|
dockerRootDir: z.string().nullable()
|
|
2966
|
-
})
|
|
2967
|
-
maintenance: z.object({
|
|
2968
|
-
quiescedAt: instantSchema$1.nullable(),
|
|
2969
|
-
pending: z.array(idSchema)
|
|
2970
|
-
}),
|
|
2971
|
-
capacity: capacityForecastSchema
|
|
3028
|
+
})
|
|
2972
3029
|
}).meta({ id: "PlatformNode" });
|
|
2973
|
-
z.object({ role: volumeRoleSchema });
|
|
2974
|
-
z.object({ sizeGb: z.int().positive() }).meta({ id: "ResizeVolumeBody" });
|
|
2975
|
-
z.object({ agents: z.int().nonnegative() }).meta({ id: "QuiesceResult" });
|
|
2976
3030
|
const requesterSchema = z.object({
|
|
2977
3031
|
userId: idSchema,
|
|
2978
3032
|
label: z.string().nullable()
|
|
@@ -3076,22 +3130,17 @@ const registryUnavailableSchema = z.object({
|
|
|
3076
3130
|
detail: z.string().nullable()
|
|
3077
3131
|
}).meta({ id: "RegistryUnavailable" });
|
|
3078
3132
|
const hostOperationsAvailabilities = ["enabled", "disabled"];
|
|
3079
|
-
const backupsAvailabilities = ["enabled", "unavailable"];
|
|
3080
3133
|
const installedImageSchema = z.string().regex(pinnedPlatformImagePattern).nullable();
|
|
3081
3134
|
z.object({
|
|
3082
3135
|
version: z.string(),
|
|
3136
|
+
environment: z.string().nullable(),
|
|
3083
3137
|
apiImage: installedImageSchema,
|
|
3084
3138
|
webImage: installedImageSchema,
|
|
3085
3139
|
nodeImage: installedImageSchema,
|
|
3086
3140
|
latest: z.string().nullable(),
|
|
3087
3141
|
latestError: registryUnavailableSchema.nullable(),
|
|
3088
|
-
versions: z.array(z.string()),
|
|
3089
|
-
upgradeable: z.boolean(),
|
|
3090
|
-
scriptsVersion: z.string(),
|
|
3091
|
-
expectedScriptsVersion: z.string(),
|
|
3092
3142
|
imagePrefix: z.string(),
|
|
3093
3143
|
hostOperations: z.enum(hostOperationsAvailabilities),
|
|
3094
|
-
backups: z.enum(backupsAvailabilities),
|
|
3095
3144
|
operations: z.array(hostOperationSchema)
|
|
3096
3145
|
}).meta({ id: "Platform" });
|
|
3097
3146
|
const keyVersionSchema = z.int().positive();
|
|
@@ -3114,13 +3163,15 @@ z.object({
|
|
|
3114
3163
|
versions: z.array(masterKeyVersionSchema),
|
|
3115
3164
|
rotationPending: z.boolean()
|
|
3116
3165
|
}).meta({ id: "MasterKey" });
|
|
3117
|
-
|
|
3118
|
-
|
|
3166
|
+
//#endregion
|
|
3167
|
+
//#region ../../packages/contracts/src/metrics.ts
|
|
3168
|
+
const metricsRanges = [
|
|
3119
3169
|
"1h",
|
|
3120
3170
|
"24h",
|
|
3121
3171
|
"7d",
|
|
3122
3172
|
"90d"
|
|
3123
|
-
]
|
|
3173
|
+
];
|
|
3174
|
+
const metricsRangeSchema = z.enum(metricsRanges);
|
|
3124
3175
|
const agentMetricsRanges = [
|
|
3125
3176
|
"1h",
|
|
3126
3177
|
"24h",
|
|
@@ -3174,18 +3225,13 @@ const agentUsageSchema = z.object({
|
|
|
3174
3225
|
blockReadBytes: byteCountSchema,
|
|
3175
3226
|
blockWriteBytes: byteCountSchema
|
|
3176
3227
|
}).meta({ id: "AgentUsage" });
|
|
3177
|
-
const nodeHostSeriesSchema = z.object({
|
|
3178
|
-
nodeId: idSchema,
|
|
3179
|
-
nodeName: z.string(),
|
|
3180
|
-
points: z.array(hostPointSchema)
|
|
3181
|
-
}).meta({ id: "NodeHostSeries" });
|
|
3182
3228
|
z.object({
|
|
3183
3229
|
resolution: metricsResolutionSchema,
|
|
3184
3230
|
from: instantSchema,
|
|
3185
3231
|
to: instantSchema,
|
|
3186
|
-
|
|
3232
|
+
points: z.array(hostPointSchema),
|
|
3187
3233
|
agents: z.array(agentUsageSchema)
|
|
3188
|
-
}).meta({ id: "
|
|
3234
|
+
}).meta({ id: "NodeMetrics" });
|
|
3189
3235
|
z.object({
|
|
3190
3236
|
resolution: metricsResolutionSchema,
|
|
3191
3237
|
from: instantSchema,
|
|
@@ -3195,24 +3241,6 @@ z.object({
|
|
|
3195
3241
|
}).meta({ id: "AgentMetrics" });
|
|
3196
3242
|
z.object({ range: metricsRangeSchema });
|
|
3197
3243
|
z.object({ range: agentMetricsRangeSchema });
|
|
3198
|
-
z.object({
|
|
3199
|
-
nodeId: idSchema,
|
|
3200
|
-
agentId: idSchema
|
|
3201
|
-
});
|
|
3202
|
-
z.object({ agentIds: z.array(idSchema).min(1).max(500) });
|
|
3203
|
-
z.object({ occupied: z.array(idSchema) });
|
|
3204
|
-
z.object({ offset: z.coerce.number().int().nonnegative() });
|
|
3205
|
-
z.object({ bytes: z.int().nonnegative() });
|
|
3206
|
-
z.object({
|
|
3207
|
-
bytes: z.int().nonnegative(),
|
|
3208
|
-
sha256: z.string().regex(/^[0-9a-f]{64}$/u)
|
|
3209
|
-
});
|
|
3210
|
-
z.object({
|
|
3211
|
-
bytes: z.int().nonnegative(),
|
|
3212
|
-
files: z.int().nonnegative(),
|
|
3213
|
-
memoryCheck: z.string()
|
|
3214
|
-
});
|
|
3215
|
-
z.object({ removed: z.boolean() });
|
|
3216
3244
|
const notificationKindSchema = z.enum([
|
|
3217
3245
|
"credential.verification_failed",
|
|
3218
3246
|
"credential.expiring",
|
|
@@ -3240,7 +3268,8 @@ const notificationTargetTypeSchema = z.enum([
|
|
|
3240
3268
|
"backup",
|
|
3241
3269
|
"queue",
|
|
3242
3270
|
"volume",
|
|
3243
|
-
"platform"
|
|
3271
|
+
"platform",
|
|
3272
|
+
"node"
|
|
3244
3273
|
]);
|
|
3245
3274
|
const hostOperationOutcomes = [
|
|
3246
3275
|
"succeeded",
|
|
@@ -3368,6 +3397,7 @@ const detailsOf$1 = (body) => {
|
|
|
3368
3397
|
const parsed = errorBodySchema.safeParse(body);
|
|
3369
3398
|
return parsed.success ? parsed.data.details ?? {} : {};
|
|
3370
3399
|
};
|
|
3400
|
+
const hintText = (hint, details) => typeof hint === "string" ? hint : hint(details);
|
|
3371
3401
|
//#endregion
|
|
3372
3402
|
//#region src/client/api.ts
|
|
3373
3403
|
const defaultTimeoutMs = 3e4;
|
|
@@ -3424,7 +3454,7 @@ const createApi = ({ origin, token, fetch = globalThis.fetch, timeoutMs = defaul
|
|
|
3424
3454
|
const refused = (action, { error, response }, hints) => {
|
|
3425
3455
|
const failure = new ApiError(response.status, codeOf(error), action, detailsOf$1(error));
|
|
3426
3456
|
const hint = failure.code === null ? void 0 : hints[failure.code];
|
|
3427
|
-
return hint === void 0 ? failure : new CliError(failure.message, hint, { cause: failure });
|
|
3457
|
+
return hint === void 0 ? failure : new CliError(failure.message, hintText(hint, failure.details), { cause: failure });
|
|
3428
3458
|
};
|
|
3429
3459
|
const dataOf = (action, answer, hints = {}) => {
|
|
3430
3460
|
if (answer.data === void 0) throw refused(action, answer, hints);
|
|
@@ -3796,7 +3826,7 @@ const createLoginCommand = (adaptersOf) => defineCommand({
|
|
|
3796
3826
|
}
|
|
3797
3827
|
});
|
|
3798
3828
|
const authorizeInBrowser = async ({ adapters, api, origin, prompt }) => {
|
|
3799
|
-
const started = dataOf("start the sign-in", await api.POST("/auth/cli/authorize", { body: { clientName: adapters.clientName() } }), { "infrastructure.not_configured": `${origin} has no administrator yet; the
|
|
3829
|
+
const started = dataOf("start the sign-in", await api.POST("/auth/cli/authorize", { body: { clientName: adapters.clientName() } }), { "infrastructure.not_configured": `${origin} has no administrator yet; the platform is seeded during pnpm platform bootstrap.` });
|
|
3800
3830
|
prompt.note([
|
|
3801
3831
|
`Code ${started.userCode}`,
|
|
3802
3832
|
`Address ${started.verificationUri}`,
|
|
@@ -4144,6 +4174,13 @@ const nodeDetailOf = async (api, { organizationId, nodeId }) => dataOf("describe
|
|
|
4144
4174
|
organizationId,
|
|
4145
4175
|
nodeId
|
|
4146
4176
|
} } }), nodeHints);
|
|
4177
|
+
const nodeMetricsOf = async (api, { organizationId, nodeId }, range) => dataOf("show what the node used", await api.GET("/organizations/{organizationId}/nodes/{nodeId}/metrics", { params: {
|
|
4178
|
+
path: {
|
|
4179
|
+
organizationId,
|
|
4180
|
+
nodeId
|
|
4181
|
+
},
|
|
4182
|
+
query: { range }
|
|
4183
|
+
} }), nodeHints);
|
|
4147
4184
|
const createdNode = async (api, organizationId, body) => {
|
|
4148
4185
|
validated(createNodeBodySchema, body, "a node");
|
|
4149
4186
|
return providerDataOf("create the node", await api.POST("/organizations/{organizationId}/nodes", {
|
|
@@ -4243,10 +4280,15 @@ const runOf = async (adaptersOf, { settings, context }) => {
|
|
|
4243
4280
|
//#region src/commands/agents/shared.ts
|
|
4244
4281
|
const agentOption = { project: { type: "string" } };
|
|
4245
4282
|
const agentUsage = [["--project <name>", "the project of the agent, when the agent is named alone"]];
|
|
4283
|
+
const actionsAfterPause = /* @__PURE__ */ new Set([
|
|
4284
|
+
"update",
|
|
4285
|
+
"restart",
|
|
4286
|
+
"resume"
|
|
4287
|
+
]);
|
|
4246
4288
|
const agentHints = {
|
|
4247
4289
|
"agent.not_found": "kcloud agents list --project <name> shows the agents.",
|
|
4248
4290
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
4249
|
-
"agent.invalid_transition": "kcloud agents show <agent> lists the actions it takes now.",
|
|
4291
|
+
"agent.invalid_transition": ({ state, action }) => state === "pausing" && actionsAfterPause.has(String(action)) ? "The agent finishes its current session before it pauses; the action goes through once kcloud agents show <agent> says paused." : "kcloud agents show <agent> lists the actions it takes now.",
|
|
4250
4292
|
"agent.busy": "The agent is working on another action; try again in a moment.",
|
|
4251
4293
|
"concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
|
|
4252
4294
|
"agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
|
|
@@ -4474,12 +4516,56 @@ const enforcementText = ({ state, reason }) => {
|
|
|
4474
4516
|
case "unsupported": return `The node holds no pool to a limit: ${reason === null ? "it cannot" : unsupportedReasons[reason] ?? reason}.`;
|
|
4475
4517
|
}
|
|
4476
4518
|
};
|
|
4477
|
-
const
|
|
4519
|
+
const agentsSliceReasons = {
|
|
4520
|
+
cgroup_driver: "Docker on the host does not use the systemd cgroup driver",
|
|
4521
|
+
cgroup_driver_unread: "the node has not read the cgroup driver of Docker yet",
|
|
4522
|
+
host_unread: "the node has not read its host yet",
|
|
4523
|
+
configure_failed: "systemd did not take the limits",
|
|
4524
|
+
pending: "the node has not applied the limit yet",
|
|
4525
|
+
platform: "the node runs no pool service"
|
|
4526
|
+
};
|
|
4527
|
+
const agentsSliceText = ({ state, reason }) => {
|
|
4528
|
+
switch (state) {
|
|
4529
|
+
case "enforced": return "The node holds its agents and their sessions together to the host minus its reserve.";
|
|
4530
|
+
case "unsplit": return "The node has no split, so its agents outside their sessions have no limit.";
|
|
4531
|
+
case "unsupported": return `The node holds its agents outside their sessions to no limit: ${reason === null ? "it cannot" : agentsSliceReasons[reason] ?? reason}.`;
|
|
4532
|
+
}
|
|
4533
|
+
};
|
|
4534
|
+
const refusedText = ({ count, pool, lastAt, reason }) => `The session starter refused ${count === 1 ? "1 session" : `${count} sessions`} of the ${pool} pool in the last hour, the last at ${lastAt}: ${reason}`;
|
|
4535
|
+
const layoutTexts = (layout, split) => [...layout.topologyError === null ? [] : [`The node did not read its processor cores, so each logical processor counts as a core and a pool can share a physical core: ${layout.topologyError}`], ...layout.clamped && layoutAppliesSplit(layout, split) ? ["The split does not fit the cores of the node, so its pools run on fewer cores than the split asks for; kcloud nodes split <node> shows the cores each pool takes."] : []];
|
|
4536
|
+
const memoryOf$1 = (pool) => {
|
|
4478
4537
|
if (pool.usage === null) return "-";
|
|
4479
4538
|
const { memoryBytes, memoryMaxBytes } = pool.usage;
|
|
4480
4539
|
return memoryMaxBytes === null ? bytesOf(memoryBytes) : `${bytesOf(memoryBytes)} of ${bytesOf(memoryMaxBytes)}`;
|
|
4481
4540
|
};
|
|
4482
4541
|
const roles = ["monitor", "executor"];
|
|
4542
|
+
const workOf = (role, { taskId, taskTitle, readable }) => {
|
|
4543
|
+
if (readable === false) return "another project";
|
|
4544
|
+
if (taskId === null) return role === "monitor" ? "monitor cycle" : "-";
|
|
4545
|
+
return taskTitle == null ? taskId : `${taskId} ${taskTitle}`;
|
|
4546
|
+
};
|
|
4547
|
+
const holdersOf = (pools) => {
|
|
4548
|
+
if (roles.every((role) => pools[role].holders == null)) return null;
|
|
4549
|
+
const held = roles.flatMap((role) => (pools[role].holders ?? []).map((holder) => [
|
|
4550
|
+
role,
|
|
4551
|
+
holder.projectName ?? "-",
|
|
4552
|
+
holder.agentName ?? holder.agentId,
|
|
4553
|
+
workOf(role, holder),
|
|
4554
|
+
holder.since ?? "-",
|
|
4555
|
+
holder.pid == null ? "-" : String(holder.pid)
|
|
4556
|
+
]));
|
|
4557
|
+
return held.length === 0 ? "No session holds a slot." : renderTable([
|
|
4558
|
+
{ header: "HOLDS" },
|
|
4559
|
+
{ header: "PROJECT" },
|
|
4560
|
+
{ header: "AGENT" },
|
|
4561
|
+
{ header: "WORK" },
|
|
4562
|
+
{ header: "SINCE" },
|
|
4563
|
+
{
|
|
4564
|
+
header: "PID",
|
|
4565
|
+
align: "right"
|
|
4566
|
+
}
|
|
4567
|
+
], held);
|
|
4568
|
+
};
|
|
4483
4569
|
const queueOf = (pools) => {
|
|
4484
4570
|
const waiting = roles.flatMap((role) => pools[role].waiting.map((entry) => [
|
|
4485
4571
|
role,
|
|
@@ -4487,7 +4573,7 @@ const queueOf = (pools) => {
|
|
|
4487
4573
|
entry.projectName ?? "-",
|
|
4488
4574
|
entry.agentName ?? entry.agentId,
|
|
4489
4575
|
entry.since,
|
|
4490
|
-
entry
|
|
4576
|
+
workOf(role, entry)
|
|
4491
4577
|
]));
|
|
4492
4578
|
return waiting.length === 0 ? "No session waits for a slot." : renderTable([
|
|
4493
4579
|
{ header: "WAITS FOR" },
|
|
@@ -4498,37 +4584,44 @@ const queueOf = (pools) => {
|
|
|
4498
4584
|
{ header: "PROJECT" },
|
|
4499
4585
|
{ header: "AGENT" },
|
|
4500
4586
|
{ header: "SINCE" },
|
|
4501
|
-
{ header: "
|
|
4587
|
+
{ header: "WORK" }
|
|
4502
4588
|
], waiting);
|
|
4503
4589
|
};
|
|
4504
|
-
const poolsText = (pools) =>
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4590
|
+
const poolsText = (pools, split) => {
|
|
4591
|
+
const holders = holdersOf(pools);
|
|
4592
|
+
return [
|
|
4593
|
+
enforcementText(pools.enforcement),
|
|
4594
|
+
...pools.agentsSlice == null ? [] : [agentsSliceText(pools.agentsSlice)],
|
|
4595
|
+
...pools.refused == null ? [] : [refusedText(pools.refused)],
|
|
4596
|
+
...pools.layout == null ? [] : layoutTexts(pools.layout, split),
|
|
4597
|
+
renderTable([
|
|
4598
|
+
{ header: "POOL" },
|
|
4599
|
+
{ header: "SLOTS IN USE" },
|
|
4600
|
+
{
|
|
4601
|
+
header: "CPU IN USE",
|
|
4602
|
+
align: "right"
|
|
4603
|
+
},
|
|
4604
|
+
{ header: "MEMORY IN USE" },
|
|
4605
|
+
{
|
|
4606
|
+
header: "MEMORY KILLS",
|
|
4607
|
+
align: "right"
|
|
4608
|
+
}
|
|
4609
|
+
], roles.map((role) => {
|
|
4610
|
+
const pool = pools[role];
|
|
4611
|
+
const cpus = pool.usage?.cpus ?? null;
|
|
4612
|
+
return [
|
|
4613
|
+
role,
|
|
4614
|
+
`${pool.inUse} of ${pool.slots}`,
|
|
4615
|
+
cpus === null ? "-" : cpus.toFixed(2),
|
|
4616
|
+
memoryOf$1(pool),
|
|
4617
|
+
pool.usage === null ? "-" : String(pool.usage.memoryKills)
|
|
4618
|
+
];
|
|
4619
|
+
})),
|
|
4620
|
+
...holders === null ? [] : ["", holders],
|
|
4621
|
+
"",
|
|
4622
|
+
queueOf(pools)
|
|
4623
|
+
].join("\n");
|
|
4624
|
+
};
|
|
4532
4625
|
//#endregion
|
|
4533
4626
|
//#region src/commands/agents/plan.ts
|
|
4534
4627
|
const agentPlanOf = (agent) => ({
|
|
@@ -4551,8 +4644,36 @@ const agentPlanOf = (agent) => ({
|
|
|
4551
4644
|
prompts: agent.prompts,
|
|
4552
4645
|
settings: agent.settings,
|
|
4553
4646
|
timezone: agent.timezone,
|
|
4554
|
-
machine:
|
|
4647
|
+
machine: {
|
|
4648
|
+
docker: agent.machine.docker,
|
|
4649
|
+
browser: agent.machine.browser,
|
|
4650
|
+
diskGb: agent.machine.diskGb
|
|
4651
|
+
}
|
|
4555
4652
|
});
|
|
4653
|
+
const machineLimitKeys = /* @__PURE__ */ new Set(["cpus", "memoryMb"]);
|
|
4654
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4655
|
+
const warnIgnored = ({ context }, keys) => {
|
|
4656
|
+
const named = keys.map((key) => `machine.${key}`).join(" and ");
|
|
4657
|
+
context.terminal.error(context.terminal.errorStyles.warning(`warning ${named} ${keys.length === 1 ? "is" : "are"} ignored: an agent has no limits of its own, and the pools of its node limit its sessions.`));
|
|
4658
|
+
};
|
|
4659
|
+
const machineWithoutLimits = (run, machine) => {
|
|
4660
|
+
if (!isRecord(machine)) return machine;
|
|
4661
|
+
const ignored = Object.keys(machine).filter((key) => machineLimitKeys.has(key));
|
|
4662
|
+
if (ignored.length === 0) return machine;
|
|
4663
|
+
warnIgnored(run, ignored);
|
|
4664
|
+
return Object.fromEntries(Object.entries(machine).filter(([key]) => !machineLimitKeys.has(key)));
|
|
4665
|
+
};
|
|
4666
|
+
const agentFileWithoutLimits = (run, document) => isRecord(document) && "machine" in document ? {
|
|
4667
|
+
...document,
|
|
4668
|
+
machine: machineWithoutLimits(run, document.machine)
|
|
4669
|
+
} : document;
|
|
4670
|
+
const agentChangeWithoutLimits = (run, document) => isRecord(document) && isRecord(document.sections) && "machine" in document.sections ? {
|
|
4671
|
+
...document,
|
|
4672
|
+
sections: {
|
|
4673
|
+
...document.sections,
|
|
4674
|
+
machine: machineWithoutLimits(run, document.sections.machine)
|
|
4675
|
+
}
|
|
4676
|
+
} : document;
|
|
4556
4677
|
//#endregion
|
|
4557
4678
|
//#region src/commands/agents/config.ts
|
|
4558
4679
|
const agentsOf = async (scope, project) => {
|
|
@@ -4663,7 +4784,7 @@ const configCommands = (adaptersOf) => [
|
|
|
4663
4784
|
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
4664
4785
|
const file = invocation.values.file;
|
|
4665
4786
|
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.");
|
|
4666
|
-
const agent = await changedAgent(run.api, agentId, await documentOf(run, file));
|
|
4787
|
+
const agent = await changedAgent(run.api, agentId, agentChangeWithoutLimits(run, await documentOf(run, file)));
|
|
4667
4788
|
return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs kcloud agents update to take the change.` : `Changed ${changed.name}.`);
|
|
4668
4789
|
}
|
|
4669
4790
|
}),
|
|
@@ -4708,6 +4829,15 @@ const admissionRowsOf = (agent) => {
|
|
|
4708
4829
|
const { admission } = agent.runtime;
|
|
4709
4830
|
return admission === null ? [] : [["monitor slot", slotOf(admission.monitor)], ["executor slot", slotOf(admission.executor)]];
|
|
4710
4831
|
};
|
|
4832
|
+
const admissionHoldRowsOf = ({ runtime }) => {
|
|
4833
|
+
const hold = runtime.admissionHold;
|
|
4834
|
+
if (hold == null) return [];
|
|
4835
|
+
if (hold.updating) return [["admissions", `held while the node updates to ${hold.target ?? "-"}`]];
|
|
4836
|
+
const all = hold.pools.includes("monitor");
|
|
4837
|
+
const noun = all ? "session" : "executor session";
|
|
4838
|
+
const waits = `${String(hold.sessions)} ${noun}${hold.sessions === 1 ? "" : "s"}`;
|
|
4839
|
+
return [["admissions", `${all ? "held" : "executor held"} for the node update to ${hold.target ?? "-"}, waiting for ${waits}`]];
|
|
4840
|
+
};
|
|
4711
4841
|
const poolLimitRowsOf = ({ runtime }) => runtime.poolLimit === null ? [] : [["pool limit", poolLimitText(runtime.poolLimit)]];
|
|
4712
4842
|
const detailsOf = (agent) => [
|
|
4713
4843
|
renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
@@ -4720,12 +4850,13 @@ const detailsOf = (agent) => [
|
|
|
4720
4850
|
["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
|
|
4721
4851
|
["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
|
|
4722
4852
|
["timezone", agent.timezone],
|
|
4723
|
-
["
|
|
4853
|
+
["disk", `${String(agent.machine.diskGb)} GB`],
|
|
4724
4854
|
["docker", agent.machine.docker ? "yes" : "no"],
|
|
4725
4855
|
["browser", agent.machine.browser ? "yes" : "no"],
|
|
4726
4856
|
["container", agent.runtime.containerId ?? "none"],
|
|
4727
4857
|
["health", agent.runtime.health],
|
|
4728
4858
|
...admissionRowsOf(agent),
|
|
4859
|
+
...admissionHoldRowsOf(agent),
|
|
4729
4860
|
...poolLimitRowsOf(agent),
|
|
4730
4861
|
["pending", agent.restartRequired ? agent.restartReasons.join(", ") : "-"],
|
|
4731
4862
|
["actions", agent.availableActions.join(", ")],
|
|
@@ -4914,15 +5045,13 @@ const askedPlan = async (run, projectId, values, required) => {
|
|
|
4914
5045
|
timezone: values.timezone ?? localTimezone(),
|
|
4915
5046
|
machine: {
|
|
4916
5047
|
docker: values.docker,
|
|
4917
|
-
browser: values.browser
|
|
4918
|
-
cpus: 2,
|
|
4919
|
-
memoryMb: defaultAgentMemoryMb
|
|
5048
|
+
browser: values.browser
|
|
4920
5049
|
}
|
|
4921
5050
|
};
|
|
4922
5051
|
};
|
|
4923
5052
|
const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
|
|
4924
5053
|
const fileOf = async (run, projectId, { file, node }, required) => {
|
|
4925
|
-
const document = await documentOf(run, file);
|
|
5054
|
+
const document = agentFileWithoutLimits(run, await documentOf(run, file));
|
|
4926
5055
|
const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
|
|
4927
5056
|
...document,
|
|
4928
5057
|
nodeId: await placedNodeId(run, {
|
|
@@ -5061,30 +5190,29 @@ const lifecycleCommands = (adaptersOf) => agentActions.map((action) => defineCom
|
|
|
5061
5190
|
}));
|
|
5062
5191
|
//#endregion
|
|
5063
5192
|
//#region src/client/pages.ts
|
|
5064
|
-
const windowOf = (limit, all, fallback = 50) => {
|
|
5193
|
+
const windowOf = (limit, all, fallback = 50, largest = 200) => {
|
|
5065
5194
|
if (limit === void 0) return {
|
|
5066
5195
|
limit: fallback,
|
|
5067
5196
|
all
|
|
5068
5197
|
};
|
|
5069
5198
|
const size = Number(limit);
|
|
5070
|
-
if (!/^[0-9]+$/u.test(limit) || size < 1 || size >
|
|
5199
|
+
if (!/^[0-9]+$/u.test(limit) || size < 1 || size > largest) throw new UsageError(`--limit ${limit} is not a whole number from 1 to ${largest}.`);
|
|
5071
5200
|
return {
|
|
5072
5201
|
limit: size,
|
|
5073
5202
|
all
|
|
5074
5203
|
};
|
|
5075
5204
|
};
|
|
5076
|
-
const collect = async (page, { limit, all }) => {
|
|
5077
|
-
const first = await page(null, Math.min(limit,
|
|
5205
|
+
const collect = async (page, { limit, all }, largest = 200) => {
|
|
5206
|
+
const first = await page(null, Math.min(limit, largest));
|
|
5078
5207
|
if (!all) return first;
|
|
5079
5208
|
const items = [...first.items];
|
|
5080
|
-
let
|
|
5081
|
-
while (
|
|
5082
|
-
|
|
5083
|
-
items.push(...
|
|
5084
|
-
cursor = next.nextCursor;
|
|
5209
|
+
let last = first;
|
|
5210
|
+
while (last.nextCursor !== null) {
|
|
5211
|
+
last = await page(last.nextCursor, largest);
|
|
5212
|
+
items.push(...last.items);
|
|
5085
5213
|
}
|
|
5086
5214
|
return {
|
|
5087
|
-
...
|
|
5215
|
+
...last,
|
|
5088
5216
|
items,
|
|
5089
5217
|
nextCursor: null
|
|
5090
5218
|
};
|
|
@@ -5131,6 +5259,43 @@ const transcriptOf = async (api, agentId, sessionId, all) => collect(async (curs
|
|
|
5131
5259
|
limit: 200,
|
|
5132
5260
|
all
|
|
5133
5261
|
});
|
|
5262
|
+
const memoryOf = (api, agentId, { query, outcome }, window) => collect(async (cursor, limit) => dataOf("search what the agent remembers", await api.GET("/agents/{id}/memory", { params: {
|
|
5263
|
+
path: { id: agentId },
|
|
5264
|
+
query: {
|
|
5265
|
+
limit,
|
|
5266
|
+
...query === void 0 ? {} : { q: query },
|
|
5267
|
+
...outcome === void 0 ? {} : { outcome },
|
|
5268
|
+
...cursor === null ? {} : { cursor }
|
|
5269
|
+
}
|
|
5270
|
+
} }), agentHints), window, 100);
|
|
5271
|
+
const memoryReasons = {
|
|
5272
|
+
stopped: "the agent is not running",
|
|
5273
|
+
unreachable: "the agent did not answer on its control socket",
|
|
5274
|
+
timeout: "the agent did not answer in time"
|
|
5275
|
+
};
|
|
5276
|
+
const unreadReasonOf = (page) => page.staleReason === null ? "the agent did not answer" : memoryReasons[page.staleReason];
|
|
5277
|
+
const memoryText = (page) => {
|
|
5278
|
+
const unread = page.source === "unavailable";
|
|
5279
|
+
if (unread && page.items.length === 0) return `The memory cannot be read: ${unreadReasonOf(page)}.`;
|
|
5280
|
+
if (page.items.length === 0) return "The agent remembers nothing that matches.";
|
|
5281
|
+
return [
|
|
5282
|
+
renderTable([
|
|
5283
|
+
{ header: "WHEN" },
|
|
5284
|
+
{ header: "OUTCOME" },
|
|
5285
|
+
{ header: "TASK" },
|
|
5286
|
+
{ header: "TITLE" },
|
|
5287
|
+
{ header: "HEADLINE" }
|
|
5288
|
+
], page.items.map((summary) => [
|
|
5289
|
+
summary.createdAt,
|
|
5290
|
+
summary.ok ? "ok" : "failed",
|
|
5291
|
+
summary.taskId,
|
|
5292
|
+
summary.title,
|
|
5293
|
+
summary.headline
|
|
5294
|
+
])),
|
|
5295
|
+
...unread ? ["", `The rest of the memory cannot be read: ${unreadReasonOf(page)}.`] : [],
|
|
5296
|
+
...page.truncated ? ["", `The agent returns at most 100 summaries, so the list may miss older ones.`] : []
|
|
5297
|
+
].join("\n");
|
|
5298
|
+
};
|
|
5134
5299
|
const observabilityCommands = (adaptersOf) => [
|
|
5135
5300
|
defineCommand({
|
|
5136
5301
|
name: "sessions",
|
|
@@ -5206,7 +5371,9 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
5206
5371
|
const run = await runOf(adaptersOf, invocation);
|
|
5207
5372
|
const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
|
|
5208
5373
|
const page = await transcriptOf(run.api, agentId, invocation.positionals[1] ?? "", invocation.values.all);
|
|
5209
|
-
|
|
5374
|
+
const code = printed(run, page.items, (entries) => entries.length === 0 ? "The session left no transcript." : entries.map((entry) => JSON.stringify(entry)).join("\n"));
|
|
5375
|
+
if (page.nextCursor !== null && !run.settings.json) run.context.terminal.error("More entries follow; --all prints the whole session.");
|
|
5376
|
+
return code;
|
|
5210
5377
|
}
|
|
5211
5378
|
}),
|
|
5212
5379
|
defineCommand({
|
|
@@ -5342,44 +5509,35 @@ const observabilityCommands = (adaptersOf) => [
|
|
|
5342
5509
|
defineCommand({
|
|
5343
5510
|
name: "memory",
|
|
5344
5511
|
summary: "Search what an agent remembers of its tasks.",
|
|
5345
|
-
usage: "agents memory <agent> [--project <name>] [--query <text>] [--limit <n>]",
|
|
5512
|
+
usage: "agents memory <agent> [--project <name>] [--query <text>] [--outcome <outcome>] [--limit <n>] [--all]",
|
|
5346
5513
|
optionsUsage: [
|
|
5347
5514
|
...agentUsage,
|
|
5348
5515
|
["--query <text>", "only the summaries that match this text"],
|
|
5349
|
-
["--
|
|
5516
|
+
["--outcome <outcome>", `only the summaries of one outcome: ${memoryOutcomes.join(", ")}`],
|
|
5517
|
+
["--limit <n>", `how many summaries (default: 20)`],
|
|
5518
|
+
allUsage
|
|
5350
5519
|
],
|
|
5351
5520
|
options: {
|
|
5352
5521
|
...agentOption,
|
|
5522
|
+
...pageOptions,
|
|
5353
5523
|
query: { type: "string" },
|
|
5354
|
-
|
|
5524
|
+
outcome: { type: "string" }
|
|
5355
5525
|
},
|
|
5356
5526
|
positionals: {
|
|
5357
5527
|
min: 1,
|
|
5358
5528
|
max: 1
|
|
5359
5529
|
},
|
|
5360
5530
|
run: async (invocation) => {
|
|
5531
|
+
const { values } = invocation;
|
|
5361
5532
|
const run = await runOf(adaptersOf, invocation);
|
|
5362
|
-
const agentId = await agentFrom(run, invocation.positionals[0],
|
|
5363
|
-
const
|
|
5364
|
-
|
|
5365
|
-
const
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
}
|
|
5371
|
-
} }), agentHints);
|
|
5372
|
-
return printed(run, listed, (shown) => shown.items.length === 0 ? "The agent remembers nothing that matches." : renderTable([
|
|
5373
|
-
{ header: "WHEN" },
|
|
5374
|
-
{ header: "OUTCOME" },
|
|
5375
|
-
{ header: "TITLE" },
|
|
5376
|
-
{ header: "HEADLINE" }
|
|
5377
|
-
], shown.items.map((summary) => [
|
|
5378
|
-
summary.createdAt,
|
|
5379
|
-
summary.ok ? "ok" : "failed",
|
|
5380
|
-
summary.title,
|
|
5381
|
-
summary.headline
|
|
5382
|
-
])));
|
|
5533
|
+
const agentId = await agentFrom(run, invocation.positionals[0], values.project);
|
|
5534
|
+
const outcome = memoryOutcomes.find((candidate) => candidate === values.outcome);
|
|
5535
|
+
if (values.outcome !== void 0 && outcome === void 0) throw new UsageError(`--outcome ${values.outcome} is not an outcome of a summary.`, `The outcomes are ${memoryOutcomes.join(", ")}.`);
|
|
5536
|
+
const page = await memoryOf(run.api, agentId, {
|
|
5537
|
+
query: values.query,
|
|
5538
|
+
outcome
|
|
5539
|
+
}, windowOf(values.limit, values.all, 20, 100));
|
|
5540
|
+
return printed(run, page, memoryText);
|
|
5383
5541
|
}
|
|
5384
5542
|
})
|
|
5385
5543
|
];
|
|
@@ -5953,25 +6111,59 @@ const secretUsage = [
|
|
|
5953
6111
|
//#region src/commands/credentials.ts
|
|
5954
6112
|
const projectOption$2 = { project: { type: "string" } };
|
|
5955
6113
|
const projectUsage$2 = [["--project <name>", "the project the credential belongs to, by slug, name or id"]];
|
|
6114
|
+
const usageUnavailable = "Only a Claude Code OAuth token reports the usage of a subscription.";
|
|
5956
6115
|
const credentialHints = {
|
|
5957
6116
|
"project.not_found": "kcloud projects list shows the projects you are a member of.",
|
|
5958
6117
|
"credential.not_found": "kcloud credentials list --project <name> shows them.",
|
|
5959
6118
|
"credential.label_taken": "Another credential of the project already has that label.",
|
|
5960
|
-
"credential.in_use": "Point the agents at another credential first."
|
|
6119
|
+
"credential.in_use": "Point the agents at another credential first.",
|
|
6120
|
+
"credential.usage_unavailable": usageUnavailable
|
|
6121
|
+
};
|
|
6122
|
+
const percent = 100;
|
|
6123
|
+
const notApplicable = "-";
|
|
6124
|
+
const usedOf = (window) => window === null ? notApplicable : `${String(Math.round(window.utilization * percent))}%`;
|
|
6125
|
+
const shareOf = (window) => window?.status === "rejected" ? "limit" : usedOf(window);
|
|
6126
|
+
const usageSummaryOf = ({ kind, usage }) => {
|
|
6127
|
+
if (kind !== "claude_oauth_token") return notApplicable;
|
|
6128
|
+
if (usage === null) return "no data";
|
|
6129
|
+
const summary = `5h ${shareOf(usage.fiveHour)} 7d ${shareOf(usage.sevenDay)}`;
|
|
6130
|
+
return usage.stale ? `${summary} (stale)` : summary;
|
|
6131
|
+
};
|
|
6132
|
+
const usageReportOf = (credential) => {
|
|
6133
|
+
const { usage } = credential;
|
|
6134
|
+
if (usage === null) return `No usage of ${credential.label} was read yet.`;
|
|
6135
|
+
const windows = [["5h", usage.fiveHour], ["7d", usage.sevenDay]];
|
|
6136
|
+
return `${renderTable([
|
|
6137
|
+
{ header: "WINDOW" },
|
|
6138
|
+
{ header: "USED" },
|
|
6139
|
+
{ header: "STATUS" },
|
|
6140
|
+
{ header: "RESETS" }
|
|
6141
|
+
], windows.map(([name, window]) => [
|
|
6142
|
+
name,
|
|
6143
|
+
usedOf(window),
|
|
6144
|
+
window?.status ?? notApplicable,
|
|
6145
|
+
window?.resetsAt ?? notApplicable
|
|
6146
|
+
]))}\n${usage.stale ? `Read at ${usage.readAt}; the latest reading failed.` : `Read at ${usage.readAt}.`}`;
|
|
6147
|
+
};
|
|
6148
|
+
const isoDateLength = 10;
|
|
6149
|
+
const expiryOf = ({ expiresAt, expiresAtComputed }) => {
|
|
6150
|
+
if (expiresAt === null) return "never";
|
|
6151
|
+
return expiresAtComputed ? `about ${expiresAt.slice(0, isoDateLength)}` : expiresAt;
|
|
5961
6152
|
};
|
|
5962
6153
|
const projectFrom$2 = async (run, given) => {
|
|
5963
6154
|
if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
|
|
5964
6155
|
return resolveProjectId(run, given);
|
|
5965
6156
|
};
|
|
5966
6157
|
const credentialsOf = async (api, projectId) => dataOf("list the credentials of the project", await api.GET("/projects/{id}/credentials", { params: { path: { id: projectId } } }), credentialHints).items;
|
|
5967
|
-
const
|
|
6158
|
+
const resolveCredential = async (api, projectId, reference) => {
|
|
5968
6159
|
const credentials = await credentialsOf(api, projectId);
|
|
5969
6160
|
const found = credentials.filter((credential) => credential.id === reference || credential.label === reference);
|
|
5970
6161
|
const [first] = found;
|
|
5971
6162
|
if (first === void 0) throw new CliError(`The project has no credential ${reference}.`, credentials.length === 0 ? "kcloud credentials add makes one." : `Its credentials are ${credentials.map(({ label }) => label).join(", ")}.`);
|
|
5972
6163
|
if (found.length > 1) throw new CliError(`${reference} names ${found.length} credentials of the project.`, "Name it by its id instead.");
|
|
5973
|
-
return first
|
|
6164
|
+
return first;
|
|
5974
6165
|
};
|
|
6166
|
+
const resolveCredentialId = async (api, projectId, reference) => (await resolveCredential(api, projectId, reference)).id;
|
|
5975
6167
|
const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
5976
6168
|
name: "credentials",
|
|
5977
6169
|
summary: "The Claude and GitHub credentials of a project.",
|
|
@@ -5997,13 +6189,15 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
5997
6189
|
{ header: "FINGERPRINT" },
|
|
5998
6190
|
{ header: "VERIFIED" },
|
|
5999
6191
|
{ header: "EXPIRES" },
|
|
6192
|
+
{ header: "USAGE" },
|
|
6000
6193
|
{ header: "ID" }
|
|
6001
6194
|
], credentials.map((credential) => [
|
|
6002
6195
|
credential.label,
|
|
6003
6196
|
credential.kind,
|
|
6004
6197
|
credential.fingerprint,
|
|
6005
6198
|
credential.verification,
|
|
6006
|
-
credential
|
|
6199
|
+
expiryOf(credential),
|
|
6200
|
+
usageSummaryOf(credential),
|
|
6007
6201
|
credential.id
|
|
6008
6202
|
])));
|
|
6009
6203
|
}
|
|
@@ -6149,6 +6343,34 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
|
|
|
6149
6343
|
return said(run, `The instance is verifying ${credentialId}; kcloud credentials list shows the answer.`);
|
|
6150
6344
|
}
|
|
6151
6345
|
}),
|
|
6346
|
+
defineCommand({
|
|
6347
|
+
name: "usage",
|
|
6348
|
+
summary: "Show the usage of the Claude subscription behind a Claude Code OAuth token.",
|
|
6349
|
+
usage: "credentials usage <credential> --project <name> [--refresh]",
|
|
6350
|
+
optionsUsage: [...projectUsage$2, ["--refresh", "read it from Anthropic now; within 30 seconds the last reading"]],
|
|
6351
|
+
options: {
|
|
6352
|
+
...projectOption$2,
|
|
6353
|
+
refresh: {
|
|
6354
|
+
type: "boolean",
|
|
6355
|
+
default: false
|
|
6356
|
+
}
|
|
6357
|
+
},
|
|
6358
|
+
positionals: {
|
|
6359
|
+
min: 1,
|
|
6360
|
+
max: 1
|
|
6361
|
+
},
|
|
6362
|
+
run: async (invocation) => {
|
|
6363
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6364
|
+
const projectId = await projectFrom$2(run, invocation.values.project);
|
|
6365
|
+
const stored = await resolveCredential(run.api, projectId, invocation.positionals[0] ?? "");
|
|
6366
|
+
if (stored.kind !== "claude_oauth_token") throw new CliError(`${stored.label} is a ${stored.kind}, which reports no usage.`, usageUnavailable);
|
|
6367
|
+
const credential = invocation.values.refresh ? dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
|
|
6368
|
+
id: projectId,
|
|
6369
|
+
credentialId: stored.id
|
|
6370
|
+
} } }), credentialHints) : stored;
|
|
6371
|
+
return printed(run, credential, usageReportOf);
|
|
6372
|
+
}
|
|
6373
|
+
}),
|
|
6152
6374
|
defineCommand({
|
|
6153
6375
|
name: "delete",
|
|
6154
6376
|
summary: "Delete a credential no agent uses.",
|
|
@@ -6534,6 +6756,115 @@ const agentTools = [
|
|
|
6534
6756
|
run: async ({ action, now, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, action === "stop" && now)
|
|
6535
6757
|
})
|
|
6536
6758
|
];
|
|
6759
|
+
const percentOf = (ratio) => ratio === null ? "-" : `${(ratio * 100).toFixed(1)}%`;
|
|
6760
|
+
const disksOf = (point) => [
|
|
6761
|
+
point.diskRootUsedRatio,
|
|
6762
|
+
point.diskDataUsedRatio,
|
|
6763
|
+
point.diskAgentsUsedRatio
|
|
6764
|
+
].map(percentOf).join(" / ");
|
|
6765
|
+
const gpuMemoryOf = ({ gpuMemoryUsedBytesAvg: used, gpuMemoryTotalBytes: total }) => used === null || total === null ? "-" : `${bytesOf(used)} of ${bytesOf(total)}`;
|
|
6766
|
+
const gpuOf = (point) => `${percentOf(point.gpuUtilRatioAvg)}, ${gpuMemoryOf(point)}`;
|
|
6767
|
+
const readsAGpu = (points) => points.some((point) => point.gpuUtilRatioAvg !== null || point.gpuMemoryTotalBytes !== null);
|
|
6768
|
+
const hostText$1 = (points) => {
|
|
6769
|
+
const gpu = readsAGpu(points);
|
|
6770
|
+
return renderTable([
|
|
6771
|
+
{ header: "AT" },
|
|
6772
|
+
{
|
|
6773
|
+
header: "CPU",
|
|
6774
|
+
align: "right"
|
|
6775
|
+
},
|
|
6776
|
+
{
|
|
6777
|
+
header: "MEMORY",
|
|
6778
|
+
align: "right"
|
|
6779
|
+
},
|
|
6780
|
+
{
|
|
6781
|
+
header: "LOAD",
|
|
6782
|
+
align: "right"
|
|
6783
|
+
},
|
|
6784
|
+
{
|
|
6785
|
+
header: "DISKS ROOT / DATA / AGENTS",
|
|
6786
|
+
align: "right"
|
|
6787
|
+
},
|
|
6788
|
+
...gpu ? [{
|
|
6789
|
+
header: "GPU",
|
|
6790
|
+
align: "right"
|
|
6791
|
+
}] : []
|
|
6792
|
+
], points.map((point) => [
|
|
6793
|
+
point.ts,
|
|
6794
|
+
percentOf(point.cpuRatioAvg),
|
|
6795
|
+
`${bytesOf(point.memoryUsedBytesAvg)} of ${bytesOf(point.memoryTotalBytes)}`,
|
|
6796
|
+
point.load1Avg === null ? "-" : point.load1Avg.toFixed(2),
|
|
6797
|
+
disksOf(point),
|
|
6798
|
+
...gpu ? [gpuOf(point)] : []
|
|
6799
|
+
]));
|
|
6800
|
+
};
|
|
6801
|
+
const byFootprint = (left, right) => right.memoryBytesAvg - left.memoryBytesAvg || left.agentName.localeCompare(right.agentName);
|
|
6802
|
+
const agentsText = (agents) => renderTable([
|
|
6803
|
+
{ header: "PROJECT" },
|
|
6804
|
+
{ header: "AGENT" },
|
|
6805
|
+
{
|
|
6806
|
+
header: "CPU (CORES)",
|
|
6807
|
+
align: "right"
|
|
6808
|
+
},
|
|
6809
|
+
{
|
|
6810
|
+
header: "MEMORY AVG",
|
|
6811
|
+
align: "right"
|
|
6812
|
+
},
|
|
6813
|
+
{
|
|
6814
|
+
header: "MEMORY MAX",
|
|
6815
|
+
align: "right"
|
|
6816
|
+
},
|
|
6817
|
+
{
|
|
6818
|
+
header: "NETWORK IN / OUT",
|
|
6819
|
+
align: "right"
|
|
6820
|
+
},
|
|
6821
|
+
{
|
|
6822
|
+
header: "DISK READ / WRITE",
|
|
6823
|
+
align: "right"
|
|
6824
|
+
}
|
|
6825
|
+
], agents.toSorted(byFootprint).map((usage) => [
|
|
6826
|
+
usage.projectName,
|
|
6827
|
+
usage.agentName,
|
|
6828
|
+
usage.cpuRatioAvg === null ? "-" : usage.cpuRatioAvg.toFixed(2),
|
|
6829
|
+
bytesOf(usage.memoryBytesAvg),
|
|
6830
|
+
bytesOf(usage.memoryBytesMax),
|
|
6831
|
+
`${bytesOf(usage.networkRxBytes)} / ${bytesOf(usage.networkTxBytes)}`,
|
|
6832
|
+
`${bytesOf(usage.blockReadBytes)} / ${bytesOf(usage.blockWriteBytes)}`
|
|
6833
|
+
]));
|
|
6834
|
+
const nodeMetricsText = (metrics) => [
|
|
6835
|
+
`${metrics.from} to ${metrics.to}, one point per ${metrics.resolution}.`,
|
|
6836
|
+
"",
|
|
6837
|
+
metrics.points.length === 0 ? "The node sent no sample in that range." : hostText$1(metrics.points),
|
|
6838
|
+
"",
|
|
6839
|
+
metrics.agents.length === 0 ? "No agent you can see ran on the node in that range." : agentsText(metrics.agents)
|
|
6840
|
+
].join("\n");
|
|
6841
|
+
const rangeOf = (given) => {
|
|
6842
|
+
if (given === void 0) return "1h";
|
|
6843
|
+
const range = metricsRanges.find((candidate) => candidate === given);
|
|
6844
|
+
if (range === void 0) throw new UsageError(`--range ${given} is not a range of metrics.`, `The ranges are ${metricsRanges.join(", ")}.`);
|
|
6845
|
+
return range;
|
|
6846
|
+
};
|
|
6847
|
+
const createNodeMetricsCommand = (adaptersOf) => defineCommand({
|
|
6848
|
+
name: "metrics",
|
|
6849
|
+
summary: "Show what the host of a node and the agents on it used over a range.",
|
|
6850
|
+
usage: "nodes metrics <node> [--range <range>]",
|
|
6851
|
+
optionsUsage: [
|
|
6852
|
+
...nodeUsage,
|
|
6853
|
+
["--range <range>", `how far back: ${metricsRanges.join(", ")} (default: 1h)`],
|
|
6854
|
+
["", "the agents are those of the projects you belong to, or all for an admin"]
|
|
6855
|
+
],
|
|
6856
|
+
options: { range: { type: "string" } },
|
|
6857
|
+
positionals: {
|
|
6858
|
+
min: 1,
|
|
6859
|
+
max: 1
|
|
6860
|
+
},
|
|
6861
|
+
run: async (invocation) => {
|
|
6862
|
+
const range = rangeOf(invocation.values.range);
|
|
6863
|
+
const run = await runOf(adaptersOf, invocation);
|
|
6864
|
+
const reference = await resolveNode(run, invocation.positionals[0] ?? "");
|
|
6865
|
+
return printed(run, await nodeMetricsOf(run.api, reference, range), nodeMetricsText);
|
|
6866
|
+
}
|
|
6867
|
+
});
|
|
6537
6868
|
//#endregion
|
|
6538
6869
|
//#region src/commands/nodes/split.ts
|
|
6539
6870
|
const splitFields = [
|
|
@@ -6589,6 +6920,8 @@ const changeOfParts = ({ reserve, monitor, executor }) => {
|
|
|
6589
6920
|
const cpusText = (cpus) => String(Math.round(cpus * 100) / 100);
|
|
6590
6921
|
const hostText = (host) => `${cpusText(host.cpus)} CPU and ${hostMemoryMbOf(host)} MB`;
|
|
6591
6922
|
const excessText = ({ cpus, memoryMb }) => [...cpus > 0 ? [`${cpusText(cpus)} CPU`] : [], ...memoryMb > 0 ? [`${memoryMb} MB`] : []].join(" and ");
|
|
6923
|
+
const coresText = (count) => count === 1 ? "1 core" : `${String(count)} cores`;
|
|
6924
|
+
const coresOfNode = (node) => node.runtime?.pools.layout?.cores ?? null;
|
|
6592
6925
|
const fittingSplit = (node, change, nameOf) => {
|
|
6593
6926
|
const merged = {
|
|
6594
6927
|
...node.split === null ? {} : valuesOf(node.split),
|
|
@@ -6602,9 +6935,25 @@ const fittingSplit = (node, change, nameOf) => {
|
|
|
6602
6935
|
validated(nodeSplitSchema, split, "a split of a node");
|
|
6603
6936
|
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.");
|
|
6604
6937
|
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.`);
|
|
6938
|
+
const cores = coresOfNode(node);
|
|
6939
|
+
const fit = cores === null ? null : splitCoresOf(split, cores);
|
|
6940
|
+
if (fit !== null && !fit.fits) throw new CliError(`The pools take whole cores of ${node.name}: the executor pool would get ${coresText(fit.executor)} and the monitor pool ${coresText(fit.monitor)} of ${String(fit.cores)}, and the reserve keeps at least 1.`, "Shrink a pool, so the reserve keeps the core of processor 0.");
|
|
6605
6941
|
return split;
|
|
6606
6942
|
};
|
|
6943
|
+
const processorCountsOf = (node) => {
|
|
6944
|
+
const pools = node.runtime?.pools;
|
|
6945
|
+
if (pools === void 0 || !layoutAppliesSplit(pools.layout, node.split)) return null;
|
|
6946
|
+
const monitor = pools.monitor.usage?.processors ?? null;
|
|
6947
|
+
const executor = pools.executor.usage?.processors ?? null;
|
|
6948
|
+
return monitor === null && executor === null ? null : {
|
|
6949
|
+
monitor,
|
|
6950
|
+
executor
|
|
6951
|
+
};
|
|
6952
|
+
};
|
|
6953
|
+
const processorsText = (count) => count === 1 ? "1 processor" : `${String(count)} processors`;
|
|
6954
|
+
const poolCpusText = (cpus, processors) => processors === null ? cpusText(cpus) : `${cpusText(cpus)} (${processorsText(processors)})`;
|
|
6607
6955
|
const splitViewOf = (node) => {
|
|
6956
|
+
const cores = coresOfNode(node);
|
|
6608
6957
|
const overflow = node.split === null || node.host === null ? null : splitOverflowOf(node.split, node.host);
|
|
6609
6958
|
return {
|
|
6610
6959
|
id: node.id,
|
|
@@ -6615,6 +6964,8 @@ const splitViewOf = (node) => {
|
|
|
6615
6964
|
memoryMb: hostMemoryMbOf(node.host)
|
|
6616
6965
|
},
|
|
6617
6966
|
split: node.split,
|
|
6967
|
+
processors: processorCountsOf(node),
|
|
6968
|
+
cores: cores === null || node.split === null ? null : splitCoresOf(node.split, cores),
|
|
6618
6969
|
free: overflow === null ? null : {
|
|
6619
6970
|
cpus: -overflow.cpus,
|
|
6620
6971
|
memoryMb: -overflow.memoryMb
|
|
@@ -6624,10 +6975,12 @@ const splitViewOf = (node) => {
|
|
|
6624
6975
|
const resourceCells = ({ cpus, memoryMb }) => [cpusText(cpus), `${memoryMb} MB`];
|
|
6625
6976
|
const oneSlotOf = ({ cpus, memoryMb, slots }) => `${cpusText(cpus / slots)} CPU, ${Math.floor(memoryMb / slots)} MB`;
|
|
6626
6977
|
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.`;
|
|
6978
|
+
const coresLineOf = ({ cores, executor, monitor, fits }) => fits ? `The pools take whole cores: the executor pool ${coresText(executor)} and the monitor pool ${coresText(monitor)}; the reserve keeps ${coresText(cores - executor - monitor)} of ${String(cores)}.` : `The executor pool would take ${coresText(executor)} and the monitor pool ${coresText(monitor)} of ${String(cores)}, so the node runs them on fewer cores and keeps 1 for the reserve.`;
|
|
6627
6979
|
const splitTableOf = (view, split) => {
|
|
6628
6980
|
const poolRow = (role) => [
|
|
6629
6981
|
role,
|
|
6630
|
-
|
|
6982
|
+
poolCpusText(split[role].cpus, view.processors?.[role] ?? null),
|
|
6983
|
+
`${String(split[role].memoryMb)} MB`,
|
|
6631
6984
|
String(split[role].slots),
|
|
6632
6985
|
oneSlotOf(split[role])
|
|
6633
6986
|
];
|
|
@@ -6675,7 +7028,8 @@ const splitTableOf = (view, split) => {
|
|
|
6675
7028
|
{ header: "ONE SLOT" }
|
|
6676
7029
|
], rows),
|
|
6677
7030
|
...view.host === null ? ["", hostLineOf(view)] : [],
|
|
6678
|
-
...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`]
|
|
7031
|
+
...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`],
|
|
7032
|
+
...view.cores === null ? [] : ["", coresLineOf(view.cores)]
|
|
6679
7033
|
].join("\n");
|
|
6680
7034
|
};
|
|
6681
7035
|
const splitText = (view) => view.split === null ? [
|
|
@@ -6683,10 +7037,14 @@ const splitText = (view) => view.split === null ? [
|
|
|
6683
7037
|
hostLineOf(view),
|
|
6684
7038
|
`kcloud nodes split set ${view.name} with every part splits it.`
|
|
6685
7039
|
].join("\n") : splitTableOf(view, view.split);
|
|
6686
|
-
const splitSummaryOf = (split) => {
|
|
7040
|
+
const splitSummaryOf = (split, processors = null) => {
|
|
6687
7041
|
if (split === null) return "none, slots counted from the host without hard limits";
|
|
6688
7042
|
const { reserve, monitor, executor } = split;
|
|
6689
|
-
const pool = (role, { cpus, memoryMb, slots }) =>
|
|
7043
|
+
const pool = (role, { cpus, memoryMb, slots }) => {
|
|
7044
|
+
const count = processors?.[role] ?? null;
|
|
7045
|
+
const onProcessors = count === null ? "" : ` (${processorsText(count)})`;
|
|
7046
|
+
return `${role} ${cpusText(cpus)} CPU${onProcessors}, ${memoryMb} MB, ${slots} slots`;
|
|
7047
|
+
};
|
|
6690
7048
|
return [
|
|
6691
7049
|
`reserve ${cpusText(reserve.cpus)} CPU, ${reserve.memoryMb} MB`,
|
|
6692
7050
|
pool("monitor", monitor),
|
|
@@ -6868,6 +7226,18 @@ const nodeTools = [
|
|
|
6868
7226
|
input: z.object({ node: nodeField }),
|
|
6869
7227
|
run: async ({ node }, run) => nodeDetailOf(run.api, await resolveNode(run, node))
|
|
6870
7228
|
}),
|
|
7229
|
+
defineTool({
|
|
7230
|
+
name: "node_metrics",
|
|
7231
|
+
title: "Show what the host of a node and its agents used",
|
|
7232
|
+
description: "Show the CPU, memory, load, disks and GPU of the host of a node over a range, and what each agent on it used over the whole range: the agents of the projects the caller belongs to, or every agent for an admin of the organization.",
|
|
7233
|
+
operations: ["getNodeMetrics"],
|
|
7234
|
+
safety: "read",
|
|
7235
|
+
input: z.object({
|
|
7236
|
+
node: nodeField,
|
|
7237
|
+
range: z.enum(metricsRanges).default("1h")
|
|
7238
|
+
}),
|
|
7239
|
+
run: async ({ node, range }, run) => nodeMetricsOf(run.api, await resolveNode(run, node), range)
|
|
7240
|
+
}),
|
|
6871
7241
|
defineTool({
|
|
6872
7242
|
name: "node_split_set",
|
|
6873
7243
|
title: "Split a node into pools",
|
|
@@ -7010,19 +7380,31 @@ const credentialTools = [
|
|
|
7010
7380
|
}),
|
|
7011
7381
|
defineTool({
|
|
7012
7382
|
name: "credential_update",
|
|
7013
|
-
title: "Rename or
|
|
7014
|
-
description: "rename gives a credential a new label; verify asks the instance to check it against its service, and credentials_list shows the answer.",
|
|
7015
|
-
operations: [
|
|
7383
|
+
title: "Rename, verify or read the usage of a credential",
|
|
7384
|
+
description: "rename gives a credential a new label; verify asks the instance to check it against its service, and credentials_list shows the answer; refresh_usage reads the usage of the Claude subscription behind a Claude Code OAuth token now, or answers the last reading within 30 seconds of the previous one.",
|
|
7385
|
+
operations: [
|
|
7386
|
+
"renameCredential",
|
|
7387
|
+
"verifyCredential",
|
|
7388
|
+
"refreshCredentialUsage"
|
|
7389
|
+
],
|
|
7016
7390
|
safety: "write",
|
|
7017
7391
|
input: z.object({
|
|
7018
7392
|
project: projectField,
|
|
7019
7393
|
credential: credentialField$1,
|
|
7020
|
-
action: z.enum([
|
|
7394
|
+
action: z.enum([
|
|
7395
|
+
"rename",
|
|
7396
|
+
"verify",
|
|
7397
|
+
"refresh_usage"
|
|
7398
|
+
]),
|
|
7021
7399
|
label: z.string().trim().min(1).optional().describe("The new label, for rename.")
|
|
7022
7400
|
}),
|
|
7023
7401
|
run: async ({ project, credential, action, label }, run) => {
|
|
7024
7402
|
const projectId = await projectIdOf(run, project);
|
|
7025
7403
|
const credentialId = await resolveCredentialId(run.api, projectId, credential);
|
|
7404
|
+
if (action === "refresh_usage") return dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
|
|
7405
|
+
id: projectId,
|
|
7406
|
+
credentialId
|
|
7407
|
+
} } }), credentialHints);
|
|
7026
7408
|
if (action === "verify") {
|
|
7027
7409
|
doneWith("verify the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/verify", { params: { path: {
|
|
7028
7410
|
id: projectId,
|
|
@@ -7363,21 +7745,23 @@ const observabilityTools = [
|
|
|
7363
7745
|
defineTool({
|
|
7364
7746
|
name: "agent_memory_search",
|
|
7365
7747
|
title: "Search the memory of an agent",
|
|
7366
|
-
description: "Search what an agent remembers of the tasks it finished.",
|
|
7748
|
+
description: "Search what an agent remembers of the tasks it finished, newest first, one page or every summary with all.",
|
|
7367
7749
|
operations: ["searchAgentMemory"],
|
|
7368
7750
|
safety: "read",
|
|
7369
7751
|
input: z.object({
|
|
7370
7752
|
...agentFields,
|
|
7371
7753
|
query: z.string().max(200).optional(),
|
|
7372
|
-
|
|
7754
|
+
outcome: memoryOutcomeSchema.optional().describe("Only the summaries of tasks that succeeded or failed."),
|
|
7755
|
+
limit: z.int().min(1).max(100).default(20),
|
|
7756
|
+
all: pageFields.all
|
|
7373
7757
|
}),
|
|
7374
|
-
run: async ({ query, limit, ...reference }, run) =>
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
}
|
|
7758
|
+
run: async ({ query, outcome, limit, all, ...reference }, run) => memoryOf(run.api, await agentIdOf(run, reference), {
|
|
7759
|
+
query,
|
|
7760
|
+
outcome
|
|
7761
|
+
}, {
|
|
7762
|
+
limit,
|
|
7763
|
+
all
|
|
7764
|
+
})
|
|
7381
7765
|
}),
|
|
7382
7766
|
defineTool({
|
|
7383
7767
|
name: "agent_earlier_output",
|
|
@@ -7442,6 +7826,70 @@ const observabilityTools = [
|
|
|
7442
7826
|
})
|
|
7443
7827
|
];
|
|
7444
7828
|
//#endregion
|
|
7829
|
+
//#region src/commands/logo.ts
|
|
7830
|
+
const logoOptions = {
|
|
7831
|
+
logo: { type: "string" },
|
|
7832
|
+
"no-logo": {
|
|
7833
|
+
type: "boolean",
|
|
7834
|
+
default: false
|
|
7835
|
+
}
|
|
7836
|
+
};
|
|
7837
|
+
const logoUsage = [["--logo <path>", "show a PNG, JPEG or WebP image instead of the color, at most 512 KB and 2048 px on a side"], ["--no-logo", "remove the logo and show the color again"]];
|
|
7838
|
+
const logoHints = {
|
|
7839
|
+
"logo.unreadable": "The file has to be a PNG, JPEG or WebP image.",
|
|
7840
|
+
"logo.too_small": "The image needs at least 64 pixels on each side.",
|
|
7841
|
+
"logo.too_large": "The image may have at most 2048 pixels on a side."
|
|
7842
|
+
};
|
|
7843
|
+
const startsWith = (bytes, prefix, at = 0) => prefix.every((byte, index) => bytes[at + index] === byte);
|
|
7844
|
+
const ascii = (text) => Array.from(text, (letter) => letter.charCodeAt(0));
|
|
7845
|
+
const signatures = [
|
|
7846
|
+
{
|
|
7847
|
+
contentType: "image/png",
|
|
7848
|
+
matches: (bytes) => startsWith(bytes, [
|
|
7849
|
+
137,
|
|
7850
|
+
...ascii("PNG\r\n"),
|
|
7851
|
+
26,
|
|
7852
|
+
10
|
|
7853
|
+
])
|
|
7854
|
+
},
|
|
7855
|
+
{
|
|
7856
|
+
contentType: "image/jpeg",
|
|
7857
|
+
matches: (bytes) => startsWith(bytes, [
|
|
7858
|
+
255,
|
|
7859
|
+
216,
|
|
7860
|
+
255
|
|
7861
|
+
])
|
|
7862
|
+
},
|
|
7863
|
+
{
|
|
7864
|
+
contentType: "image/webp",
|
|
7865
|
+
matches: (bytes) => startsWith(bytes, ascii("RIFF")) && startsWith(bytes, ascii("WEBP"), 8)
|
|
7866
|
+
}
|
|
7867
|
+
];
|
|
7868
|
+
const isLogoChange = (change) => change.logo !== void 0 || change["no-logo"] === true;
|
|
7869
|
+
const logoFileOf = async (workingDirectory, file) => {
|
|
7870
|
+
const path = resolve(workingDirectory, file);
|
|
7871
|
+
const bytes = await readFile(path).catch(() => {
|
|
7872
|
+
throw new CliError(`${path} could not be read.`);
|
|
7873
|
+
});
|
|
7874
|
+
if (bytes.byteLength > 524288) throw new UsageError(`${file} holds ${String(Math.ceil(bytes.byteLength / 1024))} KB, and a logo at most ${String(logoMaxBytes / 1024)} KB.`);
|
|
7875
|
+
const contentType = signatures.find(({ matches }) => matches(bytes))?.contentType;
|
|
7876
|
+
if (contentType === void 0) throw new UsageError(`${file} is not a PNG, JPEG or WebP image.`);
|
|
7877
|
+
return {
|
|
7878
|
+
bytes,
|
|
7879
|
+
contentType
|
|
7880
|
+
};
|
|
7881
|
+
};
|
|
7882
|
+
const logoActionOf = async (workingDirectory, change) => {
|
|
7883
|
+
if (change.logo !== void 0 && change["no-logo"] === true) throw new UsageError("--logo and --no-logo exclude each other.");
|
|
7884
|
+
if (change.logo !== void 0) return logoFileOf(workingDirectory, change.logo);
|
|
7885
|
+
return change["no-logo"] === true ? "remove" : null;
|
|
7886
|
+
};
|
|
7887
|
+
const logoUpload = ({ bytes, contentType }) => ({
|
|
7888
|
+
body: bytes,
|
|
7889
|
+
bodySerializer: (body) => body,
|
|
7890
|
+
headers: { "content-type": contentType }
|
|
7891
|
+
});
|
|
7892
|
+
//#endregion
|
|
7445
7893
|
//#region src/commands/roles.ts
|
|
7446
7894
|
const roleOption = { role: { type: "string" } };
|
|
7447
7895
|
const roleUsage = ["--role <role>", "admin or member"];
|
|
@@ -7751,6 +8199,48 @@ const costsText = (costs, { from, to }, slugs) => [
|
|
|
7751
8199
|
"",
|
|
7752
8200
|
`${costs.totalCostUsd.toFixed(2)} over ${costs.totalRuns} runs and ${costs.sessions} sessions.`
|
|
7753
8201
|
].join("\n");
|
|
8202
|
+
const organizationChangeHints = {
|
|
8203
|
+
...organizationHints,
|
|
8204
|
+
"organization.slug_taken": "Another organization already uses that slug.",
|
|
8205
|
+
"organization.invalid_slug": "A slug holds lowercase letters, digits and hyphens.",
|
|
8206
|
+
"auth.forbidden": "Only an admin of the organization changes it.",
|
|
8207
|
+
...logoHints
|
|
8208
|
+
};
|
|
8209
|
+
const followSlug = async (homeDirectory, origin, before, after) => {
|
|
8210
|
+
const store = await readInstanceStore(homeDirectory);
|
|
8211
|
+
const stored = store.instances[origin];
|
|
8212
|
+
if (stored?.organization !== before.slug) return;
|
|
8213
|
+
await writeInstanceStore(homeDirectory, withInstance(store, origin, {
|
|
8214
|
+
...stored,
|
|
8215
|
+
organization: after.slug
|
|
8216
|
+
}));
|
|
8217
|
+
};
|
|
8218
|
+
const updatedOrganization = async (api, current, fields) => {
|
|
8219
|
+
if ([
|
|
8220
|
+
fields.name,
|
|
8221
|
+
fields.slug,
|
|
8222
|
+
fields.color
|
|
8223
|
+
].every((field) => field === void 0)) return current;
|
|
8224
|
+
const body = {
|
|
8225
|
+
name: fields.name ?? current.name,
|
|
8226
|
+
slug: fields.slug ?? current.slug,
|
|
8227
|
+
color: fields.color ?? current.color
|
|
8228
|
+
};
|
|
8229
|
+
validated(updateOrganizationBodySchema, body, "a change to an organization");
|
|
8230
|
+
return dataOf("change the organization", await api.PATCH("/organizations/{organizationId}", {
|
|
8231
|
+
params: { path: { organizationId: current.id } },
|
|
8232
|
+
body
|
|
8233
|
+
}), organizationChangeHints);
|
|
8234
|
+
};
|
|
8235
|
+
const withLogo = async (api, organization, logo) => {
|
|
8236
|
+
const params = { path: { organizationId: organization.id } };
|
|
8237
|
+
if (logo === null) return organization;
|
|
8238
|
+
if (logo === "remove") return dataOf("remove the logo of the organization", await api.DELETE("/organizations/{organizationId}/logo", { params }), organizationChangeHints);
|
|
8239
|
+
return dataOf("set the logo of the organization", await api.PUT("/organizations/{organizationId}/logo", {
|
|
8240
|
+
params,
|
|
8241
|
+
...logoUpload(logo)
|
|
8242
|
+
}), organizationChangeHints);
|
|
8243
|
+
};
|
|
7754
8244
|
const createOrgsCommand = (adaptersOf) => defineGroup({
|
|
7755
8245
|
name: "orgs",
|
|
7756
8246
|
summary: "The organizations you can see, and the one kcloud acts in.",
|
|
@@ -7835,11 +8325,52 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
|
|
|
7835
8325
|
return printed(run, organization, (shown) => renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
|
|
7836
8326
|
["name", shown.name],
|
|
7837
8327
|
["slug", shown.slug],
|
|
8328
|
+
["color", shown.color],
|
|
8329
|
+
["logo", shown.logoHash ?? "-"],
|
|
7838
8330
|
["created", shown.createdAt],
|
|
7839
8331
|
["id", shown.id]
|
|
7840
8332
|
]));
|
|
7841
8333
|
}
|
|
7842
8334
|
}),
|
|
8335
|
+
defineCommand({
|
|
8336
|
+
name: "set",
|
|
8337
|
+
summary: "Change the name, slug, color or logo of an organization.",
|
|
8338
|
+
usage: "orgs set [<organization>] [--name <name>] [--slug <slug>] [--color <#rrggbb>] [--logo <path> | --no-logo]",
|
|
8339
|
+
optionsUsage: [
|
|
8340
|
+
["--name <name>", "the new name"],
|
|
8341
|
+
["--slug <slug>", "the new slug; scripts and --org that name the old one stop finding it"],
|
|
8342
|
+
["--color <#rrggbb>", "the color of the organization in the panel"],
|
|
8343
|
+
...logoUsage
|
|
8344
|
+
],
|
|
8345
|
+
options: {
|
|
8346
|
+
name: { type: "string" },
|
|
8347
|
+
slug: { type: "string" },
|
|
8348
|
+
color: { type: "string" },
|
|
8349
|
+
...logoOptions
|
|
8350
|
+
},
|
|
8351
|
+
positionals: {
|
|
8352
|
+
min: 0,
|
|
8353
|
+
max: 1
|
|
8354
|
+
},
|
|
8355
|
+
run: async (invocation) => {
|
|
8356
|
+
const { values } = invocation;
|
|
8357
|
+
if ([
|
|
8358
|
+
values.name,
|
|
8359
|
+
values.slug,
|
|
8360
|
+
values.color
|
|
8361
|
+
].every((value) => value === void 0) && !isLogoChange(values)) throw new UsageError("orgs set needs --name, --slug, --color, --logo or --no-logo.");
|
|
8362
|
+
const logo = await logoActionOf(invocation.context.workingDirectory, values);
|
|
8363
|
+
const run = await runOf(adaptersOf, invocation);
|
|
8364
|
+
const current = await organizationOrInUse(run, invocation.positionals[0]);
|
|
8365
|
+
const updated = await updatedOrganization(run.api, current, values);
|
|
8366
|
+
if (updated.slug !== current.slug) {
|
|
8367
|
+
await followSlug(run.context.homeDirectory, run.origin, current, updated);
|
|
8368
|
+
run.context.terminal.error(run.context.terminal.errorStyles.warning(`warning Scripts that pass --org ${current.slug} or set ${organizationVariable}=${current.slug} need ${updated.slug} now.`));
|
|
8369
|
+
}
|
|
8370
|
+
const organization = await withLogo(run.api, updated, logo);
|
|
8371
|
+
return printed(run, organization, (shown) => `Changed ${shown.name} (${shown.slug}).`);
|
|
8372
|
+
}
|
|
8373
|
+
}),
|
|
7843
8374
|
defineCommand({
|
|
7844
8375
|
name: "costs",
|
|
7845
8376
|
summary: "Show what the organization spent, by day and by project.",
|
|
@@ -7904,6 +8435,20 @@ const organizationTools = [
|
|
|
7904
8435
|
return dataOf("describe the organization", await run.api.GET("/organizations/{organizationId}", { params: { path: { organizationId: id } } }), organizationHints);
|
|
7905
8436
|
}
|
|
7906
8437
|
}),
|
|
8438
|
+
defineTool({
|
|
8439
|
+
name: "organization_update",
|
|
8440
|
+
title: "Change an organization",
|
|
8441
|
+
description: "Change the name or color of an organization, by default the one this server acts in. A field left out keeps its value. A new slug is set with kcloud orgs set or in the panel.",
|
|
8442
|
+
operations: ["listOrganizations", "updateOrganization"],
|
|
8443
|
+
safety: "write",
|
|
8444
|
+
idempotent: true,
|
|
8445
|
+
input: z.object({
|
|
8446
|
+
organization: z.string().trim().min(1).optional().describe("The organization, by its slug, its name or its id."),
|
|
8447
|
+
name: organizationNameSchema.optional(),
|
|
8448
|
+
color: colorSchema.optional().describe("The color in the panel, as #rrggbb.")
|
|
8449
|
+
}),
|
|
8450
|
+
run: async ({ organization, ...change }, run) => updatedOrganization(run.api, await organizationOrInUse(run, organization), change)
|
|
8451
|
+
}),
|
|
7907
8452
|
defineTool({
|
|
7908
8453
|
name: "organization_costs",
|
|
7909
8454
|
title: "Show what the organization spent",
|
|
@@ -8433,19 +8978,37 @@ const createdProject = async (scope, name, { slug, description, color }) => {
|
|
|
8433
8978
|
organization
|
|
8434
8979
|
};
|
|
8435
8980
|
};
|
|
8436
|
-
const
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8981
|
+
const logoHint = {
|
|
8982
|
+
...projectHint,
|
|
8983
|
+
...logoHints
|
|
8984
|
+
};
|
|
8985
|
+
const changedProject = async (api, id, change, logo = null) => {
|
|
8986
|
+
const params = { path: { id } };
|
|
8987
|
+
let project = await projectOf(api, id);
|
|
8988
|
+
if ([
|
|
8989
|
+
change.name,
|
|
8990
|
+
change.slug,
|
|
8991
|
+
change.description,
|
|
8992
|
+
change.color
|
|
8993
|
+
].some((field) => field !== void 0)) {
|
|
8994
|
+
const body = {
|
|
8995
|
+
name: change.name ?? project.name,
|
|
8996
|
+
slug: change.slug ?? project.slug,
|
|
8997
|
+
description: change.description ?? project.description,
|
|
8998
|
+
color: change.color ?? project.color
|
|
8999
|
+
};
|
|
9000
|
+
validated(updateProjectBodySchema, body, "a change to a project");
|
|
9001
|
+
project = dataOf("change the project", await api.PATCH("/projects/{id}", {
|
|
9002
|
+
params,
|
|
9003
|
+
body
|
|
9004
|
+
}), projectHint);
|
|
9005
|
+
}
|
|
9006
|
+
if (logo !== null && logo !== "remove") project = dataOf("set the logo of the project", await api.PUT("/projects/{id}/logo", {
|
|
9007
|
+
params,
|
|
9008
|
+
...logoUpload(logo)
|
|
9009
|
+
}), logoHint);
|
|
9010
|
+
if (logo === "remove") project = dataOf("remove the logo of the project", await api.DELETE("/projects/{id}/logo", { params }), logoHint);
|
|
9011
|
+
return project;
|
|
8449
9012
|
};
|
|
8450
9013
|
const deleteProject = async (api, id) => {
|
|
8451
9014
|
doneWith("delete the project", await api.DELETE("/projects/{id}", { params: { path: { id } } }), projectHint);
|
|
@@ -8504,6 +9067,7 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
8504
9067
|
["slug", shown.slug],
|
|
8505
9068
|
["description", shown.description === "" ? "-" : shown.description],
|
|
8506
9069
|
["color", shown.color],
|
|
9070
|
+
["logo", shown.logoHash ?? "-"],
|
|
8507
9071
|
["docker", shown.dockerAllowed ? "allowed" : "not allowed"],
|
|
8508
9072
|
["members", String(shown.memberCount)],
|
|
8509
9073
|
["created", shown.createdAt],
|
|
@@ -8529,12 +9093,17 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
8529
9093
|
}),
|
|
8530
9094
|
defineCommand({
|
|
8531
9095
|
name: "update",
|
|
8532
|
-
summary: "Change the name, slug, description or
|
|
8533
|
-
usage: "projects update <project> [--name <name>] [--slug <slug>] [--description <text>] [--color <#rrggbb>]",
|
|
8534
|
-
optionsUsage: [
|
|
9096
|
+
summary: "Change the name, slug, description, color or logo of a project.",
|
|
9097
|
+
usage: "projects update <project> [--name <name>] [--slug <slug>] [--description <text>] [--color <#rrggbb>] [--logo <path> | --no-logo]",
|
|
9098
|
+
optionsUsage: [
|
|
9099
|
+
["--name <name>", "the new name"],
|
|
9100
|
+
...fieldUsage,
|
|
9101
|
+
...logoUsage
|
|
9102
|
+
],
|
|
8535
9103
|
options: {
|
|
8536
9104
|
name: { type: "string" },
|
|
8537
|
-
...fieldOptions
|
|
9105
|
+
...fieldOptions,
|
|
9106
|
+
...logoOptions
|
|
8538
9107
|
},
|
|
8539
9108
|
positionals: {
|
|
8540
9109
|
min: 1,
|
|
@@ -8547,10 +9116,11 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
|
|
|
8547
9116
|
values.slug,
|
|
8548
9117
|
values.description,
|
|
8549
9118
|
values.color
|
|
8550
|
-
].every((value) => value === void 0)) throw new UsageError("projects update needs --name, --slug, --description or --
|
|
9119
|
+
].every((value) => value === void 0) && !isLogoChange(values)) throw new UsageError("projects update needs --name, --slug, --description, --color, --logo or --no-logo.");
|
|
9120
|
+
const logo = await logoActionOf(invocation.context.workingDirectory, values);
|
|
8551
9121
|
const run = await runOf(adaptersOf, invocation);
|
|
8552
9122
|
const id = await resolveProjectId(run, invocation.positionals[0] ?? "");
|
|
8553
|
-
const project = await changedProject(run.api, id, values);
|
|
9123
|
+
const project = await changedProject(run.api, id, values, logo);
|
|
8554
9124
|
return printed(run, project, (shown) => `Changed ${shown.name} (${shown.slug}).`);
|
|
8555
9125
|
}
|
|
8556
9126
|
}),
|
|
@@ -9329,20 +9899,31 @@ const operationRowsOf = (operation) => {
|
|
|
9329
9899
|
...error === null ? [] : [["error", error]]
|
|
9330
9900
|
];
|
|
9331
9901
|
};
|
|
9332
|
-
const
|
|
9902
|
+
const lagOf = ({ oldestCursorLagSeconds }) => oldestCursorLagSeconds === null ? "-" : `${oldestCursorLagSeconds} s`;
|
|
9903
|
+
const sandboxRowsOf = ({ sandbox }) => sandbox.enabled ? [
|
|
9904
|
+
["sandboxes", `enabled, image ${sandbox.image.tag}`],
|
|
9905
|
+
["agents with docker", String(sandbox.agentsWithSandbox)],
|
|
9906
|
+
["sidecars running", String(sandbox.sidecarsRunning)],
|
|
9907
|
+
["sandbox volumes", sandbox.volumeBytes === null ? "not measured yet" : bytesOf(sandbox.volumeBytes)]
|
|
9908
|
+
] : [["sandboxes", "disabled, SANDBOX_ENABLED is off on the node"]];
|
|
9909
|
+
const runtimeRowsOf = (runtime, engine) => [
|
|
9333
9910
|
["reported", runtime.reportedAt],
|
|
9334
9911
|
["docker", runtime.docker.lastError === null ? runtime.docker.state : `${runtime.docker.state}: ${runtime.docker.lastError}`],
|
|
9335
9912
|
["data storage", storageOf(runtime.storage.data)],
|
|
9336
9913
|
["engine storage", storageOf(runtime.storage.engine)],
|
|
9337
9914
|
["last reconcile", runtime.reconcile.lastTickError === null ? runtime.reconcile.lastTickAt ?? "never" : `${runtime.reconcile.lastTickAt ?? "never"}: ${runtime.reconcile.lastTickError}`],
|
|
9338
9915
|
["agents", `${runtime.reconcile.agentsActive} active, ${runtime.reconcile.agentsQueued} queued`],
|
|
9339
|
-
["log readers", String(runtime.ingest.readers)]
|
|
9916
|
+
["log readers", String(runtime.ingest.readers)],
|
|
9917
|
+
["oldest cursor lag", lagOf(runtime.ingest)],
|
|
9918
|
+
...engine === "native" ? [] : sandboxRowsOf(runtime)
|
|
9340
9919
|
];
|
|
9920
|
+
const sessionsText = (count, noun) => `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
|
|
9921
|
+
const heldPoolsOf = ({ held = [] }) => held;
|
|
9341
9922
|
const updateOf = ({ update }) => {
|
|
9342
9923
|
if (update === null) return "not reported";
|
|
9343
9924
|
switch (update.state) {
|
|
9344
9925
|
case "current": return "current";
|
|
9345
|
-
case "waiting": return `
|
|
9926
|
+
case "waiting": return heldPoolsOf(update).includes("monitor") ? `admissions held for ${update.target ?? "-"}, waits for ${sessionsText(update.sessions, "session")}` : `executor admissions held for ${update.target ?? "-"}, waits for ${sessionsText(update.sessions, "executor session")}; monitors go on until the executor pool is empty`;
|
|
9346
9927
|
case "updating": return `updating to ${update.target ?? "-"}`;
|
|
9347
9928
|
case "failed": return `failed to reach ${update.target ?? "-"}, runs its previous version`;
|
|
9348
9929
|
case "unmanaged": return "updated with its platform";
|
|
@@ -9368,7 +9949,7 @@ const nodeDetailsText = (node) => [
|
|
|
9368
9949
|
["data root", node.host?.dataRoot ?? "-"],
|
|
9369
9950
|
["capabilities", capabilitiesOf(node)],
|
|
9370
9951
|
["offers", node.offered.length === 0 ? "-" : node.offered.join(", ")],
|
|
9371
|
-
["split", splitSummaryOf(node.split)],
|
|
9952
|
+
["split", splitSummaryOf(node.split, processorCountsOf(node))],
|
|
9372
9953
|
["registration", registrationOf(node)],
|
|
9373
9954
|
["connected", node.connectedAt ?? "never"],
|
|
9374
9955
|
["last seen", node.lastSeenAt ?? "never"],
|
|
@@ -9394,9 +9975,9 @@ const nodeDetailsText = (node) => [
|
|
|
9394
9975
|
])),
|
|
9395
9976
|
"",
|
|
9396
9977
|
...node.runtime === null ? ["The node has reported no runtime yet."] : [
|
|
9397
|
-
renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime)),
|
|
9978
|
+
renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime, node.engine)),
|
|
9398
9979
|
"",
|
|
9399
|
-
poolsText(node.runtime.pools)
|
|
9980
|
+
poolsText(node.runtime.pools, node.split)
|
|
9400
9981
|
]
|
|
9401
9982
|
].join("\n");
|
|
9402
9983
|
const installCommandsOf = (origin) => [
|
|
@@ -9523,6 +10104,7 @@ const createNodesCommand = (adaptersOf) => defineGroup({
|
|
|
9523
10104
|
return printed(run, await nodeDetailOf(run.api, reference), nodeDetailsText);
|
|
9524
10105
|
}
|
|
9525
10106
|
}),
|
|
10107
|
+
createNodeMetricsCommand(adaptersOf),
|
|
9526
10108
|
createSplitCommand(adaptersOf),
|
|
9527
10109
|
defineCommand({
|
|
9528
10110
|
name: "create",
|