@kikimoradev/kcloud 1.0.4 → 1.2.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.
Files changed (2) hide show
  1. package/dist/main.js +787 -202
  2. 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",
@@ -781,9 +802,13 @@ const errorCodes = Object.keys({
781
802
  "runtime.monitor_draining": "conflict",
782
803
  "runtime.monitor_limited": "conflict",
783
804
  "runtime.monitor_check_unsupported": "rule_violation",
805
+ "runtime.task_deletion_unsupported": "rule_violation",
806
+ "task.not_found": "not_found",
807
+ "task.not_finished": "conflict",
784
808
  "node.not_found": "not_found",
785
809
  "node.name_taken": "conflict",
786
810
  "node.offline": "rule_violation",
811
+ "node.outdated": "rule_violation",
787
812
  "node.not_registered": "rule_violation",
788
813
  "node.registration_invalid": "unauthenticated",
789
814
  "node.registration_used": "conflict",
@@ -801,7 +826,6 @@ const errorCodes = Object.keys({
801
826
  "node.size_unavailable": "rule_violation",
802
827
  "node.size_not_larger": "rule_violation",
803
828
  "node.region_unavailable": "rule_violation",
804
- "node.import_refused": "rule_violation",
805
829
  "provider.not_found": "not_found",
806
830
  "provider.name_taken": "conflict",
807
831
  "provider.in_use": "conflict",
@@ -815,20 +839,18 @@ const errorCodes = Object.keys({
815
839
  "infrastructure.provider_error": "internal",
816
840
  "infrastructure.operation_in_progress": "conflict",
817
841
  "infrastructure.host_ops_disabled": "rule_violation",
818
- "infrastructure.backups_unavailable": "rule_violation",
819
- "infrastructure.maintenance_in_progress": "conflict",
820
- "infrastructure.maintenance_not_in_progress": "conflict",
821
842
  "backup.not_found": "not_found",
822
843
  "backup.in_progress": "conflict",
823
844
  "backup.unavailable": "rule_violation",
824
845
  "backup.corrupt": "rule_violation",
825
846
  "backup.undecryptable": "rule_violation",
826
- "platform.version_unknown": "rule_violation",
827
- "platform.same_version": "rule_violation",
828
- "platform.image_unresolved": "rule_violation",
829
- "platform.scripts_outdated": "rule_violation",
830
847
  "volume.invalid_size": "rule_violation",
831
- "notification.not_found": "not_found"
848
+ "notification.not_found": "not_found",
849
+ "logo.not_found": "not_found",
850
+ "logo.unreadable": "rule_violation",
851
+ "logo.too_small": "rule_violation",
852
+ "logo.too_large": "rule_violation",
853
+ "logo.type_mismatch": "rule_violation"
832
854
  });
833
855
  const errorCodeSchema = z.enum(errorCodes);
834
856
  const errorDetailsSchema = z.record(z.string(), z.union([
@@ -1211,11 +1233,6 @@ z.object({
1211
1233
  sidecarsRunning: z.int().nonnegative(),
1212
1234
  volumeBytes: z.int().nonnegative().nullable()
1213
1235
  }),
1214
- sessionLogs: z.object({
1215
- prunedDirectories: z.int().nonnegative(),
1216
- lastPrunedAt: z.iso.datetime().nullable(),
1217
- failures: z.int().nonnegative()
1218
- }),
1219
1236
  metrics: runtimeMetricsSchema,
1220
1237
  deadLetters: z.array(deadLetterQueueSchema),
1221
1238
  degradedAgents: z.array(degradedAgentSchema)
@@ -1235,10 +1252,16 @@ const agentEventSummarySchema = z.object({
1235
1252
  durationMs: z.int().nullable(),
1236
1253
  payload: z.record(z.string(), z.unknown())
1237
1254
  }).meta({ id: "AgentEventSummary" });
1255
+ const agentEventLimitSchema = z.coerce.number().int().min(1).max(500).default(200);
1238
1256
  z.object({
1239
1257
  since: z.string().max(64).optional(),
1240
- limit: z.coerce.number().int().min(1).max(500).default(200)
1258
+ limit: agentEventLimitSchema
1241
1259
  });
1260
+ z.object({
1261
+ before: z.string().max(64).optional(),
1262
+ limit: agentEventLimitSchema
1263
+ });
1264
+ pageSchema(agentEventSummarySchema).meta({ id: "LiveHistoryPage" });
1242
1265
  const taskStatuses = [
1243
1266
  "pending",
1244
1267
  "in_progress",
@@ -1247,6 +1270,13 @@ const taskStatuses = [
1247
1270
  "cancelled"
1248
1271
  ];
1249
1272
  const taskStatusSchema = z.enum(taskStatuses);
1273
+ const finalTaskStatuses = [
1274
+ "done",
1275
+ "failed",
1276
+ "cancelled"
1277
+ ];
1278
+ const finalTaskStatusSchema = z.enum(finalTaskStatuses);
1279
+ const taskDeletionKikimoraVersion = "0.14.0";
1250
1280
  const maxTaskDescriptionLength = 4e3;
1251
1281
  const agentTaskSchema = z.object({
1252
1282
  id: z.string(),
@@ -1262,15 +1292,31 @@ const agentTaskSchema = z.object({
1262
1292
  startedAt: z.iso.datetime().nullable(),
1263
1293
  finishedAt: z.iso.datetime().nullable()
1264
1294
  }).meta({ id: "AgentTask" });
1295
+ const agentTaskSourceSchema = z.enum(["worker", "projection"]);
1296
+ const agentTaskStaleReasonSchema = z.enum([
1297
+ "unreachable",
1298
+ "timeout",
1299
+ "stopped"
1300
+ ]).nullable();
1301
+ const taskCountsSchema = z.object({
1302
+ pending: z.int().nonnegative(),
1303
+ in_progress: z.int().nonnegative(),
1304
+ done: z.int().nonnegative(),
1305
+ failed: z.int().nonnegative(),
1306
+ cancelled: z.int().nonnegative()
1307
+ }).meta({ id: "TaskCounts" });
1265
1308
  z.object({
1266
1309
  items: z.array(agentTaskSchema),
1267
- source: z.enum(["worker", "projection"]),
1268
- staleReason: z.enum([
1269
- "unreachable",
1270
- "timeout",
1271
- "stopped"
1272
- ]).nullable()
1310
+ nextCursor: z.string().nullable(),
1311
+ counts: taskCountsSchema.nullable(),
1312
+ source: agentTaskSourceSchema,
1313
+ staleReason: agentTaskStaleReasonSchema
1273
1314
  }).meta({ id: "AgentTaskList" });
1315
+ z.object({
1316
+ task: agentTaskSchema,
1317
+ source: agentTaskSourceSchema,
1318
+ staleReason: agentTaskStaleReasonSchema
1319
+ }).meta({ id: "AgentTaskView" });
1274
1320
  z.object({
1275
1321
  title: z.string().trim().min(1).max(200).optional(),
1276
1322
  description: z.string().trim().min(1).max(maxTaskDescriptionLength)
@@ -1279,7 +1325,9 @@ z.object({
1279
1325
  state: z.enum(["requested", "running"]),
1280
1326
  cycle: z.int().nonnegative().nullable()
1281
1327
  }).meta({ id: "MonitorCheck" });
1282
- z.object({ status: taskStatusSchema.optional() });
1328
+ cursorQuerySchema.extend({ status: taskStatusSchema.optional() });
1329
+ z.object({ status: finalTaskStatusSchema });
1330
+ z.object({ deleted: z.array(z.string()) }).meta({ id: "AgentTasksDeleted" });
1283
1331
  z.object({
1284
1332
  id: idSchema,
1285
1333
  taskId: z.string().min(1).max(128)
@@ -1407,17 +1455,21 @@ const memorySummarySchema = z.object({
1407
1455
  sessionId: z.string().nullable(),
1408
1456
  createdAt: z.iso.datetime()
1409
1457
  }).meta({ id: "MemorySummary" });
1410
- z.object({
1411
- items: z.array(memorySummarySchema),
1458
+ const memoryOutcomes = ["succeeded", "failed"];
1459
+ const memoryOutcomeSchema = z.enum(memoryOutcomes);
1460
+ pageSchema(memorySummarySchema).extend({
1412
1461
  source: z.enum(["worker", "unavailable"]),
1413
1462
  staleReason: z.enum([
1414
1463
  "unreachable",
1415
1464
  "timeout",
1416
1465
  "stopped"
1417
- ]).nullable()
1418
- }).meta({ id: "AgentMemoryList" });
1466
+ ]).nullable(),
1467
+ truncated: z.boolean()
1468
+ }).meta({ id: "AgentMemoryPage" });
1419
1469
  z.object({
1420
1470
  q: z.string().max(200).optional(),
1471
+ outcome: memoryOutcomeSchema.optional(),
1472
+ cursor: cursorSchema.optional(),
1421
1473
  limit: z.coerce.number().int().min(1).max(100).default(20)
1422
1474
  });
1423
1475
  const costPointSchema = z.object({
@@ -1530,7 +1582,7 @@ z.object({
1530
1582
  organizationId: idSchema,
1531
1583
  accountId: idSchema
1532
1584
  });
1533
- z.enum(["monitor", "executor"]);
1585
+ const sessionRoleSchema = z.enum(["monitor", "executor"]);
1534
1586
  const sessionSlotCountSchema = z.int().min(1).max(64);
1535
1587
  const sessionQueueEntrySchema = z.object({
1536
1588
  agentId: idSchema,
@@ -1539,8 +1591,21 @@ const sessionQueueEntrySchema = z.object({
1539
1591
  projectName: z.string().nullable(),
1540
1592
  position: z.int().positive(),
1541
1593
  since: z.iso.datetime(),
1542
- taskId: z.string().nullable()
1594
+ taskId: z.string().nullable(),
1595
+ taskTitle: z.string().nullable(),
1596
+ readable: z.boolean()
1543
1597
  }).meta({ id: "SessionQueueEntry" });
1598
+ const sessionHolderSchema = z.object({
1599
+ agentId: idSchema,
1600
+ agentName: z.string().nullable(),
1601
+ projectId: idSchema.nullable(),
1602
+ projectName: z.string().nullable(),
1603
+ since: z.iso.datetime().nullable(),
1604
+ taskId: z.string().nullable(),
1605
+ taskTitle: z.string().nullable(),
1606
+ readable: z.boolean(),
1607
+ pid: z.int().positive().nullable()
1608
+ }).meta({ id: "SessionHolder" });
1544
1609
  const sessionPoolSchema = z.object({
1545
1610
  slots: sessionSlotCountSchema.nullable(),
1546
1611
  effective: z.int().nonnegative(),
@@ -1567,6 +1632,12 @@ const agentAdmissionSchema = z.object({
1567
1632
  monitor: agentAdmissionStateSchema.nullable(),
1568
1633
  executor: agentAdmissionStateSchema.nullable()
1569
1634
  }).meta({ id: "AgentAdmission" });
1635
+ const admissionHoldSchema = z.object({
1636
+ pools: z.array(sessionRoleSchema),
1637
+ sessions: z.int().nonnegative(),
1638
+ target: z.string().nullable(),
1639
+ updating: z.boolean()
1640
+ }).meta({ id: "AdmissionHold" });
1570
1641
  const nodeStateSchema = z.enum([
1571
1642
  "pending",
1572
1643
  "online",
@@ -1641,6 +1712,7 @@ const nodePoolSchema = z.object({
1641
1712
  slots: z.int().nonnegative(),
1642
1713
  inUse: z.int().nonnegative(),
1643
1714
  waiting: z.array(sessionQueueEntrySchema),
1715
+ holders: z.array(sessionHolderSchema).nullable(),
1644
1716
  usage: poolUsageSchema.nullable()
1645
1717
  }).meta({ id: "NodePool" });
1646
1718
  const nodePoolsSchema = z.object({
@@ -1726,6 +1798,7 @@ const nodeUpdateSchema = z.object({
1726
1798
  target: z.string().nullable(),
1727
1799
  sessions: z.int().nonnegative(),
1728
1800
  since: z.iso.datetime().nullable(),
1801
+ held: z.array(sessionRoleSchema),
1729
1802
  last: z.object({
1730
1803
  outcome: z.enum([
1731
1804
  "updated",
@@ -1860,16 +1933,26 @@ const splitFits = (split, host) => {
1860
1933
  return overflow.cpus <= 0 && overflow.memoryMb <= 0;
1861
1934
  };
1862
1935
  //#endregion
1936
+ //#region ../../packages/contracts/src/appearance.ts
1937
+ const colorSchema = z.string().regex(hexColorPattern);
1938
+ const logoMaxBytes = 524288;
1939
+ const logoHashSchema = z.string().regex(logoHashPattern);
1940
+ z.instanceof(Uint8Array).meta({ description: "The bytes of a PNG, JPEG or WebP image." });
1941
+ z.instanceof(Uint8Array).meta({ description: "The bytes of the WebP image." });
1942
+ z.object({ v: logoHashSchema.optional() });
1943
+ //#endregion
1863
1944
  //#region ../../packages/contracts/src/identity.ts
1864
1945
  const emailSchema = z.email().max(254);
1865
1946
  const passwordSchema = z.string().min(12).max(128);
1866
1947
  const userStatusSchema = z.enum(["active", "disabled"]);
1867
1948
  const organizationNameSchema = z.string().trim().min(2).max(80);
1868
- const organizationSlugSchema = z.string().regex(slugPattern);
1949
+ const organizationSlugSchema = z.string().regex(slugPattern).refine((slug) => !uuidShapePattern.test(slug));
1869
1950
  const organizationSchema = z.object({
1870
1951
  id: idSchema,
1871
1952
  name: z.string(),
1872
1953
  slug: organizationSlugSchema,
1954
+ color: colorSchema,
1955
+ logoHash: logoHashSchema.nullable(),
1873
1956
  createdAt: z.iso.datetime()
1874
1957
  }).meta({ id: "Organization" });
1875
1958
  z.object({ items: z.array(organizationSchema) }).meta({ id: "OrganizationList" });
@@ -1927,6 +2010,11 @@ z.object({
1927
2010
  initialPassword: passwordSchema.optional()
1928
2011
  }).optional()
1929
2012
  }).meta({ id: "CreateOrganizationBody" });
2013
+ const updateOrganizationBodySchema = z.object({
2014
+ name: organizationNameSchema,
2015
+ slug: organizationSlugSchema,
2016
+ color: colorSchema
2017
+ }).meta({ id: "UpdateOrganizationBody" });
1930
2018
  z.object({ temporaryPassword: z.string() }).meta({ id: "TemporaryPassword" });
1931
2019
  z.object({ items: z.array(userSchema) }).meta({ id: "UserList" });
1932
2020
  z.object({ id: idSchema });
@@ -1995,6 +2083,9 @@ const auditActionSchema = z.enum([
1995
2083
  "platform.bootstrapped",
1996
2084
  "organization.created",
1997
2085
  "organization.renamed",
2086
+ "organization.updated",
2087
+ "organization.logo_set",
2088
+ "organization.logo_removed",
1998
2089
  "user.created",
1999
2090
  "user.role_changed",
2000
2091
  "user.disabled",
@@ -2008,6 +2099,8 @@ const auditActionSchema = z.enum([
2008
2099
  "token.revoked",
2009
2100
  "project.created",
2010
2101
  "project.updated",
2102
+ "project.logo_set",
2103
+ "project.logo_removed",
2011
2104
  "project.member_added",
2012
2105
  "project.member_role_changed",
2013
2106
  "project.member_removed",
@@ -2030,6 +2123,7 @@ const auditActionSchema = z.enum([
2030
2123
  "agent.action",
2031
2124
  "agent.backup_requested",
2032
2125
  "agent.restored",
2126
+ "agent.tasks_deleted",
2033
2127
  "platform.settings_updated",
2034
2128
  "runtime.sessions_updated",
2035
2129
  "platform.upgrade_requested",
@@ -2115,7 +2209,6 @@ pageSchema(auditEntrySchema).meta({ id: "AuditPage" });
2115
2209
  //#endregion
2116
2210
  //#region ../../packages/contracts/src/projects.ts
2117
2211
  const slugSchema = z.string().regex(slugPattern);
2118
- const colorSchema = z.string().regex(hexColorPattern);
2119
2212
  const projectSchema = z.object({
2120
2213
  id: idSchema,
2121
2214
  organizationId: idSchema,
@@ -2123,6 +2216,7 @@ const projectSchema = z.object({
2123
2216
  slug: slugSchema,
2124
2217
  description: z.string(),
2125
2218
  color: colorSchema,
2219
+ logoHash: logoHashSchema.nullable(),
2126
2220
  dockerAllowed: z.boolean(),
2127
2221
  memberCount: z.int().nonnegative(),
2128
2222
  createdAt: z.iso.datetime(),
@@ -2551,6 +2645,7 @@ const agentRuntimeSchema = z.object({
2551
2645
  stopMode: agentStopModeSchema.nullable(),
2552
2646
  stopDeadlineAt: z.iso.datetime().nullable(),
2553
2647
  admission: agentAdmissionSchema.nullable(),
2648
+ admissionHold: admissionHoldSchema.nullable(),
2554
2649
  poolLimit: poolLimitSchema.nullable()
2555
2650
  }).meta({ id: "AgentRuntime" });
2556
2651
  const agentListItemSchema = z.object({
@@ -2899,19 +2994,6 @@ const nodeVolumesSchema = z.discriminatedUnion("source", [z.object({
2899
2994
  source: z.literal("ok"),
2900
2995
  items: z.array(nodeVolumeSchema)
2901
2996
  }), unavailableBecause(["not_configured"])]).meta({ id: "NodeVolumes" });
2902
- const dropletSizeSchema = z.object({
2903
- slug: z.string(),
2904
- vcpus: z.int().positive(),
2905
- memoryMb: z.int().positive(),
2906
- diskGb: z.int().positive(),
2907
- priceMonthly: z.number().nonnegative(),
2908
- command: z.string()
2909
- }).meta({ id: "DropletSize" });
2910
- const nodeSizesSchema = z.discriminatedUnion("source", [z.object({
2911
- source: z.literal("ok"),
2912
- items: z.array(dropletSizeSchema)
2913
- }), unavailableBecause(providerUnavailableReasons)]).meta({ id: "NodeSizes" });
2914
- const capacityForecastSchema = z.object({ byDisk: z.int().nonnegative().nullable() }).meta({ id: "CapacityForecast" });
2915
2997
  z.object({
2916
2998
  droplet: nodeDropletSchema,
2917
2999
  volumes: nodeVolumesSchema,
@@ -2920,23 +3002,16 @@ z.object({
2920
3002
  data: storageReadingSchema,
2921
3003
  agents: storageReadingSchema
2922
3004
  }),
2923
- sizes: nodeSizesSchema,
2924
3005
  platform: z.object({
2925
3006
  version: z.string(),
2926
3007
  instance: z.string(),
2927
3008
  scriptsVersion: z.string(),
2928
3009
  dockerVersion: z.string().nullable(),
2929
3010
  dockerRootDir: z.string().nullable()
2930
- }),
2931
- maintenance: z.object({
2932
- quiescedAt: instantSchema$1.nullable(),
2933
- pending: z.array(idSchema)
2934
- }),
2935
- capacity: capacityForecastSchema
3011
+ })
2936
3012
  }).meta({ id: "PlatformNode" });
2937
3013
  z.object({ role: volumeRoleSchema });
2938
3014
  z.object({ sizeGb: z.int().positive() }).meta({ id: "ResizeVolumeBody" });
2939
- z.object({ agents: z.int().nonnegative() }).meta({ id: "QuiesceResult" });
2940
3015
  const requesterSchema = z.object({
2941
3016
  userId: idSchema,
2942
3017
  label: z.string().nullable()
@@ -3040,22 +3115,17 @@ const registryUnavailableSchema = z.object({
3040
3115
  detail: z.string().nullable()
3041
3116
  }).meta({ id: "RegistryUnavailable" });
3042
3117
  const hostOperationsAvailabilities = ["enabled", "disabled"];
3043
- const backupsAvailabilities = ["enabled", "unavailable"];
3044
3118
  const installedImageSchema = z.string().regex(pinnedPlatformImagePattern).nullable();
3045
3119
  z.object({
3046
3120
  version: z.string(),
3121
+ environment: z.string().nullable(),
3047
3122
  apiImage: installedImageSchema,
3048
3123
  webImage: installedImageSchema,
3049
3124
  nodeImage: installedImageSchema,
3050
3125
  latest: z.string().nullable(),
3051
3126
  latestError: registryUnavailableSchema.nullable(),
3052
- versions: z.array(z.string()),
3053
- upgradeable: z.boolean(),
3054
- scriptsVersion: z.string(),
3055
- expectedScriptsVersion: z.string(),
3056
3127
  imagePrefix: z.string(),
3057
3128
  hostOperations: z.enum(hostOperationsAvailabilities),
3058
- backups: z.enum(backupsAvailabilities),
3059
3129
  operations: z.array(hostOperationSchema)
3060
3130
  }).meta({ id: "Platform" });
3061
3131
  const keyVersionSchema = z.int().positive();
@@ -3078,7 +3148,6 @@ z.object({
3078
3148
  versions: z.array(masterKeyVersionSchema),
3079
3149
  rotationPending: z.boolean()
3080
3150
  }).meta({ id: "MasterKey" });
3081
- z.object({ version: z.string().regex(versionTagPattern) }).meta({ id: "UpgradePlatformBody" });
3082
3151
  const metricsRangeSchema = z.enum([
3083
3152
  "1h",
3084
3153
  "24h",
@@ -3159,24 +3228,6 @@ z.object({
3159
3228
  }).meta({ id: "AgentMetrics" });
3160
3229
  z.object({ range: metricsRangeSchema });
3161
3230
  z.object({ range: agentMetricsRangeSchema });
3162
- z.object({
3163
- nodeId: idSchema,
3164
- agentId: idSchema
3165
- });
3166
- z.object({ agentIds: z.array(idSchema).min(1).max(500) });
3167
- z.object({ occupied: z.array(idSchema) });
3168
- z.object({ offset: z.coerce.number().int().nonnegative() });
3169
- z.object({ bytes: z.int().nonnegative() });
3170
- z.object({
3171
- bytes: z.int().nonnegative(),
3172
- sha256: z.string().regex(/^[0-9a-f]{64}$/u)
3173
- });
3174
- z.object({
3175
- bytes: z.int().nonnegative(),
3176
- files: z.int().nonnegative(),
3177
- memoryCheck: z.string()
3178
- });
3179
- z.object({ removed: z.boolean() });
3180
3231
  const notificationKindSchema = z.enum([
3181
3232
  "credential.verification_failed",
3182
3233
  "credential.expiring",
@@ -3332,6 +3383,7 @@ const detailsOf$1 = (body) => {
3332
3383
  const parsed = errorBodySchema.safeParse(body);
3333
3384
  return parsed.success ? parsed.data.details ?? {} : {};
3334
3385
  };
3386
+ const hintText = (hint, details) => typeof hint === "string" ? hint : hint(details);
3335
3387
  //#endregion
3336
3388
  //#region src/client/api.ts
3337
3389
  const defaultTimeoutMs = 3e4;
@@ -3388,7 +3440,7 @@ const createApi = ({ origin, token, fetch = globalThis.fetch, timeoutMs = defaul
3388
3440
  const refused = (action, { error, response }, hints) => {
3389
3441
  const failure = new ApiError(response.status, codeOf(error), action, detailsOf$1(error));
3390
3442
  const hint = failure.code === null ? void 0 : hints[failure.code];
3391
- return hint === void 0 ? failure : new CliError(failure.message, hint, { cause: failure });
3443
+ return hint === void 0 ? failure : new CliError(failure.message, hintText(hint, failure.details), { cause: failure });
3392
3444
  };
3393
3445
  const dataOf = (action, answer, hints = {}) => {
3394
3446
  if (answer.data === void 0) throw refused(action, answer, hints);
@@ -3760,7 +3812,7 @@ const createLoginCommand = (adaptersOf) => defineCommand({
3760
3812
  }
3761
3813
  });
3762
3814
  const authorizeInBrowser = async ({ adapters, api, origin, prompt }) => {
3763
- 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 instance is seeded during kcloud-admin init.` });
3815
+ 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.` });
3764
3816
  prompt.note([
3765
3817
  `Code ${started.userCode}`,
3766
3818
  `Address ${started.verificationUri}`,
@@ -4207,10 +4259,15 @@ const runOf = async (adaptersOf, { settings, context }) => {
4207
4259
  //#region src/commands/agents/shared.ts
4208
4260
  const agentOption = { project: { type: "string" } };
4209
4261
  const agentUsage = [["--project <name>", "the project of the agent, when the agent is named alone"]];
4262
+ const actionsAfterPause = /* @__PURE__ */ new Set([
4263
+ "update",
4264
+ "restart",
4265
+ "resume"
4266
+ ]);
4210
4267
  const agentHints = {
4211
4268
  "agent.not_found": "kcloud agents list --project <name> shows the agents.",
4212
4269
  "project.not_found": "kcloud projects list shows the projects you are a member of.",
4213
- "agent.invalid_transition": "kcloud agents show <agent> lists the actions it takes now.",
4270
+ "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.",
4214
4271
  "agent.busy": "The agent is working on another action; try again in a moment.",
4215
4272
  "concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
4216
4273
  "agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
@@ -4438,12 +4495,39 @@ const enforcementText = ({ state, reason }) => {
4438
4495
  case "unsupported": return `The node holds no pool to a limit: ${reason === null ? "it cannot" : unsupportedReasons[reason] ?? reason}.`;
4439
4496
  }
4440
4497
  };
4441
- const memoryOf = (pool) => {
4498
+ const memoryOf$1 = (pool) => {
4442
4499
  if (pool.usage === null) return "-";
4443
4500
  const { memoryBytes, memoryMaxBytes } = pool.usage;
4444
4501
  return memoryMaxBytes === null ? bytesOf(memoryBytes) : `${bytesOf(memoryBytes)} of ${bytesOf(memoryMaxBytes)}`;
4445
4502
  };
4446
4503
  const roles = ["monitor", "executor"];
4504
+ const workOf = (role, { taskId, taskTitle, readable }) => {
4505
+ if (readable === false) return "another project";
4506
+ if (taskId === null) return role === "monitor" ? "monitor cycle" : "-";
4507
+ return taskTitle == null ? taskId : `${taskId} ${taskTitle}`;
4508
+ };
4509
+ const holdersOf = (pools) => {
4510
+ if (roles.every((role) => pools[role].holders == null)) return null;
4511
+ const held = roles.flatMap((role) => (pools[role].holders ?? []).map((holder) => [
4512
+ role,
4513
+ holder.projectName ?? "-",
4514
+ holder.agentName ?? holder.agentId,
4515
+ workOf(role, holder),
4516
+ holder.since ?? "-",
4517
+ holder.pid == null ? "-" : String(holder.pid)
4518
+ ]));
4519
+ return held.length === 0 ? "No session holds a slot." : renderTable([
4520
+ { header: "HOLDS" },
4521
+ { header: "PROJECT" },
4522
+ { header: "AGENT" },
4523
+ { header: "WORK" },
4524
+ { header: "SINCE" },
4525
+ {
4526
+ header: "PID",
4527
+ align: "right"
4528
+ }
4529
+ ], held);
4530
+ };
4447
4531
  const queueOf = (pools) => {
4448
4532
  const waiting = roles.flatMap((role) => pools[role].waiting.map((entry) => [
4449
4533
  role,
@@ -4451,7 +4535,7 @@ const queueOf = (pools) => {
4451
4535
  entry.projectName ?? "-",
4452
4536
  entry.agentName ?? entry.agentId,
4453
4537
  entry.since,
4454
- entry.taskId ?? "-"
4538
+ workOf(role, entry)
4455
4539
  ]));
4456
4540
  return waiting.length === 0 ? "No session waits for a slot." : renderTable([
4457
4541
  { header: "WAITS FOR" },
@@ -4462,37 +4546,41 @@ const queueOf = (pools) => {
4462
4546
  { header: "PROJECT" },
4463
4547
  { header: "AGENT" },
4464
4548
  { header: "SINCE" },
4465
- { header: "TASK" }
4549
+ { header: "WORK" }
4466
4550
  ], waiting);
4467
4551
  };
4468
- const poolsText = (pools) => [
4469
- enforcementText(pools.enforcement),
4470
- renderTable([
4471
- { header: "POOL" },
4472
- { header: "SLOTS IN USE" },
4473
- {
4474
- header: "CPU IN USE",
4475
- align: "right"
4476
- },
4477
- { header: "MEMORY IN USE" },
4478
- {
4479
- header: "MEMORY KILLS",
4480
- align: "right"
4481
- }
4482
- ], roles.map((role) => {
4483
- const pool = pools[role];
4484
- const cpus = pool.usage?.cpus ?? null;
4485
- return [
4486
- role,
4487
- `${pool.inUse} of ${pool.slots}`,
4488
- cpus === null ? "-" : cpus.toFixed(2),
4489
- memoryOf(pool),
4490
- pool.usage === null ? "-" : String(pool.usage.memoryKills)
4491
- ];
4492
- })),
4493
- "",
4494
- queueOf(pools)
4495
- ].join("\n");
4552
+ const poolsText = (pools) => {
4553
+ const holders = holdersOf(pools);
4554
+ return [
4555
+ enforcementText(pools.enforcement),
4556
+ renderTable([
4557
+ { header: "POOL" },
4558
+ { header: "SLOTS IN USE" },
4559
+ {
4560
+ header: "CPU IN USE",
4561
+ align: "right"
4562
+ },
4563
+ { header: "MEMORY IN USE" },
4564
+ {
4565
+ header: "MEMORY KILLS",
4566
+ align: "right"
4567
+ }
4568
+ ], roles.map((role) => {
4569
+ const pool = pools[role];
4570
+ const cpus = pool.usage?.cpus ?? null;
4571
+ return [
4572
+ role,
4573
+ `${pool.inUse} of ${pool.slots}`,
4574
+ cpus === null ? "-" : cpus.toFixed(2),
4575
+ memoryOf$1(pool),
4576
+ pool.usage === null ? "-" : String(pool.usage.memoryKills)
4577
+ ];
4578
+ })),
4579
+ ...holders === null ? [] : ["", holders],
4580
+ "",
4581
+ queueOf(pools)
4582
+ ].join("\n");
4583
+ };
4496
4584
  //#endregion
4497
4585
  //#region src/commands/agents/plan.ts
4498
4586
  const agentPlanOf = (agent) => ({
@@ -4672,6 +4760,15 @@ const admissionRowsOf = (agent) => {
4672
4760
  const { admission } = agent.runtime;
4673
4761
  return admission === null ? [] : [["monitor slot", slotOf(admission.monitor)], ["executor slot", slotOf(admission.executor)]];
4674
4762
  };
4763
+ const admissionHoldRowsOf = ({ runtime }) => {
4764
+ const hold = runtime.admissionHold;
4765
+ if (hold == null) return [];
4766
+ if (hold.updating) return [["admissions", `held while the node updates to ${hold.target ?? "-"}`]];
4767
+ const all = hold.pools.includes("monitor");
4768
+ const noun = all ? "session" : "executor session";
4769
+ const waits = `${String(hold.sessions)} ${noun}${hold.sessions === 1 ? "" : "s"}`;
4770
+ return [["admissions", `${all ? "held" : "executor held"} for the node update to ${hold.target ?? "-"}, waiting for ${waits}`]];
4771
+ };
4675
4772
  const poolLimitRowsOf = ({ runtime }) => runtime.poolLimit === null ? [] : [["pool limit", poolLimitText(runtime.poolLimit)]];
4676
4773
  const detailsOf = (agent) => [
4677
4774
  renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
@@ -4690,6 +4787,7 @@ const detailsOf = (agent) => [
4690
4787
  ["container", agent.runtime.containerId ?? "none"],
4691
4788
  ["health", agent.runtime.health],
4692
4789
  ...admissionRowsOf(agent),
4790
+ ...admissionHoldRowsOf(agent),
4693
4791
  ...poolLimitRowsOf(agent),
4694
4792
  ["pending", agent.restartRequired ? agent.restartReasons.join(", ") : "-"],
4695
4793
  ["actions", agent.availableActions.join(", ")],
@@ -5025,29 +5123,29 @@ const lifecycleCommands = (adaptersOf) => agentActions.map((action) => defineCom
5025
5123
  }));
5026
5124
  //#endregion
5027
5125
  //#region src/client/pages.ts
5028
- const windowOf = (limit, all, fallback = 50) => {
5126
+ const windowOf = (limit, all, fallback = 50, largest = 200) => {
5029
5127
  if (limit === void 0) return {
5030
5128
  limit: fallback,
5031
5129
  all
5032
5130
  };
5033
5131
  const size = Number(limit);
5034
- if (!/^[0-9]+$/u.test(limit) || size < 1 || size > 200) throw new UsageError(`--limit ${limit} is not a whole number from 1 to 200.`);
5132
+ if (!/^[0-9]+$/u.test(limit) || size < 1 || size > largest) throw new UsageError(`--limit ${limit} is not a whole number from 1 to ${largest}.`);
5035
5133
  return {
5036
5134
  limit: size,
5037
5135
  all
5038
5136
  };
5039
5137
  };
5040
- const collect = async (page, { limit, all }) => {
5041
- const first = await page(null, Math.min(limit, 200));
5138
+ const collect = async (page, { limit, all }, largest = 200) => {
5139
+ const first = await page(null, Math.min(limit, largest));
5042
5140
  if (!all) return first;
5043
5141
  const items = [...first.items];
5044
- let cursor = first.nextCursor;
5045
- while (cursor !== null) {
5046
- const next = await page(cursor, 200);
5047
- items.push(...next.items);
5048
- cursor = next.nextCursor;
5142
+ let last = first;
5143
+ while (last.nextCursor !== null) {
5144
+ last = await page(last.nextCursor, largest);
5145
+ items.push(...last.items);
5049
5146
  }
5050
5147
  return {
5148
+ ...last,
5051
5149
  items,
5052
5150
  nextCursor: null
5053
5151
  };
@@ -5094,6 +5192,43 @@ const transcriptOf = async (api, agentId, sessionId, all) => collect(async (curs
5094
5192
  limit: 200,
5095
5193
  all
5096
5194
  });
5195
+ const memoryOf = (api, agentId, { query, outcome }, window) => collect(async (cursor, limit) => dataOf("search what the agent remembers", await api.GET("/agents/{id}/memory", { params: {
5196
+ path: { id: agentId },
5197
+ query: {
5198
+ limit,
5199
+ ...query === void 0 ? {} : { q: query },
5200
+ ...outcome === void 0 ? {} : { outcome },
5201
+ ...cursor === null ? {} : { cursor }
5202
+ }
5203
+ } }), agentHints), window, 100);
5204
+ const memoryReasons = {
5205
+ stopped: "the agent is not running",
5206
+ unreachable: "the agent did not answer on its control socket",
5207
+ timeout: "the agent did not answer in time"
5208
+ };
5209
+ const unreadReasonOf = (page) => page.staleReason === null ? "the agent did not answer" : memoryReasons[page.staleReason];
5210
+ const memoryText = (page) => {
5211
+ const unread = page.source === "unavailable";
5212
+ if (unread && page.items.length === 0) return `The memory cannot be read: ${unreadReasonOf(page)}.`;
5213
+ if (page.items.length === 0) return "The agent remembers nothing that matches.";
5214
+ return [
5215
+ renderTable([
5216
+ { header: "WHEN" },
5217
+ { header: "OUTCOME" },
5218
+ { header: "TASK" },
5219
+ { header: "TITLE" },
5220
+ { header: "HEADLINE" }
5221
+ ], page.items.map((summary) => [
5222
+ summary.createdAt,
5223
+ summary.ok ? "ok" : "failed",
5224
+ summary.taskId,
5225
+ summary.title,
5226
+ summary.headline
5227
+ ])),
5228
+ ...unread ? ["", `The rest of the memory cannot be read: ${unreadReasonOf(page)}.`] : [],
5229
+ ...page.truncated ? ["", `The agent returns at most 100 summaries, so the list may miss older ones.`] : []
5230
+ ].join("\n");
5231
+ };
5097
5232
  const observabilityCommands = (adaptersOf) => [
5098
5233
  defineCommand({
5099
5234
  name: "sessions",
@@ -5169,7 +5304,9 @@ const observabilityCommands = (adaptersOf) => [
5169
5304
  const run = await runOf(adaptersOf, invocation);
5170
5305
  const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
5171
5306
  const page = await transcriptOf(run.api, agentId, invocation.positionals[1] ?? "", invocation.values.all);
5172
- return printed(run, page.items, (entries) => entries.length === 0 ? "The session left no transcript." : entries.map((entry) => JSON.stringify(entry)).join("\n"));
5307
+ const code = printed(run, page.items, (entries) => entries.length === 0 ? "The session left no transcript." : entries.map((entry) => JSON.stringify(entry)).join("\n"));
5308
+ if (page.nextCursor !== null && !run.settings.json) run.context.terminal.error("More entries follow; --all prints the whole session.");
5309
+ return code;
5173
5310
  }
5174
5311
  }),
5175
5312
  defineCommand({
@@ -5305,44 +5442,35 @@ const observabilityCommands = (adaptersOf) => [
5305
5442
  defineCommand({
5306
5443
  name: "memory",
5307
5444
  summary: "Search what an agent remembers of its tasks.",
5308
- usage: "agents memory <agent> [--project <name>] [--query <text>] [--limit <n>]",
5445
+ usage: "agents memory <agent> [--project <name>] [--query <text>] [--outcome <outcome>] [--limit <n>] [--all]",
5309
5446
  optionsUsage: [
5310
5447
  ...agentUsage,
5311
5448
  ["--query <text>", "only the summaries that match this text"],
5312
- ["--limit <n>", "how many summaries (default: 20)"]
5449
+ ["--outcome <outcome>", `only the summaries of one outcome: ${memoryOutcomes.join(", ")}`],
5450
+ ["--limit <n>", `how many summaries (default: 20)`],
5451
+ allUsage
5313
5452
  ],
5314
5453
  options: {
5315
5454
  ...agentOption,
5455
+ ...pageOptions,
5316
5456
  query: { type: "string" },
5317
- limit: { type: "string" }
5457
+ outcome: { type: "string" }
5318
5458
  },
5319
5459
  positionals: {
5320
5460
  min: 1,
5321
5461
  max: 1
5322
5462
  },
5323
5463
  run: async (invocation) => {
5464
+ const { values } = invocation;
5324
5465
  const run = await runOf(adaptersOf, invocation);
5325
- const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
5326
- const { limit } = windowOf(invocation.values.limit, false, 20);
5327
- const search = invocation.values.query;
5328
- const listed = dataOf("search what the agent remembers", await run.api.GET("/agents/{id}/memory", { params: {
5329
- path: { id: agentId },
5330
- query: {
5331
- limit,
5332
- ...search === void 0 ? {} : { q: search }
5333
- }
5334
- } }), agentHints);
5335
- return printed(run, listed, (shown) => shown.items.length === 0 ? "The agent remembers nothing that matches." : renderTable([
5336
- { header: "WHEN" },
5337
- { header: "OUTCOME" },
5338
- { header: "TITLE" },
5339
- { header: "HEADLINE" }
5340
- ], shown.items.map((summary) => [
5341
- summary.createdAt,
5342
- summary.ok ? "ok" : "failed",
5343
- summary.title,
5344
- summary.headline
5345
- ])));
5466
+ const agentId = await agentFrom(run, invocation.positionals[0], values.project);
5467
+ const outcome = memoryOutcomes.find((candidate) => candidate === values.outcome);
5468
+ 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(", ")}.`);
5469
+ const page = await memoryOf(run.api, agentId, {
5470
+ query: values.query,
5471
+ outcome
5472
+ }, windowOf(values.limit, values.all, 20, 100));
5473
+ return printed(run, page, memoryText);
5346
5474
  }
5347
5475
  })
5348
5476
  ];
@@ -5511,42 +5639,67 @@ const streamOptions = {
5511
5639
  default: false
5512
5640
  },
5513
5641
  since: { type: "string" },
5642
+ before: { type: "string" },
5514
5643
  limit: { type: "string" }
5515
5644
  };
5516
5645
  const streamUsage = [
5517
5646
  ...agentUsage,
5518
5647
  ["-f, --follow", "keep the stream open instead of leaving when the output stops"],
5519
5648
  ["--since <id>", "start after this event instead of at the start of the buffer"],
5520
- ["--limit <n>", "how many events to replay before the live ones"]
5649
+ ["--limit <n>", "how many events to replay before the live ones, or the page size with --before"]
5521
5650
  ];
5522
- const lineOf = (frame) => {
5523
- const parsed = agentEventSummarySchema.safeParse(frame.data);
5524
- if (!parsed.success) return JSON.stringify(frame.data);
5651
+ const beforeUsage = ["--before <id>", "print the stored output before this event and leave, oldest first"];
5652
+ const linePrinter = (json) => (data) => {
5653
+ if (json) return JSON.stringify(data);
5654
+ const parsed = agentEventSummarySchema.safeParse(data);
5655
+ if (!parsed.success) return JSON.stringify(data);
5525
5656
  const event = parsed.data;
5526
5657
  const text = "text" in event.payload && typeof event.payload.text === "string" ? event.payload.text : JSON.stringify(event.payload);
5527
5658
  return `${event.ts} ${event.level.padEnd(5)} ${event.agentRole ?? "-"} ${event.event} ${text}`;
5528
5659
  };
5660
+ const earlierOutput = async (session, agentId, before, limit) => dataOf("read the earlier output of the agent", await session.api.GET("/agents/{id}/live/history", { params: {
5661
+ path: { id: agentId },
5662
+ query: {
5663
+ before,
5664
+ ...limit === void 0 ? {} : { limit: Number(limit) }
5665
+ }
5666
+ } }), agentHints);
5667
+ const olderPageCommand = (reference, project, cursor, limit) => [
5668
+ "kcloud agents logs",
5669
+ reference,
5670
+ ...project === void 0 ? [] : ["--project", project],
5671
+ "--before",
5672
+ cursor,
5673
+ ...limit === void 0 ? [] : ["--limit", limit]
5674
+ ].join(" ");
5529
5675
  const streamCommands = (adaptersOf) => [{
5530
5676
  name: "logs",
5531
5677
  path: "/agents/{id}/live",
5532
5678
  summary: "Print what the agent is writing, and keep printing with --follow.",
5533
- resumable: false
5679
+ resumable: false,
5680
+ pagesBack: true
5534
5681
  }, {
5535
5682
  name: "events",
5536
5683
  path: "/agents/{id}/events",
5537
5684
  summary: "Print the stored events of the agent, and keep printing with --follow.",
5538
- resumable: true
5539
- }].map(({ name, path, summary, resumable }) => defineCommand({
5685
+ resumable: true,
5686
+ pagesBack: false
5687
+ }].map(({ name, path, summary, resumable, pagesBack }) => defineCommand({
5540
5688
  name,
5541
5689
  summary,
5542
- usage: `agents ${name} <agent> [--project <name>] [--follow] [--since <id>] [--limit <n>]`,
5543
- optionsUsage: streamUsage,
5690
+ usage: pagesBack ? `agents ${name} <agent> [--project <name>] [--follow] [--since <id>] [--before <id>] [--limit <n>]` : `agents ${name} <agent> [--project <name>] [--follow] [--since <id>] [--limit <n>]`,
5691
+ optionsUsage: pagesBack ? [...streamUsage, beforeUsage] : streamUsage,
5544
5692
  options: streamOptions,
5545
5693
  positionals: {
5546
5694
  min: 1,
5547
5695
  max: 1
5548
5696
  },
5549
5697
  run: async ({ values, positionals, settings, context }) => {
5698
+ const before = values.before;
5699
+ if (before !== void 0 && !pagesBack) throw new UsageError("--before reads the output of kcloud agents logs alone.");
5700
+ if (before !== void 0 && (values.follow || values.since !== void 0)) throw new UsageError("--before reads earlier output; leave out --follow and --since.");
5701
+ const limit = values.limit;
5702
+ if (limit !== void 0 && !/^[0-9]+$/u.test(limit)) throw new UsageError(`--limit ${limit} is not a whole number.`);
5550
5703
  const adapters = adaptersOf(context);
5551
5704
  const session = await openSession({
5552
5705
  settings,
@@ -5555,8 +5708,17 @@ const streamCommands = (adaptersOf) => [{
5555
5708
  });
5556
5709
  const reference = positionals[0] ?? "";
5557
5710
  const agentId = await resolveAgentId(session, reference, values.project);
5558
- const limit = values.limit;
5559
- if (limit !== void 0 && !/^[0-9]+$/u.test(limit)) throw new UsageError(`--limit ${limit} is not a whole number.`);
5711
+ const lineOf = linePrinter(settings.json);
5712
+ if (before !== void 0) {
5713
+ const page = await earlierOutput(session, agentId, before, limit);
5714
+ if (settings.json) {
5715
+ context.terminal.line(JSON.stringify(page));
5716
+ return exitCodes.ok;
5717
+ }
5718
+ for (const event of page.items) context.terminal.line(lineOf(event));
5719
+ context.terminal.error(context.terminal.errorStyles.dim(page.nextCursor === null ? "The stored output begins here." : `older: ${olderPageCommand(reference, values.project, page.nextCursor, limit)}`));
5720
+ return exitCodes.ok;
5721
+ }
5560
5722
  await streamFrames({
5561
5723
  origin: session.origin,
5562
5724
  token: session.token,
@@ -5575,7 +5737,7 @@ const streamCommands = (adaptersOf) => [{
5575
5737
  },
5576
5738
  onFrame: (frame) => {
5577
5739
  if (frame.event === "agent.state") return;
5578
- context.terminal.line(settings.json ? JSON.stringify(frame.data) : lineOf(frame));
5740
+ context.terminal.line(lineOf(frame.data));
5579
5741
  }
5580
5742
  });
5581
5743
  return exitCodes.ok;
@@ -5585,7 +5747,10 @@ const streamCommands = (adaptersOf) => [{
5585
5747
  //#region src/commands/agents/tasks.ts
5586
5748
  const taskHints = {
5587
5749
  ...agentHints,
5588
- "runtime.control_unreachable": "The worker is not answering; kcloud agents show <agent> tells whether it runs."
5750
+ "runtime.control_unreachable": "The worker is not answering; kcloud agents show <agent> tells whether it runs.",
5751
+ "task.not_found": "kcloud agents tasks list <agent> shows the tasks of the agent.",
5752
+ "task.not_finished": "Only done, failed and cancelled tasks can be deleted; kcloud agents tasks cancel <agent> <task> cancels a pending one.",
5753
+ "runtime.task_deletion_unsupported": `Move the agent to Kikimora ${taskDeletionKikimoraVersion} or newer with kcloud agents set, then kcloud agents update.`
5589
5754
  };
5590
5755
  const tasksCommand = (adaptersOf) => defineGroup({
5591
5756
  name: "tasks",
@@ -5595,11 +5760,21 @@ const tasksCommand = (adaptersOf) => defineGroup({
5595
5760
  defineCommand({
5596
5761
  name: "list",
5597
5762
  summary: "List the tasks of an agent.",
5598
- usage: "agents tasks list <agent> [--project <name>] [--status <status>]",
5599
- optionsUsage: [...agentUsage, ["--status <status>", `one of ${taskStatuses.join(", ")}`]],
5763
+ usage: "agents tasks list <agent> [--project <name>] [--status <status>] [--limit <n>] [--all]",
5764
+ optionsUsage: [
5765
+ ...agentUsage,
5766
+ ["--status <status>", `one of ${taskStatuses.join(", ")}`],
5767
+ ["--limit <n>", `how many tasks, newest first (default: 50)`],
5768
+ ["--all", "follow the cursor to the end instead of one page"]
5769
+ ],
5600
5770
  options: {
5601
5771
  ...agentOption,
5602
- status: { type: "string" }
5772
+ status: { type: "string" },
5773
+ limit: { type: "string" },
5774
+ all: {
5775
+ type: "boolean",
5776
+ default: false
5777
+ }
5603
5778
  },
5604
5779
  positionals: {
5605
5780
  min: 1,
@@ -5611,10 +5786,14 @@ const tasksCommand = (adaptersOf) => defineGroup({
5611
5786
  const given = invocation.values.status;
5612
5787
  const status = taskStatuses.find((candidate) => candidate === given);
5613
5788
  if (given !== void 0 && status === void 0) throw new UsageError(`--status ${given} is not a status of a task.`, `The statuses are ${taskStatuses.join(", ")}.`);
5614
- const listed = dataOf("list the tasks of the agent", await run.api.GET("/agents/{id}/tasks", { params: {
5789
+ const listed = await collect(async (cursor, limit) => dataOf("list the tasks of the agent", await run.api.GET("/agents/{id}/tasks", { params: {
5615
5790
  path: { id: agentId },
5616
- ...status === void 0 ? {} : { query: { status } }
5617
- } }), taskHints);
5791
+ query: {
5792
+ limit,
5793
+ ...cursor === null ? {} : { cursor },
5794
+ ...status === void 0 ? {} : { status }
5795
+ }
5796
+ } }), taskHints), windowOf(invocation.values.limit, invocation.values.all));
5618
5797
  return printed(run, listed, (page) => page.items.length === 0 ? "The agent has no task in its queue." : renderTable([
5619
5798
  { header: "STATUS" },
5620
5799
  { header: "TITLE" },
@@ -5627,12 +5806,42 @@ const tasksCommand = (adaptersOf) => defineGroup({
5627
5806
  ], page.items.map((task) => [
5628
5807
  task.status,
5629
5808
  task.title,
5630
- `${String(task.attempt)}/${String(task.maxAttempts)}`,
5809
+ task.attempt === null ? "-" : task.maxAttempts === null ? String(task.attempt) : `${String(task.attempt)}/${String(task.maxAttempts)}`,
5631
5810
  task.costUsd === null ? "-" : task.costUsd.toFixed(2),
5632
5811
  task.id
5633
5812
  ])));
5634
5813
  }
5635
5814
  }),
5815
+ defineCommand({
5816
+ name: "show",
5817
+ summary: "Show one task of an agent with its description and error.",
5818
+ usage: "agents tasks show <agent> <task> [--project <name>]",
5819
+ optionsUsage: agentUsage,
5820
+ options: agentOption,
5821
+ positionals: {
5822
+ min: 2,
5823
+ max: 2
5824
+ },
5825
+ run: async (invocation) => {
5826
+ const run = await runOf(adaptersOf, invocation);
5827
+ const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
5828
+ const taskId = invocation.positionals[1] ?? "";
5829
+ const view = dataOf("read the task", await run.api.GET("/agents/{id}/tasks/{taskId}", { params: { path: {
5830
+ id: agentId,
5831
+ taskId
5832
+ } } }), taskHints);
5833
+ return printed(run, view, ({ task }) => [
5834
+ `${task.title} (${task.id})`,
5835
+ `status ${task.status}`,
5836
+ `attempt ${task.attempt === null ? "-" : String(task.attempt)}${task.maxAttempts === null ? "" : `/${String(task.maxAttempts)}`}`,
5837
+ `created ${task.createdAt ?? "-"}`,
5838
+ `started ${task.startedAt ?? "-"}`,
5839
+ `finished ${task.finishedAt ?? "-"}`,
5840
+ ...task.error === null ? [] : [`error ${task.error}`],
5841
+ ...task.description === null ? [] : ["", task.description]
5842
+ ].join("\n"));
5843
+ }
5844
+ }),
5636
5845
  defineCommand({
5637
5846
  name: "add",
5638
5847
  summary: "Put a task in the queue of an agent.",
@@ -5712,9 +5921,50 @@ const tasksCommand = (adaptersOf) => defineGroup({
5712
5921
  } } }), taskHints);
5713
5922
  return said(run, `Cancelled the task ${taskId}.`);
5714
5923
  }
5924
+ }),
5925
+ defineCommand({
5926
+ name: "delete",
5927
+ summary: "Delete a finished task, or every task of one final status.",
5928
+ usage: "agents tasks delete <agent> <task> [--project <name>] | agents tasks delete <agent> --status <status> [--project <name>]",
5929
+ optionsUsage: [...agentUsage, ["--status <status>", `one of ${finalTaskStatuses.join(", ")}`]],
5930
+ options: {
5931
+ ...agentOption,
5932
+ status: { type: "string" }
5933
+ },
5934
+ positionals: {
5935
+ min: 1,
5936
+ max: 2
5937
+ },
5938
+ run: async (invocation) => {
5939
+ const taskId = invocation.positionals[1];
5940
+ const given = invocation.values.status;
5941
+ if (taskId === void 0 === (given === void 0)) throw new UsageError("agents tasks delete needs either a task or --status, not both.", "kcloud agents tasks delete <agent> <task> deletes one task.");
5942
+ const status = finalTaskStatuses.find((candidate) => candidate === given);
5943
+ if (given !== void 0 && status === void 0) throw new UsageError(`--status ${given} is not a final status of a task.`, `Only ${finalTaskStatuses.join(", ")} tasks can be deleted.`);
5944
+ const run = await runOf(adaptersOf, invocation);
5945
+ const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
5946
+ await confirmed({
5947
+ prompt: run.prompt,
5948
+ settings: run.settings,
5949
+ question: status === void 0 ? `Delete the task ${taskId ?? ""}?` : `Delete every ${status} task of the agent?${status === "done" ? " The monitor may report them again." : ""}`
5950
+ });
5951
+ const answer = dataOf("delete the tasks", status === void 0 ? await run.api.DELETE("/agents/{id}/tasks/{taskId}", { params: { path: {
5952
+ id: agentId,
5953
+ taskId: taskId ?? ""
5954
+ } } }) : await run.api.DELETE("/agents/{id}/tasks", { params: {
5955
+ path: { id: agentId },
5956
+ query: { status }
5957
+ } }), taskHints);
5958
+ return printed(run, answer, ({ deleted }) => deletedSummary(deleted.length, status ?? null, taskId ?? null));
5959
+ }
5715
5960
  })
5716
5961
  ]
5717
5962
  });
5963
+ const deletedSummary = (count, status, taskId) => {
5964
+ if (status === null) return `Deleted the task ${taskId ?? ""}.`;
5965
+ if (count === 0) return `The agent has no ${status} task.`;
5966
+ return `Deleted ${String(count)} ${status} ${count === 1 ? "task" : "tasks"}.`;
5967
+ };
5718
5968
  const descriptionOf = async (run, given) => {
5719
5969
  if (given === "-") {
5720
5970
  const chunks = [];
@@ -5794,25 +6044,59 @@ const secretUsage = [
5794
6044
  //#region src/commands/credentials.ts
5795
6045
  const projectOption$2 = { project: { type: "string" } };
5796
6046
  const projectUsage$2 = [["--project <name>", "the project the credential belongs to, by slug, name or id"]];
6047
+ const usageUnavailable = "Only a Claude Code OAuth token reports the usage of a subscription.";
5797
6048
  const credentialHints = {
5798
6049
  "project.not_found": "kcloud projects list shows the projects you are a member of.",
5799
6050
  "credential.not_found": "kcloud credentials list --project <name> shows them.",
5800
6051
  "credential.label_taken": "Another credential of the project already has that label.",
5801
- "credential.in_use": "Point the agents at another credential first."
6052
+ "credential.in_use": "Point the agents at another credential first.",
6053
+ "credential.usage_unavailable": usageUnavailable
6054
+ };
6055
+ const percent = 100;
6056
+ const notApplicable = "-";
6057
+ const usedOf = (window) => window === null ? notApplicable : `${String(Math.round(window.utilization * percent))}%`;
6058
+ const shareOf = (window) => window?.status === "rejected" ? "limit" : usedOf(window);
6059
+ const usageSummaryOf = ({ kind, usage }) => {
6060
+ if (kind !== "claude_oauth_token") return notApplicable;
6061
+ if (usage === null) return "no data";
6062
+ const summary = `5h ${shareOf(usage.fiveHour)} 7d ${shareOf(usage.sevenDay)}`;
6063
+ return usage.stale ? `${summary} (stale)` : summary;
6064
+ };
6065
+ const usageReportOf = (credential) => {
6066
+ const { usage } = credential;
6067
+ if (usage === null) return `No usage of ${credential.label} was read yet.`;
6068
+ const windows = [["5h", usage.fiveHour], ["7d", usage.sevenDay]];
6069
+ return `${renderTable([
6070
+ { header: "WINDOW" },
6071
+ { header: "USED" },
6072
+ { header: "STATUS" },
6073
+ { header: "RESETS" }
6074
+ ], windows.map(([name, window]) => [
6075
+ name,
6076
+ usedOf(window),
6077
+ window?.status ?? notApplicable,
6078
+ window?.resetsAt ?? notApplicable
6079
+ ]))}\n${usage.stale ? `Read at ${usage.readAt}; the latest reading failed.` : `Read at ${usage.readAt}.`}`;
6080
+ };
6081
+ const isoDateLength = 10;
6082
+ const expiryOf = ({ expiresAt, expiresAtComputed }) => {
6083
+ if (expiresAt === null) return "never";
6084
+ return expiresAtComputed ? `about ${expiresAt.slice(0, isoDateLength)}` : expiresAt;
5802
6085
  };
5803
6086
  const projectFrom$2 = async (run, given) => {
5804
6087
  if (given === void 0) throw new UsageError("This command needs a project.", "Pass --project <name>; kcloud projects list shows them.");
5805
6088
  return resolveProjectId(run, given);
5806
6089
  };
5807
6090
  const credentialsOf = async (api, projectId) => dataOf("list the credentials of the project", await api.GET("/projects/{id}/credentials", { params: { path: { id: projectId } } }), credentialHints).items;
5808
- const resolveCredentialId = async (api, projectId, reference) => {
6091
+ const resolveCredential = async (api, projectId, reference) => {
5809
6092
  const credentials = await credentialsOf(api, projectId);
5810
6093
  const found = credentials.filter((credential) => credential.id === reference || credential.label === reference);
5811
6094
  const [first] = found;
5812
6095
  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(", ")}.`);
5813
6096
  if (found.length > 1) throw new CliError(`${reference} names ${found.length} credentials of the project.`, "Name it by its id instead.");
5814
- return first.id;
6097
+ return first;
5815
6098
  };
6099
+ const resolveCredentialId = async (api, projectId, reference) => (await resolveCredential(api, projectId, reference)).id;
5816
6100
  const createCredentialsCommand = (adaptersOf) => defineGroup({
5817
6101
  name: "credentials",
5818
6102
  summary: "The Claude and GitHub credentials of a project.",
@@ -5838,13 +6122,15 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
5838
6122
  { header: "FINGERPRINT" },
5839
6123
  { header: "VERIFIED" },
5840
6124
  { header: "EXPIRES" },
6125
+ { header: "USAGE" },
5841
6126
  { header: "ID" }
5842
6127
  ], credentials.map((credential) => [
5843
6128
  credential.label,
5844
6129
  credential.kind,
5845
6130
  credential.fingerprint,
5846
6131
  credential.verification,
5847
- credential.expiresAt ?? "never",
6132
+ expiryOf(credential),
6133
+ usageSummaryOf(credential),
5848
6134
  credential.id
5849
6135
  ])));
5850
6136
  }
@@ -5990,6 +6276,34 @@ const createCredentialsCommand = (adaptersOf) => defineGroup({
5990
6276
  return said(run, `The instance is verifying ${credentialId}; kcloud credentials list shows the answer.`);
5991
6277
  }
5992
6278
  }),
6279
+ defineCommand({
6280
+ name: "usage",
6281
+ summary: "Show the usage of the Claude subscription behind a Claude Code OAuth token.",
6282
+ usage: "credentials usage <credential> --project <name> [--refresh]",
6283
+ optionsUsage: [...projectUsage$2, ["--refresh", "read it from Anthropic now; within 30 seconds the last reading"]],
6284
+ options: {
6285
+ ...projectOption$2,
6286
+ refresh: {
6287
+ type: "boolean",
6288
+ default: false
6289
+ }
6290
+ },
6291
+ positionals: {
6292
+ min: 1,
6293
+ max: 1
6294
+ },
6295
+ run: async (invocation) => {
6296
+ const run = await runOf(adaptersOf, invocation);
6297
+ const projectId = await projectFrom$2(run, invocation.values.project);
6298
+ const stored = await resolveCredential(run.api, projectId, invocation.positionals[0] ?? "");
6299
+ if (stored.kind !== "claude_oauth_token") throw new CliError(`${stored.label} is a ${stored.kind}, which reports no usage.`, usageUnavailable);
6300
+ const credential = invocation.values.refresh ? dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
6301
+ id: projectId,
6302
+ credentialId: stored.id
6303
+ } } }), credentialHints) : stored;
6304
+ return printed(run, credential, usageReportOf);
6305
+ }
6306
+ }),
5993
6307
  defineCommand({
5994
6308
  name: "delete",
5995
6309
  summary: "Delete a credential no agent uses.",
@@ -6851,19 +7165,31 @@ const credentialTools = [
6851
7165
  }),
6852
7166
  defineTool({
6853
7167
  name: "credential_update",
6854
- title: "Rename or verify a credential",
6855
- description: "rename gives a credential a new label; verify asks the instance to check it against its service, and credentials_list shows the answer.",
6856
- operations: ["renameCredential", "verifyCredential"],
7168
+ title: "Rename, verify or read the usage of a credential",
7169
+ 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.",
7170
+ operations: [
7171
+ "renameCredential",
7172
+ "verifyCredential",
7173
+ "refreshCredentialUsage"
7174
+ ],
6857
7175
  safety: "write",
6858
7176
  input: z.object({
6859
7177
  project: projectField,
6860
7178
  credential: credentialField$1,
6861
- action: z.enum(["rename", "verify"]),
7179
+ action: z.enum([
7180
+ "rename",
7181
+ "verify",
7182
+ "refresh_usage"
7183
+ ]),
6862
7184
  label: z.string().trim().min(1).optional().describe("The new label, for rename.")
6863
7185
  }),
6864
7186
  run: async ({ project, credential, action, label }, run) => {
6865
7187
  const projectId = await projectIdOf(run, project);
6866
7188
  const credentialId = await resolveCredentialId(run.api, projectId, credential);
7189
+ if (action === "refresh_usage") return dataOf("read the usage of the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/usage", { params: { path: {
7190
+ id: projectId,
7191
+ credentialId
7192
+ } } }), credentialHints);
6867
7193
  if (action === "verify") {
6868
7194
  doneWith("verify the credential", await run.api.POST("/projects/{id}/credentials/{credentialId}/verify", { params: { path: {
6869
7195
  id: projectId,
@@ -7204,19 +7530,40 @@ const observabilityTools = [
7204
7530
  defineTool({
7205
7531
  name: "agent_memory_search",
7206
7532
  title: "Search the memory of an agent",
7207
- description: "Search what an agent remembers of the tasks it finished.",
7533
+ description: "Search what an agent remembers of the tasks it finished, newest first, one page or every summary with all.",
7208
7534
  operations: ["searchAgentMemory"],
7209
7535
  safety: "read",
7210
7536
  input: z.object({
7211
7537
  ...agentFields,
7212
7538
  query: z.string().max(200).optional(),
7213
- limit: z.int().min(1).max(100).default(20)
7539
+ outcome: memoryOutcomeSchema.optional().describe("Only the summaries of tasks that succeeded or failed."),
7540
+ limit: z.int().min(1).max(100).default(20),
7541
+ all: pageFields.all
7542
+ }),
7543
+ run: async ({ query, outcome, limit, all, ...reference }, run) => memoryOf(run.api, await agentIdOf(run, reference), {
7544
+ query,
7545
+ outcome
7546
+ }, {
7547
+ limit,
7548
+ all
7549
+ })
7550
+ }),
7551
+ defineTool({
7552
+ name: "agent_earlier_output",
7553
+ title: "Read the earlier output of an agent",
7554
+ description: "Read what an agent wrote before an event, oldest first: the newest page without before. Pass nextCursor as before to read the page before that; null means the stored output begins here.",
7555
+ operations: ["listAgentLiveHistory"],
7556
+ safety: "read",
7557
+ input: z.object({
7558
+ ...agentFields,
7559
+ before: z.string().trim().min(1).max(64).optional().describe("End the page before this event id."),
7560
+ limit: z.int().min(1).max(500).optional().describe("How many events to read.")
7214
7561
  }),
7215
- run: async ({ query, limit, ...reference }, run) => dataOf("search what the agent remembers", await run.api.GET("/agents/{id}/memory", { params: {
7562
+ run: async ({ before, limit, ...reference }, run) => dataOf("read the earlier output of the agent", await run.api.GET("/agents/{id}/live/history", { params: {
7216
7563
  path: { id: await agentIdOf(run, reference) },
7217
7564
  query: {
7218
- limit,
7219
- ...query === void 0 ? {} : { q: query }
7565
+ ...before === void 0 ? {} : { before },
7566
+ ...limit === void 0 ? {} : { limit }
7220
7567
  }
7221
7568
  } }), agentHints)
7222
7569
  }),
@@ -7264,6 +7611,70 @@ const observabilityTools = [
7264
7611
  })
7265
7612
  ];
7266
7613
  //#endregion
7614
+ //#region src/commands/logo.ts
7615
+ const logoOptions = {
7616
+ logo: { type: "string" },
7617
+ "no-logo": {
7618
+ type: "boolean",
7619
+ default: false
7620
+ }
7621
+ };
7622
+ 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"]];
7623
+ const logoHints = {
7624
+ "logo.unreadable": "The file has to be a PNG, JPEG or WebP image.",
7625
+ "logo.too_small": "The image needs at least 64 pixels on each side.",
7626
+ "logo.too_large": "The image may have at most 2048 pixels on a side."
7627
+ };
7628
+ const startsWith = (bytes, prefix, at = 0) => prefix.every((byte, index) => bytes[at + index] === byte);
7629
+ const ascii = (text) => Array.from(text, (letter) => letter.charCodeAt(0));
7630
+ const signatures = [
7631
+ {
7632
+ contentType: "image/png",
7633
+ matches: (bytes) => startsWith(bytes, [
7634
+ 137,
7635
+ ...ascii("PNG\r\n"),
7636
+ 26,
7637
+ 10
7638
+ ])
7639
+ },
7640
+ {
7641
+ contentType: "image/jpeg",
7642
+ matches: (bytes) => startsWith(bytes, [
7643
+ 255,
7644
+ 216,
7645
+ 255
7646
+ ])
7647
+ },
7648
+ {
7649
+ contentType: "image/webp",
7650
+ matches: (bytes) => startsWith(bytes, ascii("RIFF")) && startsWith(bytes, ascii("WEBP"), 8)
7651
+ }
7652
+ ];
7653
+ const isLogoChange = (change) => change.logo !== void 0 || change["no-logo"] === true;
7654
+ const logoFileOf = async (workingDirectory, file) => {
7655
+ const path = resolve(workingDirectory, file);
7656
+ const bytes = await readFile(path).catch(() => {
7657
+ throw new CliError(`${path} could not be read.`);
7658
+ });
7659
+ 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.`);
7660
+ const contentType = signatures.find(({ matches }) => matches(bytes))?.contentType;
7661
+ if (contentType === void 0) throw new UsageError(`${file} is not a PNG, JPEG or WebP image.`);
7662
+ return {
7663
+ bytes,
7664
+ contentType
7665
+ };
7666
+ };
7667
+ const logoActionOf = async (workingDirectory, change) => {
7668
+ if (change.logo !== void 0 && change["no-logo"] === true) throw new UsageError("--logo and --no-logo exclude each other.");
7669
+ if (change.logo !== void 0) return logoFileOf(workingDirectory, change.logo);
7670
+ return change["no-logo"] === true ? "remove" : null;
7671
+ };
7672
+ const logoUpload = ({ bytes, contentType }) => ({
7673
+ body: bytes,
7674
+ bodySerializer: (body) => body,
7675
+ headers: { "content-type": contentType }
7676
+ });
7677
+ //#endregion
7267
7678
  //#region src/commands/roles.ts
7268
7679
  const roleOption = { role: { type: "string" } };
7269
7680
  const roleUsage = ["--role <role>", "admin or member"];
@@ -7573,6 +7984,48 @@ const costsText = (costs, { from, to }, slugs) => [
7573
7984
  "",
7574
7985
  `${costs.totalCostUsd.toFixed(2)} over ${costs.totalRuns} runs and ${costs.sessions} sessions.`
7575
7986
  ].join("\n");
7987
+ const organizationChangeHints = {
7988
+ ...organizationHints,
7989
+ "organization.slug_taken": "Another organization already uses that slug.",
7990
+ "organization.invalid_slug": "A slug holds lowercase letters, digits and hyphens.",
7991
+ "auth.forbidden": "Only an admin of the organization changes it.",
7992
+ ...logoHints
7993
+ };
7994
+ const followSlug = async (homeDirectory, origin, before, after) => {
7995
+ const store = await readInstanceStore(homeDirectory);
7996
+ const stored = store.instances[origin];
7997
+ if (stored?.organization !== before.slug) return;
7998
+ await writeInstanceStore(homeDirectory, withInstance(store, origin, {
7999
+ ...stored,
8000
+ organization: after.slug
8001
+ }));
8002
+ };
8003
+ const updatedOrganization = async (api, current, fields) => {
8004
+ if ([
8005
+ fields.name,
8006
+ fields.slug,
8007
+ fields.color
8008
+ ].every((field) => field === void 0)) return current;
8009
+ const body = {
8010
+ name: fields.name ?? current.name,
8011
+ slug: fields.slug ?? current.slug,
8012
+ color: fields.color ?? current.color
8013
+ };
8014
+ validated(updateOrganizationBodySchema, body, "a change to an organization");
8015
+ return dataOf("change the organization", await api.PATCH("/organizations/{organizationId}", {
8016
+ params: { path: { organizationId: current.id } },
8017
+ body
8018
+ }), organizationChangeHints);
8019
+ };
8020
+ const withLogo = async (api, organization, logo) => {
8021
+ const params = { path: { organizationId: organization.id } };
8022
+ if (logo === null) return organization;
8023
+ if (logo === "remove") return dataOf("remove the logo of the organization", await api.DELETE("/organizations/{organizationId}/logo", { params }), organizationChangeHints);
8024
+ return dataOf("set the logo of the organization", await api.PUT("/organizations/{organizationId}/logo", {
8025
+ params,
8026
+ ...logoUpload(logo)
8027
+ }), organizationChangeHints);
8028
+ };
7576
8029
  const createOrgsCommand = (adaptersOf) => defineGroup({
7577
8030
  name: "orgs",
7578
8031
  summary: "The organizations you can see, and the one kcloud acts in.",
@@ -7657,11 +8110,52 @@ const createOrgsCommand = (adaptersOf) => defineGroup({
7657
8110
  return printed(run, organization, (shown) => renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
7658
8111
  ["name", shown.name],
7659
8112
  ["slug", shown.slug],
8113
+ ["color", shown.color],
8114
+ ["logo", shown.logoHash ?? "-"],
7660
8115
  ["created", shown.createdAt],
7661
8116
  ["id", shown.id]
7662
8117
  ]));
7663
8118
  }
7664
8119
  }),
8120
+ defineCommand({
8121
+ name: "set",
8122
+ summary: "Change the name, slug, color or logo of an organization.",
8123
+ usage: "orgs set [<organization>] [--name <name>] [--slug <slug>] [--color <#rrggbb>] [--logo <path> | --no-logo]",
8124
+ optionsUsage: [
8125
+ ["--name <name>", "the new name"],
8126
+ ["--slug <slug>", "the new slug; scripts and --org that name the old one stop finding it"],
8127
+ ["--color <#rrggbb>", "the color of the organization in the panel"],
8128
+ ...logoUsage
8129
+ ],
8130
+ options: {
8131
+ name: { type: "string" },
8132
+ slug: { type: "string" },
8133
+ color: { type: "string" },
8134
+ ...logoOptions
8135
+ },
8136
+ positionals: {
8137
+ min: 0,
8138
+ max: 1
8139
+ },
8140
+ run: async (invocation) => {
8141
+ const { values } = invocation;
8142
+ if ([
8143
+ values.name,
8144
+ values.slug,
8145
+ values.color
8146
+ ].every((value) => value === void 0) && !isLogoChange(values)) throw new UsageError("orgs set needs --name, --slug, --color, --logo or --no-logo.");
8147
+ const logo = await logoActionOf(invocation.context.workingDirectory, values);
8148
+ const run = await runOf(adaptersOf, invocation);
8149
+ const current = await organizationOrInUse(run, invocation.positionals[0]);
8150
+ const updated = await updatedOrganization(run.api, current, values);
8151
+ if (updated.slug !== current.slug) {
8152
+ await followSlug(run.context.homeDirectory, run.origin, current, updated);
8153
+ 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.`));
8154
+ }
8155
+ const organization = await withLogo(run.api, updated, logo);
8156
+ return printed(run, organization, (shown) => `Changed ${shown.name} (${shown.slug}).`);
8157
+ }
8158
+ }),
7665
8159
  defineCommand({
7666
8160
  name: "costs",
7667
8161
  summary: "Show what the organization spent, by day and by project.",
@@ -7726,6 +8220,20 @@ const organizationTools = [
7726
8220
  return dataOf("describe the organization", await run.api.GET("/organizations/{organizationId}", { params: { path: { organizationId: id } } }), organizationHints);
7727
8221
  }
7728
8222
  }),
8223
+ defineTool({
8224
+ name: "organization_update",
8225
+ title: "Change an organization",
8226
+ 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.",
8227
+ operations: ["listOrganizations", "updateOrganization"],
8228
+ safety: "write",
8229
+ idempotent: true,
8230
+ input: z.object({
8231
+ organization: z.string().trim().min(1).optional().describe("The organization, by its slug, its name or its id."),
8232
+ name: organizationNameSchema.optional(),
8233
+ color: colorSchema.optional().describe("The color in the panel, as #rrggbb.")
8234
+ }),
8235
+ run: async ({ organization, ...change }, run) => updatedOrganization(run.api, await organizationOrInUse(run, organization), change)
8236
+ }),
7729
8237
  defineTool({
7730
8238
  name: "organization_costs",
7731
8239
  title: "Show what the organization spent",
@@ -8255,19 +8763,37 @@ const createdProject = async (scope, name, { slug, description, color }) => {
8255
8763
  organization
8256
8764
  };
8257
8765
  };
8258
- const changedProject = async (api, id, change) => {
8259
- const current = await projectOf(api, id);
8260
- const body = {
8261
- name: change.name ?? current.name,
8262
- slug: change.slug ?? current.slug,
8263
- description: change.description ?? current.description,
8264
- color: change.color ?? current.color
8265
- };
8266
- validated(updateProjectBodySchema, body, "a change to a project");
8267
- return dataOf("change the project", await api.PATCH("/projects/{id}", {
8268
- params: { path: { id } },
8269
- body
8270
- }), projectHint);
8766
+ const logoHint = {
8767
+ ...projectHint,
8768
+ ...logoHints
8769
+ };
8770
+ const changedProject = async (api, id, change, logo = null) => {
8771
+ const params = { path: { id } };
8772
+ let project = await projectOf(api, id);
8773
+ if ([
8774
+ change.name,
8775
+ change.slug,
8776
+ change.description,
8777
+ change.color
8778
+ ].some((field) => field !== void 0)) {
8779
+ const body = {
8780
+ name: change.name ?? project.name,
8781
+ slug: change.slug ?? project.slug,
8782
+ description: change.description ?? project.description,
8783
+ color: change.color ?? project.color
8784
+ };
8785
+ validated(updateProjectBodySchema, body, "a change to a project");
8786
+ project = dataOf("change the project", await api.PATCH("/projects/{id}", {
8787
+ params,
8788
+ body
8789
+ }), projectHint);
8790
+ }
8791
+ if (logo !== null && logo !== "remove") project = dataOf("set the logo of the project", await api.PUT("/projects/{id}/logo", {
8792
+ params,
8793
+ ...logoUpload(logo)
8794
+ }), logoHint);
8795
+ if (logo === "remove") project = dataOf("remove the logo of the project", await api.DELETE("/projects/{id}/logo", { params }), logoHint);
8796
+ return project;
8271
8797
  };
8272
8798
  const deleteProject = async (api, id) => {
8273
8799
  doneWith("delete the project", await api.DELETE("/projects/{id}", { params: { path: { id } } }), projectHint);
@@ -8326,6 +8852,7 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
8326
8852
  ["slug", shown.slug],
8327
8853
  ["description", shown.description === "" ? "-" : shown.description],
8328
8854
  ["color", shown.color],
8855
+ ["logo", shown.logoHash ?? "-"],
8329
8856
  ["docker", shown.dockerAllowed ? "allowed" : "not allowed"],
8330
8857
  ["members", String(shown.memberCount)],
8331
8858
  ["created", shown.createdAt],
@@ -8351,12 +8878,17 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
8351
8878
  }),
8352
8879
  defineCommand({
8353
8880
  name: "update",
8354
- summary: "Change the name, slug, description or color of a project.",
8355
- usage: "projects update <project> [--name <name>] [--slug <slug>] [--description <text>] [--color <#rrggbb>]",
8356
- optionsUsage: [["--name <name>", "the new name"], ...fieldUsage],
8881
+ summary: "Change the name, slug, description, color or logo of a project.",
8882
+ usage: "projects update <project> [--name <name>] [--slug <slug>] [--description <text>] [--color <#rrggbb>] [--logo <path> | --no-logo]",
8883
+ optionsUsage: [
8884
+ ["--name <name>", "the new name"],
8885
+ ...fieldUsage,
8886
+ ...logoUsage
8887
+ ],
8357
8888
  options: {
8358
8889
  name: { type: "string" },
8359
- ...fieldOptions
8890
+ ...fieldOptions,
8891
+ ...logoOptions
8360
8892
  },
8361
8893
  positionals: {
8362
8894
  min: 1,
@@ -8369,10 +8901,11 @@ const createProjectsCommand = (adaptersOf) => defineGroup({
8369
8901
  values.slug,
8370
8902
  values.description,
8371
8903
  values.color
8372
- ].every((value) => value === void 0)) throw new UsageError("projects update needs --name, --slug, --description or --color.");
8904
+ ].every((value) => value === void 0) && !isLogoChange(values)) throw new UsageError("projects update needs --name, --slug, --description, --color, --logo or --no-logo.");
8905
+ const logo = await logoActionOf(invocation.context.workingDirectory, values);
8373
8906
  const run = await runOf(adaptersOf, invocation);
8374
8907
  const id = await resolveProjectId(run, invocation.positionals[0] ?? "");
8375
- const project = await changedProject(run.api, id, values);
8908
+ const project = await changedProject(run.api, id, values, logo);
8376
8909
  return printed(run, project, (shown) => `Changed ${shown.name} (${shown.slug}).`);
8377
8910
  }
8378
8911
  }),
@@ -8919,17 +9452,40 @@ const taskTools = [
8919
9452
  defineTool({
8920
9453
  name: "agent_tasks_list",
8921
9454
  title: "List the tasks of an agent",
8922
- description: "List the task queue of an agent, with the status, attempts and cost of each task.",
9455
+ description: "List the task queue of an agent, newest first, with the status, attempts and cost of each task.",
8923
9456
  operations: ["listAgentTasks"],
8924
9457
  safety: "read",
8925
9458
  input: z.object({
8926
9459
  ...agentFields,
9460
+ ...pageFields,
8927
9461
  status: z.enum(taskStatuses).optional()
8928
9462
  }),
8929
- run: async ({ status, ...reference }, run) => dataOf("list the tasks of the agent", await run.api.GET("/agents/{id}/tasks", { params: {
8930
- path: { id: await agentIdOf(run, reference) },
8931
- ...status === void 0 ? {} : { query: { status } }
8932
- } }), taskHints)
9463
+ run: async ({ status, limit, all, ...reference }, run) => {
9464
+ const agentId = await agentIdOf(run, reference);
9465
+ return collect(async (cursor, size) => dataOf("list the tasks of the agent", await run.api.GET("/agents/{id}/tasks", { params: {
9466
+ path: { id: agentId },
9467
+ query: {
9468
+ limit: size,
9469
+ ...cursor === null ? {} : { cursor },
9470
+ ...status === void 0 ? {} : { status }
9471
+ }
9472
+ } }), taskHints), windowOfPage({
9473
+ limit,
9474
+ all
9475
+ }));
9476
+ }
9477
+ }),
9478
+ defineTool({
9479
+ name: "agent_task_show",
9480
+ title: "Show a task",
9481
+ description: "Read one task of an agent with its description, status, attempts, cost and error.",
9482
+ operations: ["getAgentTask"],
9483
+ safety: "read",
9484
+ input: z.object(taskFields),
9485
+ run: async ({ task, ...reference }, run) => dataOf("read the task", await run.api.GET("/agents/{id}/tasks/{taskId}", { params: { path: {
9486
+ id: await agentIdOf(run, reference),
9487
+ taskId: task
9488
+ } } }), taskHints)
8933
9489
  }),
8934
9490
  defineTool({
8935
9491
  name: "agent_task_add",
@@ -8979,6 +9535,33 @@ const taskTools = [
8979
9535
  } } }), taskHints);
8980
9536
  return { cancelled: task };
8981
9537
  }
9538
+ }),
9539
+ defineTool({
9540
+ name: "agent_task_delete",
9541
+ title: "Delete a task",
9542
+ description: "Delete a done, failed or cancelled task of an agent. A monitor may report a deleted done task again.",
9543
+ operations: ["deleteAgentTask"],
9544
+ safety: "destructive",
9545
+ input: z.object(taskFields),
9546
+ run: async ({ task, ...reference }, run) => dataOf("delete the task", await run.api.DELETE("/agents/{id}/tasks/{taskId}", { params: { path: {
9547
+ id: await agentIdOf(run, reference),
9548
+ taskId: task
9549
+ } } }), taskHints)
9550
+ }),
9551
+ defineTool({
9552
+ name: "agent_tasks_delete",
9553
+ title: "Delete the tasks of a status",
9554
+ description: "Delete every task of an agent with one final status: done, failed or cancelled. A monitor may report a deleted done task again.",
9555
+ operations: ["deleteAgentTasks"],
9556
+ safety: "destructive",
9557
+ input: z.object({
9558
+ ...agentFields,
9559
+ status: z.enum(finalTaskStatuses)
9560
+ }),
9561
+ run: async ({ status, ...reference }, run) => dataOf("delete the tasks", await run.api.DELETE("/agents/{id}/tasks", { params: {
9562
+ path: { id: await agentIdOf(run, reference) },
9563
+ query: { status }
9564
+ } }), taskHints)
8982
9565
  })
8983
9566
  ];
8984
9567
  //#endregion
@@ -9110,11 +9693,13 @@ const runtimeRowsOf = (runtime) => [
9110
9693
  ["agents", `${runtime.reconcile.agentsActive} active, ${runtime.reconcile.agentsQueued} queued`],
9111
9694
  ["log readers", String(runtime.ingest.readers)]
9112
9695
  ];
9696
+ const sessionsText = (count, noun) => `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
9697
+ const heldPoolsOf = ({ held = [] }) => held;
9113
9698
  const updateOf = ({ update }) => {
9114
9699
  if (update === null) return "not reported";
9115
9700
  switch (update.state) {
9116
9701
  case "current": return "current";
9117
- case "waiting": return `waits for ${String(update.sessions)} ${update.sessions === 1 ? "session" : "sessions"} to end before ${update.target ?? "-"}`;
9702
+ 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`;
9118
9703
  case "updating": return `updating to ${update.target ?? "-"}`;
9119
9704
  case "failed": return `failed to reach ${update.target ?? "-"}, runs its previous version`;
9120
9705
  case "unmanaged": return "updated with its platform";