@kikimoradev/kcloud 1.7.1 → 1.9.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 +373 -159
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1466,10 +1466,17 @@ const costQuerySchema = z.object({
1466
1466
  from: z.iso.date(),
1467
1467
  to: z.iso.date()
1468
1468
  });
1469
+ const agentCostSchema = z.object({
1470
+ agentId: idSchema,
1471
+ name: z.string().nullable(),
1472
+ costUsd: z.number().min(0),
1473
+ runs: z.int().nonnegative()
1474
+ }).meta({ id: "AgentCost" });
1469
1475
  const projectCostSchema = z.object({
1470
1476
  projectId: idSchema,
1471
1477
  costUsd: z.number().min(0),
1472
- runs: z.int().nonnegative()
1478
+ runs: z.int().nonnegative(),
1479
+ agents: z.array(agentCostSchema)
1473
1480
  }).meta({ id: "ProjectCost" });
1474
1481
  costSeriesSchema.extend({ projects: z.array(projectCostSchema) }).meta({ id: "OrganizationCosts" });
1475
1482
  pageSchema(z.object({
@@ -2856,6 +2863,8 @@ z.object({
2856
2863
  "agent.state",
2857
2864
  "notification",
2858
2865
  "notification.count",
2866
+ "dashboard.agent",
2867
+ "dashboard.removed",
2859
2868
  "stream.closed",
2860
2869
  "error"
2861
2870
  ]),
@@ -2918,6 +2927,78 @@ z.object({
2918
2927
  contentDigest: agentBackupContentDigestSchema
2919
2928
  });
2920
2929
  z.object({ backupId: idSchema.nullable() });
2930
+ //#endregion
2931
+ //#region ../../packages/contracts/src/agent-phase.ts
2932
+ const agentPhaseKinds = [
2933
+ "offHours",
2934
+ "limitWait",
2935
+ "authBlocked",
2936
+ "queued",
2937
+ "waiting",
2938
+ "patrolling",
2939
+ "sleeping",
2940
+ "working",
2941
+ "summarising"
2942
+ ];
2943
+ const dashboardStatusSchema = z.enum([
2944
+ "working",
2945
+ "waiting",
2946
+ "paused",
2947
+ "stopped",
2948
+ "error",
2949
+ "transitioning"
2950
+ ]);
2951
+ const dashboardFrameNames = [
2952
+ "dashboard.agent",
2953
+ "dashboard.removed",
2954
+ "stream.closed",
2955
+ "error"
2956
+ ];
2957
+ const dashboardActivitySchema = z.object({
2958
+ role: agentRoleSchema,
2959
+ kind: z.enum(agentPhaseKinds),
2960
+ taskId: z.string().nullable(),
2961
+ title: z.string().nullable(),
2962
+ cycle: z.int().nullable(),
2963
+ sessionId: z.string().nullable(),
2964
+ startedAt: z.iso.datetime()
2965
+ }).meta({ id: "DashboardActivity" });
2966
+ const dashboardAgentSchema = z.object({
2967
+ id: idSchema,
2968
+ projectId: idSchema,
2969
+ name: z.string(),
2970
+ slug: slugSchema,
2971
+ state: agentStateSchema,
2972
+ status: dashboardStatusSchema,
2973
+ since: z.iso.datetime(),
2974
+ until: z.iso.datetime().nullable(),
2975
+ failureReason: z.string().nullable(),
2976
+ retryAt: z.iso.datetime().nullable(),
2977
+ activity: dashboardActivitySchema.nullable(),
2978
+ costTodayUsd: z.number().nonnegative()
2979
+ }).meta({ id: "DashboardAgent" });
2980
+ const dashboardProjectSchema = z.object({
2981
+ id: idSchema,
2982
+ name: z.string(),
2983
+ slug: slugSchema,
2984
+ color: colorSchema,
2985
+ logoHash: logoHashSchema.nullable(),
2986
+ agents: z.array(dashboardAgentSchema)
2987
+ }).meta({ id: "DashboardProject" });
2988
+ z.object({
2989
+ day: z.iso.date(),
2990
+ projects: z.array(dashboardProjectSchema)
2991
+ }).meta({ id: "OrganizationDashboard" });
2992
+ const dashboardRemovalSchema = z.object({ agentId: idSchema }).meta({ id: "DashboardRemoval" });
2993
+ z.object({
2994
+ event: z.enum(dashboardFrameNames),
2995
+ data: z.union([
2996
+ dashboardAgentSchema,
2997
+ dashboardRemovalSchema,
2998
+ z.object({ reason: z.string() }),
2999
+ z.object({ code: errorCodeSchema })
3000
+ ])
3001
+ }).meta({ id: "DashboardFrame" });
2921
3002
  [
2922
3003
  "application/vnd.oci.image.index.v1+json",
2923
3004
  "application/vnd.oci.image.manifest.v1+json",
@@ -4207,10 +4288,10 @@ const splitHints = {
4207
4288
  "node.split_exceeds_host": "The split asks for more than the host has; kcloud nodes split <node> shows the host."
4208
4289
  };
4209
4290
  const nodesOf = async (api, organizationId) => dataOf("list the nodes", await api.GET("/organizations/{organizationId}/nodes", { params: { path: { organizationId } } }), nodeHints).items;
4210
- const namesOf = (nodes) => nodes.map(({ name }) => name).join(", ");
4291
+ const namesOf$1 = (nodes) => nodes.map(({ name }) => name).join(", ");
4211
4292
  const nodeIn = (nodes, reference, organization) => {
4212
4293
  const found = nodes.find((node) => node.id === reference.toLowerCase() || node.name === reference);
4213
- if (found === void 0) throw new CliError(`There is no node ${reference} in ${organization}.`, nodes.length === 0 ? "kcloud nodes create <name> creates one." : `kcloud nodes list shows ${namesOf(nodes)}.`);
4294
+ if (found === void 0) throw new CliError(`There is no node ${reference} in ${organization}.`, nodes.length === 0 ? "kcloud nodes create <name> creates one." : `kcloud nodes list shows ${namesOf$1(nodes)}.`);
4214
4295
  return found;
4215
4296
  };
4216
4297
  const resolveNode = async (scope, reference) => {
@@ -5233,6 +5314,284 @@ const createCommand = (adaptersOf) => defineCommand({
5233
5314
  }
5234
5315
  });
5235
5316
  //#endregion
5317
+ //#region src/client/stream.ts
5318
+ const firstBackoffMs = 1e3;
5319
+ const lastBackoffMs = 3e4;
5320
+ const defaultIdleMs = 1e3;
5321
+ const fatalStatuses = /* @__PURE__ */ new Set([
5322
+ 400,
5323
+ 401,
5324
+ 403,
5325
+ 404
5326
+ ]);
5327
+ const fatalReasons = /* @__PURE__ */ new Set(["session.expired", "member.removed"]);
5328
+ const jittered = (milliseconds) => Math.round(milliseconds * (.8 + Math.random() * .2));
5329
+ const nextBackoff = (previous) => Math.min(previous === 0 ? firstBackoffMs : previous * 2, lastBackoffMs);
5330
+ const framesOf = (block) => {
5331
+ let event = "message";
5332
+ let id = null;
5333
+ const data = [];
5334
+ for (const line of block.split("\n")) {
5335
+ if (line === "" || line.startsWith(":")) continue;
5336
+ const separator = line.indexOf(":");
5337
+ const field = separator === -1 ? line : line.slice(0, separator);
5338
+ const value = separator === -1 ? "" : line.slice(separator + 1).trimStart();
5339
+ if (field === "event") event = value;
5340
+ else if (field === "id") id = value;
5341
+ else if (field === "data") data.push(value);
5342
+ }
5343
+ if (data.length === 0) return null;
5344
+ const text = data.join("\n");
5345
+ try {
5346
+ return {
5347
+ event,
5348
+ id,
5349
+ data: JSON.parse(text)
5350
+ };
5351
+ } catch {
5352
+ return {
5353
+ event,
5354
+ id,
5355
+ data: text
5356
+ };
5357
+ }
5358
+ };
5359
+ const readBody = async (response, request, onId) => {
5360
+ const body = response.body;
5361
+ if (body === null) return {
5362
+ closed: null,
5363
+ frames: 0
5364
+ };
5365
+ const reader = body.getReader();
5366
+ const decoder = new TextDecoder();
5367
+ const timer = { current: null };
5368
+ let buffer = "";
5369
+ let frames = 0;
5370
+ let closed = null;
5371
+ const armIdle = () => {
5372
+ if (request.follow) return;
5373
+ if (timer.current !== null) clearTimeout(timer.current);
5374
+ timer.current = setTimeout(() => {
5375
+ reader.cancel();
5376
+ }, request.idleMs ?? 1e3);
5377
+ };
5378
+ const stop = () => {
5379
+ reader.cancel().catch(() => void 0);
5380
+ };
5381
+ request.signal?.addEventListener("abort", stop, { once: true });
5382
+ if (request.signal?.aborted === true) stop();
5383
+ armIdle();
5384
+ try {
5385
+ for (;;) {
5386
+ const chunk = await reader.read().catch((error) => {
5387
+ if (request.signal?.aborted === true) return { done: true };
5388
+ throw error;
5389
+ });
5390
+ if (chunk.done) break;
5391
+ buffer += decoder.decode(chunk.value, { stream: true });
5392
+ let boundary = buffer.indexOf("\n\n");
5393
+ while (boundary !== -1) {
5394
+ const block = buffer.slice(0, boundary);
5395
+ buffer = buffer.slice(boundary + 2);
5396
+ const frame = framesOf(block);
5397
+ if (frame !== null) {
5398
+ frames += 1;
5399
+ if (frame.id !== null) onId(frame.id);
5400
+ if (frame.event === "stream.closed") closed = reasonOf(frame.data);
5401
+ else if (frame.event === "error") throw new CliError(`${request.action} failed on the instance: ${codeOf(frame.data) ?? "internal.error"}.`);
5402
+ else {
5403
+ request.onFrame(frame);
5404
+ armIdle();
5405
+ }
5406
+ }
5407
+ boundary = buffer.indexOf("\n\n");
5408
+ }
5409
+ if (closed !== null) break;
5410
+ }
5411
+ } finally {
5412
+ if (timer.current !== null) clearTimeout(timer.current);
5413
+ request.signal?.removeEventListener("abort", stop);
5414
+ }
5415
+ return {
5416
+ closed,
5417
+ frames
5418
+ };
5419
+ };
5420
+ const reasonOf = (data) => {
5421
+ if (typeof data === "object" && data !== null && "reason" in data) {
5422
+ const { reason } = data;
5423
+ if (typeof reason === "string") return reason;
5424
+ }
5425
+ return "unknown";
5426
+ };
5427
+ const streamFrames = async (request) => {
5428
+ const fetching = request.fetch ?? globalThis.fetch;
5429
+ const query = new URLSearchParams(request.query ?? {});
5430
+ const url = `${request.origin}/api/v1${request.path}${query.size === 0 ? "" : `?${query.toString()}`}`;
5431
+ const resume = { lastEventId: null };
5432
+ let backoff = 0;
5433
+ const aborted = () => request.signal?.aborted === true;
5434
+ for (;;) {
5435
+ if (aborted()) return;
5436
+ let response;
5437
+ try {
5438
+ response = await fetching(url, {
5439
+ headers: {
5440
+ accept: "text/event-stream",
5441
+ authorization: `Bearer ${request.token}`,
5442
+ ...request.resumable === true && resume.lastEventId !== null ? { "last-event-id": resume.lastEventId } : {}
5443
+ },
5444
+ redirect: "error",
5445
+ ...request.signal === void 0 ? {} : { signal: request.signal }
5446
+ });
5447
+ } catch (error) {
5448
+ if (aborted()) return;
5449
+ throw unreachable(request.origin, error);
5450
+ }
5451
+ if (!response.ok) {
5452
+ const body = await response.json().catch(() => void 0);
5453
+ const failure = new ApiError(response.status, codeOf(body), request.action, detailsOf$1(body));
5454
+ if (fatalStatuses.has(response.status) || !request.follow) throw failure;
5455
+ request.onNotice?.(`${failure.message} Waiting to try again.`);
5456
+ backoff = nextBackoff(backoff);
5457
+ await request.wait(jittered(backoff));
5458
+ continue;
5459
+ }
5460
+ backoff = 0;
5461
+ const { closed } = await readBody(response, request, (id) => {
5462
+ resume.lastEventId = id;
5463
+ });
5464
+ if (closed !== null && fatalReasons.has(closed)) throw new CliError(`The instance closed the stream: ${closed}.`, closed === "session.expired" ? "The token is no longer valid; run kcloud login again." : "You are no longer a member of that project.");
5465
+ if (!request.follow || aborted()) return;
5466
+ request.onNotice?.(closed === null ? "The stream ended; opening it again." : `The instance closed the stream: ${closed}. Opening it again.`);
5467
+ backoff = nextBackoff(backoff);
5468
+ await request.wait(jittered(backoff));
5469
+ }
5470
+ };
5471
+ //#endregion
5472
+ //#region src/commands/agents/dashboard.ts
5473
+ const noTask = "-";
5474
+ const workKinds = /* @__PURE__ */ new Set([
5475
+ "working",
5476
+ "patrolling",
5477
+ "summarising"
5478
+ ]);
5479
+ const organizationDashboardOf = async (api, organizationId) => dataOf("show the dashboard of the organization", await api.GET("/organizations/{organizationId}/dashboard", { params: { path: { organizationId } } }), organizationHints);
5480
+ const statusOf = (agent) => agent.status === "transitioning" ? `${agent.status} (${agent.state})` : agent.status;
5481
+ const taskOf = (agent) => {
5482
+ const { activity } = agent;
5483
+ if (activity === null) return noTask;
5484
+ if (!workKinds.has(activity.kind)) return agent.until === null ? activity.kind : `${activity.kind} until ${agent.until}`;
5485
+ if (activity.kind === "patrolling") return activity.cycle === null ? "cycle" : `cycle ${activity.cycle}`;
5486
+ if (activity.kind === "summarising") return "summary";
5487
+ return activity.title ?? activity.taskId ?? noTask;
5488
+ };
5489
+ const dashboardText = (dashboard) => {
5490
+ const rows = dashboard.projects.flatMap((project) => project.agents.map((agent) => [
5491
+ project.slug,
5492
+ agent.slug,
5493
+ statusOf(agent),
5494
+ agent.since,
5495
+ taskOf(agent),
5496
+ agent.costTodayUsd.toFixed(2)
5497
+ ]));
5498
+ return rows.length === 0 ? `No agent in the projects you can see on ${dashboard.day}.` : renderTable([
5499
+ { header: "PROJECT" },
5500
+ { header: "AGENT" },
5501
+ { header: "STATUS" },
5502
+ { header: "SINCE" },
5503
+ { header: "TASK" },
5504
+ {
5505
+ header: "COST TODAY",
5506
+ align: "right"
5507
+ }
5508
+ ], rows);
5509
+ };
5510
+ const namesOf = (dashboard) => ({
5511
+ projects: new Map(dashboard.projects.map(({ id, slug }) => [id, slug])),
5512
+ agents: new Map(dashboard.projects.flatMap((project) => project.agents.map(({ id, slug }) => [id, {
5513
+ projectId: project.id,
5514
+ slug
5515
+ }])))
5516
+ });
5517
+ const frameLineOf = (names, frame, at) => {
5518
+ const changed = dashboardAgentSchema.safeParse(frame.data);
5519
+ if (frame.event === "dashboard.agent" && changed.success) {
5520
+ const agent = changed.data;
5521
+ names.agents.set(agent.id, {
5522
+ projectId: agent.projectId,
5523
+ slug: agent.slug
5524
+ });
5525
+ const project = names.projects.get(agent.projectId) ?? agent.projectId;
5526
+ return `${at.toISOString()} ${project}/${agent.slug} ${statusOf(agent)} ${taskOf(agent)}`;
5527
+ }
5528
+ const removed = dashboardRemovalSchema.safeParse(frame.data);
5529
+ if (frame.event === "dashboard.removed" && removed.success) {
5530
+ const known = names.agents.get(removed.data.agentId);
5531
+ names.agents.delete(removed.data.agentId);
5532
+ const project = known === void 0 ? "-" : names.projects.get(known.projectId) ?? known.projectId;
5533
+ return `${at.toISOString()} ${project}/${known?.slug ?? removed.data.agentId} removed ${noTask}`;
5534
+ }
5535
+ return null;
5536
+ };
5537
+ const dashboardCommand = (adaptersOf) => defineCommand({
5538
+ name: "dashboard",
5539
+ summary: "Show what each agent of the organization is doing now, by project.",
5540
+ usage: "agents dashboard [--follow]",
5541
+ optionsUsage: [["-f, --follow", "keep printing a line for each change of an agent after the table"]],
5542
+ options: { follow: {
5543
+ type: "boolean",
5544
+ short: "f",
5545
+ default: false
5546
+ } },
5547
+ positionals: {
5548
+ min: 0,
5549
+ max: 0
5550
+ },
5551
+ run: async ({ values, settings, context }) => {
5552
+ const adapters = adaptersOf(context);
5553
+ const session = await openSession({
5554
+ settings,
5555
+ context,
5556
+ api: adapters.api
5557
+ });
5558
+ const { id } = await session.organization();
5559
+ const dashboard = await organizationDashboardOf(session.api, id);
5560
+ if (!values.follow) {
5561
+ if (settings.json) context.terminal.json(dashboard);
5562
+ else context.terminal.line(dashboardText(dashboard));
5563
+ return exitCodes.ok;
5564
+ }
5565
+ context.terminal.line(settings.json ? JSON.stringify(dashboard) : dashboardText(dashboard));
5566
+ const names = namesOf(dashboard);
5567
+ await streamFrames({
5568
+ origin: session.origin,
5569
+ token: session.token,
5570
+ path: `/organizations/${encodeURIComponent(id)}/dashboard/stream`,
5571
+ follow: true,
5572
+ resumable: false,
5573
+ idleMs: defaultIdleMs,
5574
+ action: "follow the dashboard of the organization",
5575
+ wait: adapters.wait,
5576
+ onNotice: (text) => {
5577
+ context.terminal.error(context.terminal.errorStyles.dim(text));
5578
+ },
5579
+ onFrame: (frame) => {
5580
+ if (settings.json) {
5581
+ context.terminal.line(JSON.stringify({
5582
+ event: frame.event,
5583
+ data: frame.data
5584
+ }));
5585
+ return;
5586
+ }
5587
+ const line = frameLineOf(names, frame, adapters.now());
5588
+ if (line !== null) context.terminal.line(line);
5589
+ }
5590
+ });
5591
+ return exitCodes.ok;
5592
+ }
5593
+ });
5594
+ //#endregion
5236
5595
  //#region src/commands/agents/lifecycle.ts
5237
5596
  const summaries = {
5238
5597
  start: "Start the agent and let it work.",
@@ -5645,161 +6004,6 @@ const observabilityCommands = (adaptersOf) => [
5645
6004
  })
5646
6005
  ];
5647
6006
  //#endregion
5648
- //#region src/client/stream.ts
5649
- const firstBackoffMs = 1e3;
5650
- const lastBackoffMs = 3e4;
5651
- const defaultIdleMs = 1e3;
5652
- const fatalStatuses = /* @__PURE__ */ new Set([
5653
- 400,
5654
- 401,
5655
- 403,
5656
- 404
5657
- ]);
5658
- const fatalReasons = /* @__PURE__ */ new Set(["session.expired", "member.removed"]);
5659
- const jittered = (milliseconds) => Math.round(milliseconds * (.8 + Math.random() * .2));
5660
- const nextBackoff = (previous) => Math.min(previous === 0 ? firstBackoffMs : previous * 2, lastBackoffMs);
5661
- const framesOf = (block) => {
5662
- let event = "message";
5663
- let id = null;
5664
- const data = [];
5665
- for (const line of block.split("\n")) {
5666
- if (line === "" || line.startsWith(":")) continue;
5667
- const separator = line.indexOf(":");
5668
- const field = separator === -1 ? line : line.slice(0, separator);
5669
- const value = separator === -1 ? "" : line.slice(separator + 1).trimStart();
5670
- if (field === "event") event = value;
5671
- else if (field === "id") id = value;
5672
- else if (field === "data") data.push(value);
5673
- }
5674
- if (data.length === 0) return null;
5675
- const text = data.join("\n");
5676
- try {
5677
- return {
5678
- event,
5679
- id,
5680
- data: JSON.parse(text)
5681
- };
5682
- } catch {
5683
- return {
5684
- event,
5685
- id,
5686
- data: text
5687
- };
5688
- }
5689
- };
5690
- const readBody = async (response, request, onId) => {
5691
- const body = response.body;
5692
- if (body === null) return {
5693
- closed: null,
5694
- frames: 0
5695
- };
5696
- const reader = body.getReader();
5697
- const decoder = new TextDecoder();
5698
- const timer = { current: null };
5699
- let buffer = "";
5700
- let frames = 0;
5701
- let closed = null;
5702
- const armIdle = () => {
5703
- if (request.follow) return;
5704
- if (timer.current !== null) clearTimeout(timer.current);
5705
- timer.current = setTimeout(() => {
5706
- reader.cancel();
5707
- }, request.idleMs ?? 1e3);
5708
- };
5709
- const stop = () => {
5710
- reader.cancel().catch(() => void 0);
5711
- };
5712
- request.signal?.addEventListener("abort", stop, { once: true });
5713
- if (request.signal?.aborted === true) stop();
5714
- armIdle();
5715
- try {
5716
- for (;;) {
5717
- const chunk = await reader.read().catch((error) => {
5718
- if (request.signal?.aborted === true) return { done: true };
5719
- throw error;
5720
- });
5721
- if (chunk.done) break;
5722
- buffer += decoder.decode(chunk.value, { stream: true });
5723
- let boundary = buffer.indexOf("\n\n");
5724
- while (boundary !== -1) {
5725
- const block = buffer.slice(0, boundary);
5726
- buffer = buffer.slice(boundary + 2);
5727
- const frame = framesOf(block);
5728
- if (frame !== null) {
5729
- frames += 1;
5730
- if (frame.id !== null) onId(frame.id);
5731
- if (frame.event === "stream.closed") closed = reasonOf(frame.data);
5732
- else if (frame.event === "error") throw new CliError(`${request.action} failed on the instance: ${codeOf(frame.data) ?? "internal.error"}.`);
5733
- else {
5734
- request.onFrame(frame);
5735
- armIdle();
5736
- }
5737
- }
5738
- boundary = buffer.indexOf("\n\n");
5739
- }
5740
- if (closed !== null) break;
5741
- }
5742
- } finally {
5743
- if (timer.current !== null) clearTimeout(timer.current);
5744
- request.signal?.removeEventListener("abort", stop);
5745
- }
5746
- return {
5747
- closed,
5748
- frames
5749
- };
5750
- };
5751
- const reasonOf = (data) => {
5752
- if (typeof data === "object" && data !== null && "reason" in data) {
5753
- const { reason } = data;
5754
- if (typeof reason === "string") return reason;
5755
- }
5756
- return "unknown";
5757
- };
5758
- const streamFrames = async (request) => {
5759
- const fetching = request.fetch ?? globalThis.fetch;
5760
- const query = new URLSearchParams(request.query ?? {});
5761
- const url = `${request.origin}/api/v1${request.path}${query.size === 0 ? "" : `?${query.toString()}`}`;
5762
- const resume = { lastEventId: null };
5763
- let backoff = 0;
5764
- const aborted = () => request.signal?.aborted === true;
5765
- for (;;) {
5766
- if (aborted()) return;
5767
- let response;
5768
- try {
5769
- response = await fetching(url, {
5770
- headers: {
5771
- accept: "text/event-stream",
5772
- authorization: `Bearer ${request.token}`,
5773
- ...request.resumable === true && resume.lastEventId !== null ? { "last-event-id": resume.lastEventId } : {}
5774
- },
5775
- redirect: "error",
5776
- ...request.signal === void 0 ? {} : { signal: request.signal }
5777
- });
5778
- } catch (error) {
5779
- if (aborted()) return;
5780
- throw unreachable(request.origin, error);
5781
- }
5782
- if (!response.ok) {
5783
- const body = await response.json().catch(() => void 0);
5784
- const failure = new ApiError(response.status, codeOf(body), request.action, detailsOf$1(body));
5785
- if (fatalStatuses.has(response.status) || !request.follow) throw failure;
5786
- request.onNotice?.(`${failure.message} Waiting to try again.`);
5787
- backoff = nextBackoff(backoff);
5788
- await request.wait(jittered(backoff));
5789
- continue;
5790
- }
5791
- backoff = 0;
5792
- const { closed } = await readBody(response, request, (id) => {
5793
- resume.lastEventId = id;
5794
- });
5795
- if (closed !== null && fatalReasons.has(closed)) throw new CliError(`The instance closed the stream: ${closed}.`, closed === "session.expired" ? "The token is no longer valid; run kcloud login again." : "You are no longer a member of that project.");
5796
- if (!request.follow || aborted()) return;
5797
- request.onNotice?.(closed === null ? "The stream ended; opening it again." : `The instance closed the stream: ${closed}. Opening it again.`);
5798
- backoff = nextBackoff(backoff);
5799
- await request.wait(jittered(backoff));
5800
- }
5801
- };
5802
- //#endregion
5803
6007
  //#region src/commands/agents/streams.ts
5804
6008
  const streamOptions = {
5805
6009
  ...agentOption,
@@ -6165,7 +6369,8 @@ const createAgentsCommand = (adaptersOf) => defineGroup({
6165
6369
  backupsCommand(adaptersOf),
6166
6370
  restoreCommand(adaptersOf),
6167
6371
  ...observabilityCommands(adaptersOf),
6168
- ...streamCommands(adaptersOf)
6372
+ ...streamCommands(adaptersOf),
6373
+ dashboardCommand(adaptersOf)
6169
6374
  ]
6170
6375
  });
6171
6376
  //#endregion
@@ -8567,6 +8772,15 @@ const organizationTools = [
8567
8772
  return organizationCostsOf(run.api, id, costWindowFrom(from, to, run.now()));
8568
8773
  }
8569
8774
  }),
8775
+ defineTool({
8776
+ name: "organization_dashboard",
8777
+ title: "Show what each agent of the organization is doing",
8778
+ description: "Show the agents of the organization this server acts in, by project, each with its status and since when, the task or the cycle it works on, and what it spent today, a UTC day. A member sees the projects they belong to.",
8779
+ operations: ["getOrganizationDashboard"],
8780
+ safety: "read",
8781
+ input: z.object({}),
8782
+ run: async (_, run) => organizationDashboardOf(run.api, (await run.organization()).id)
8783
+ }),
8570
8784
  defineTool({
8571
8785
  name: "organization_members_list",
8572
8786
  title: "List the members of the organization",
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
18
- "version": "1.7.1",
18
+ "version": "1.9.0",
19
19
  "dependencies": {
20
20
  "@clack/prompts": "1.8.1",
21
21
  "@modelcontextprotocol/server": "2.3.0",