@bopen-io/tortuga-plugin 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/worker.js CHANGED
@@ -4,17 +4,17 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
- // node_modules/.pnpm/@paperclipai+plugin-sdk@file+.paperclip-sdk+paperclipai-plugin-sdk-1.0.0.tgz_react@19.2.4/node_modules/@paperclipai/plugin-sdk/dist/define-plugin.js
7
+ // node_modules/@paperclipai/plugin-sdk/dist/define-plugin.js
8
8
  function definePlugin(definition) {
9
9
  return Object.freeze({ definition });
10
10
  }
11
11
 
12
- // node_modules/.pnpm/@paperclipai+plugin-sdk@file+.paperclip-sdk+paperclipai-plugin-sdk-1.0.0.tgz_react@19.2.4/node_modules/@paperclipai/plugin-sdk/dist/worker-rpc-host.js
12
+ // node_modules/@paperclipai/plugin-sdk/dist/worker-rpc-host.js
13
13
  import path from "node:path";
14
14
  import { createInterface } from "node:readline";
15
15
  import { fileURLToPath } from "node:url";
16
16
 
17
- // node_modules/.pnpm/@paperclipai+plugin-sdk@file+.paperclip-sdk+paperclipai-plugin-sdk-1.0.0.tgz_react@19.2.4/node_modules/@paperclipai/plugin-sdk/dist/protocol.js
17
+ // node_modules/@paperclipai/plugin-sdk/dist/protocol.js
18
18
  var JSONRPC_VERSION = "2.0";
19
19
  var JSONRPC_ERROR_CODES = {
20
20
  /** Invalid JSON was received by the server. */
@@ -144,7 +144,7 @@ var JsonRpcCallError = class extends Error {
144
144
  }
145
145
  };
146
146
 
147
- // node_modules/.pnpm/@paperclipai+plugin-sdk@file+.paperclip-sdk+paperclipai-plugin-sdk-1.0.0.tgz_react@19.2.4/node_modules/@paperclipai/plugin-sdk/dist/worker-rpc-host.js
147
+ // node_modules/@paperclipai/plugin-sdk/dist/worker-rpc-host.js
148
148
  var DEFAULT_RPC_TIMEOUT_MS = 3e4;
149
149
  function runWorker(plugin2, moduleUrl, options) {
150
150
  if (options?.stdin != null && options?.stdout != null) {
@@ -860,7 +860,7 @@ function startWorkerRpcHost(options) {
860
860
  };
861
861
  }
862
862
 
863
- // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
863
+ // node_modules/zod/v3/external.js
864
864
  var external_exports = {};
865
865
  __export(external_exports, {
866
866
  BRAND: () => BRAND,
@@ -972,7 +972,7 @@ __export(external_exports, {
972
972
  void: () => voidType
973
973
  });
974
974
 
975
- // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
975
+ // node_modules/zod/v3/helpers/util.js
976
976
  var util;
977
977
  (function(util2) {
978
978
  util2.assertEqual = (_) => {
@@ -1106,7 +1106,7 @@ var getParsedType = (data) => {
1106
1106
  }
1107
1107
  };
1108
1108
 
1109
- // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js
1109
+ // node_modules/zod/v3/ZodError.js
1110
1110
  var ZodIssueCode = util.arrayToEnum([
1111
1111
  "invalid_type",
1112
1112
  "invalid_literal",
@@ -1224,7 +1224,7 @@ ZodError.create = (issues) => {
1224
1224
  return error;
1225
1225
  };
1226
1226
 
1227
- // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js
1227
+ // node_modules/zod/v3/locales/en.js
1228
1228
  var errorMap = (issue, _ctx) => {
1229
1229
  let message;
1230
1230
  switch (issue.code) {
@@ -1327,7 +1327,7 @@ var errorMap = (issue, _ctx) => {
1327
1327
  };
1328
1328
  var en_default = errorMap;
1329
1329
 
1330
- // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js
1330
+ // node_modules/zod/v3/errors.js
1331
1331
  var overrideErrorMap = en_default;
1332
1332
  function setErrorMap(map) {
1333
1333
  overrideErrorMap = map;
@@ -1336,7 +1336,7 @@ function getErrorMap() {
1336
1336
  return overrideErrorMap;
1337
1337
  }
1338
1338
 
1339
- // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
1339
+ // node_modules/zod/v3/helpers/parseUtil.js
1340
1340
  var makeIssue = (params) => {
1341
1341
  const { data, path: path2, errorMaps, issueData } = params;
1342
1342
  const fullPath = [...path2, ...issueData.path || []];
@@ -1446,14 +1446,14 @@ var isDirty = (x) => x.status === "dirty";
1446
1446
  var isValid = (x) => x.status === "valid";
1447
1447
  var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
1448
1448
 
1449
- // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
1449
+ // node_modules/zod/v3/helpers/errorUtil.js
1450
1450
  var errorUtil;
1451
1451
  (function(errorUtil2) {
1452
1452
  errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
1453
1453
  errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
1454
1454
  })(errorUtil || (errorUtil = {}));
1455
1455
 
1456
- // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
1456
+ // node_modules/zod/v3/types.js
1457
1457
  var ParseInputLazyPath = class {
1458
1458
  constructor(parent, value, path2, key) {
1459
1459
  this._cachedPath = [];
@@ -4901,7 +4901,7 @@ var coerce = {
4901
4901
  };
4902
4902
  var NEVER = INVALID;
4903
4903
 
4904
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/constants.js
4904
+ // node_modules/@paperclipai/shared/dist/constants.js
4905
4905
  var COMPANY_STATUSES = ["active", "paused", "archived"];
4906
4906
  var DEPLOYMENT_MODES = ["local_trusted", "authenticated"];
4907
4907
  var DEPLOYMENT_EXPOSURES = ["private", "public"];
@@ -5168,7 +5168,7 @@ var PLUGIN_STATE_SCOPE_KINDS = [
5168
5168
  "run"
5169
5169
  ];
5170
5170
 
5171
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/company.js
5171
+ // node_modules/@paperclipai/shared/dist/validators/company.js
5172
5172
  var createCompanySchema = external_exports.object({
5173
5173
  name: external_exports.string().min(1),
5174
5174
  description: external_exports.string().optional().nullable(),
@@ -5181,7 +5181,7 @@ var updateCompanySchema = createCompanySchema.partial().extend({
5181
5181
  brandColor: external_exports.string().regex(/^#[0-9a-fA-F]{6}$/).nullable().optional()
5182
5182
  });
5183
5183
 
5184
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/company-portability.js
5184
+ // node_modules/@paperclipai/shared/dist/validators/company-portability.js
5185
5185
  var portabilityIncludeSchema = external_exports.object({
5186
5186
  company: external_exports.boolean().optional(),
5187
5187
  agents: external_exports.boolean().optional()
@@ -5271,7 +5271,7 @@ var companyPortabilityPreviewSchema = external_exports.object({
5271
5271
  collisionStrategy: portabilityCollisionStrategySchema.optional()
5272
5272
  });
5273
5273
 
5274
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/secret.js
5274
+ // node_modules/@paperclipai/shared/dist/validators/secret.js
5275
5275
  var envBindingPlainSchema = external_exports.object({
5276
5276
  type: external_exports.literal("plain"),
5277
5277
  value: external_exports.string()
@@ -5304,7 +5304,7 @@ var updateSecretSchema = external_exports.object({
5304
5304
  externalRef: external_exports.string().optional().nullable()
5305
5305
  });
5306
5306
 
5307
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/agent.js
5307
+ // node_modules/@paperclipai/shared/dist/validators/agent.js
5308
5308
  var agentPermissionsSchema = external_exports.object({
5309
5309
  canCreateAgents: external_exports.boolean().optional().default(false)
5310
5310
  });
@@ -5369,7 +5369,7 @@ var updateAgentPermissionsSchema = external_exports.object({
5369
5369
  canCreateAgents: external_exports.boolean()
5370
5370
  });
5371
5371
 
5372
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/project.js
5372
+ // node_modules/@paperclipai/shared/dist/validators/project.js
5373
5373
  var executionWorkspaceStrategySchema = external_exports.object({
5374
5374
  type: external_exports.enum(["project_primary", "git_worktree"]).optional(),
5375
5375
  baseRef: external_exports.string().optional().nullable(),
@@ -5432,7 +5432,7 @@ var createProjectSchema = external_exports.object({
5432
5432
  });
5433
5433
  var updateProjectSchema = external_exports.object(projectFields).partial();
5434
5434
 
5435
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/issue.js
5435
+ // node_modules/@paperclipai/shared/dist/validators/issue.js
5436
5436
  var executionWorkspaceStrategySchema2 = external_exports.object({
5437
5437
  type: external_exports.enum(["project_primary", "git_worktree"]).optional(),
5438
5438
  baseRef: external_exports.string().optional().nullable(),
@@ -5500,7 +5500,7 @@ var upsertIssueDocumentSchema = external_exports.object({
5500
5500
  baseRevisionId: external_exports.string().uuid().nullable().optional()
5501
5501
  });
5502
5502
 
5503
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/goal.js
5503
+ // node_modules/@paperclipai/shared/dist/validators/goal.js
5504
5504
  var createGoalSchema = external_exports.object({
5505
5505
  title: external_exports.string().min(1),
5506
5506
  description: external_exports.string().optional().nullable(),
@@ -5511,7 +5511,7 @@ var createGoalSchema = external_exports.object({
5511
5511
  });
5512
5512
  var updateGoalSchema = createGoalSchema.partial();
5513
5513
 
5514
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/approval.js
5514
+ // node_modules/@paperclipai/shared/dist/validators/approval.js
5515
5515
  var createApprovalSchema = external_exports.object({
5516
5516
  type: external_exports.enum(APPROVAL_TYPES),
5517
5517
  requestedByAgentId: external_exports.string().uuid().optional().nullable(),
@@ -5533,7 +5533,7 @@ var addApprovalCommentSchema = external_exports.object({
5533
5533
  body: external_exports.string().min(1)
5534
5534
  });
5535
5535
 
5536
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/cost.js
5536
+ // node_modules/@paperclipai/shared/dist/validators/cost.js
5537
5537
  var createCostEventSchema = external_exports.object({
5538
5538
  agentId: external_exports.string().uuid(),
5539
5539
  issueId: external_exports.string().uuid().optional().nullable(),
@@ -5551,12 +5551,12 @@ var updateBudgetSchema = external_exports.object({
5551
5551
  budgetMonthlyCents: external_exports.number().int().nonnegative()
5552
5552
  });
5553
5553
 
5554
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/asset.js
5554
+ // node_modules/@paperclipai/shared/dist/validators/asset.js
5555
5555
  var createAssetImageMetadataSchema = external_exports.object({
5556
5556
  namespace: external_exports.string().trim().min(1).max(120).regex(/^[a-zA-Z0-9/_-]+$/).optional()
5557
5557
  });
5558
5558
 
5559
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/access.js
5559
+ // node_modules/@paperclipai/shared/dist/validators/access.js
5560
5560
  var createCompanyInviteSchema = external_exports.object({
5561
5561
  allowedJoinTypes: external_exports.enum(INVITE_JOIN_TYPES).default("both"),
5562
5562
  defaultsPayload: external_exports.record(external_exports.string(), external_exports.unknown()).optional().nullable(),
@@ -5595,7 +5595,7 @@ var updateUserCompanyAccessSchema = external_exports.object({
5595
5595
  companyIds: external_exports.array(external_exports.string().uuid()).default([])
5596
5596
  });
5597
5597
 
5598
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/validators/plugin.js
5598
+ // node_modules/@paperclipai/shared/dist/validators/plugin.js
5599
5599
  var jsonSchemaSchema = external_exports.record(external_exports.unknown()).refine((val) => {
5600
5600
  if (Object.keys(val).length === 0)
5601
5601
  return true;
@@ -5964,7 +5964,7 @@ var listPluginStateSchema = external_exports.object({
5964
5964
  namespace: external_exports.string().min(1).optional()
5965
5965
  });
5966
5966
 
5967
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/api.js
5967
+ // node_modules/@paperclipai/shared/dist/api.js
5968
5968
  var API_PREFIX = "/api";
5969
5969
  var API = {
5970
5970
  health: `${API_PREFIX}/health`,
@@ -5985,7 +5985,7 @@ var API = {
5985
5985
  admin: `${API_PREFIX}/admin`
5986
5986
  };
5987
5987
 
5988
- // node_modules/.pnpm/@paperclipai+shared@file+.paperclip-sdk+paperclipai-shared-0.3.1.tgz/node_modules/@paperclipai/shared/dist/config-schema.js
5988
+ // node_modules/@paperclipai/shared/dist/config-schema.js
5989
5989
  var configMetaSchema = external_exports.object({
5990
5990
  version: external_exports.literal(1),
5991
5991
  updatedAt: external_exports.string(),
@@ -6125,24 +6125,490 @@ var paperclipConfigSchema = external_exports.object({
6125
6125
  }
6126
6126
  });
6127
6127
 
6128
+ // src/constants.ts
6129
+ var JOB_KEYS = {
6130
+ fleetHealth: "fleet-health"
6131
+ };
6132
+ var STREAM_CHANNELS = {
6133
+ fleetStatus: "fleet-status"
6134
+ };
6135
+
6128
6136
  // src/worker.ts
6129
- var plugin = definePlugin({
6130
- async setup(ctx) {
6131
- ctx.events.on("issue.created", async (event) => {
6132
- const issueId = event.entityId ?? "unknown";
6133
- await ctx.state.set({ scopeKind: "issue", scopeId: issueId, stateKey: "seen" }, true);
6134
- ctx.logger.info("Observed issue.created", { issueId });
6137
+ var STATE_KEYS = {
6138
+ /** Per-agent health snapshot: status, lastHeartbeat, run counts */
6139
+ agentHealth: "health",
6140
+ /** Per-agent last-run timestamp */
6141
+ lastRun: "last-run",
6142
+ /** Per-agent run counters: started, completed, failed */
6143
+ runCounts: "run-counts",
6144
+ /** Instance-level: last fleet health check timestamp */
6145
+ lastHealthCheck: "last-health-check"
6146
+ };
6147
+ var DATA_KEYS = {
6148
+ fleetOverview: "fleet-overview",
6149
+ agentDetail: "agent-detail"
6150
+ };
6151
+ var ACTION_KEYS = {
6152
+ pauseAgent: "pause-agent",
6153
+ resumeAgent: "resume-agent",
6154
+ invokeAgent: "invoke-agent"
6155
+ };
6156
+ var currentContext = null;
6157
+ var openStreams = /* @__PURE__ */ new Set();
6158
+ function summarizeError(error) {
6159
+ if (error instanceof Error) return error.message;
6160
+ return String(error);
6161
+ }
6162
+ function requireParam(params, key) {
6163
+ const value = params[key];
6164
+ if (typeof value !== "string" || value.length === 0) {
6165
+ throw new Error(`${key} is required`);
6166
+ }
6167
+ return value;
6168
+ }
6169
+ function classifyHealth(agent) {
6170
+ const terminalStatuses = ["terminated", "error"];
6171
+ if (terminalStatuses.includes(agent.status)) return "error";
6172
+ if (agent.status === "paused" || agent.status === "pending_approval") {
6173
+ return "degraded";
6174
+ }
6175
+ if (agent.lastHeartbeatAt) {
6176
+ const age = Date.now() - new Date(agent.lastHeartbeatAt).getTime();
6177
+ const fifteenMinutes = 15 * 60 * 1e3;
6178
+ if (age > fifteenMinutes) return "degraded";
6179
+ }
6180
+ return "healthy";
6181
+ }
6182
+ async function getRunCounts(ctx, agentId) {
6183
+ const counts = await ctx.state.get({
6184
+ scopeKind: "agent",
6185
+ scopeId: agentId,
6186
+ stateKey: STATE_KEYS.runCounts
6187
+ });
6188
+ return counts ?? { started: 0, completed: 0, failed: 0 };
6189
+ }
6190
+ async function setRunCounts(ctx, agentId, counts) {
6191
+ await ctx.state.set(
6192
+ { scopeKind: "agent", scopeId: agentId, stateKey: STATE_KEYS.runCounts },
6193
+ counts
6194
+ );
6195
+ }
6196
+ async function getAgentHealthState(ctx, agentId) {
6197
+ const state = await ctx.state.get({
6198
+ scopeKind: "agent",
6199
+ scopeId: agentId,
6200
+ stateKey: STATE_KEYS.agentHealth
6201
+ });
6202
+ return state ?? null;
6203
+ }
6204
+ async function setAgentHealthState(ctx, agentId, state) {
6205
+ await ctx.state.set(
6206
+ { scopeKind: "agent", scopeId: agentId, stateKey: STATE_KEYS.agentHealth },
6207
+ state
6208
+ );
6209
+ }
6210
+ async function performFleetHealthCheck(ctx) {
6211
+ const companies = await ctx.companies.list({ limit: 100 });
6212
+ const allAgents = [];
6213
+ for (const company of companies) {
6214
+ const agents = await ctx.agents.list({ companyId: company.id, limit: 200, offset: 0 });
6215
+ allAgents.push(...agents);
6216
+ }
6217
+ let healthy = 0;
6218
+ let degraded = 0;
6219
+ let errorCount = 0;
6220
+ const changedAgents = [];
6221
+ for (const agent of allAgents) {
6222
+ const health = classifyHealth(agent);
6223
+ const runCounts = await getRunCounts(ctx, agent.id);
6224
+ const previousState = await getAgentHealthState(ctx, agent.id);
6225
+ const newState = {
6226
+ status: agent.status,
6227
+ lastHeartbeatAt: agent.lastHeartbeatAt ? new Date(agent.lastHeartbeatAt).toISOString() : null,
6228
+ lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
6229
+ runCounts,
6230
+ lastRunAt: previousState?.lastRunAt ?? null
6231
+ };
6232
+ if (previousState && previousState.status !== agent.status) {
6233
+ changedAgents.push({
6234
+ agent,
6235
+ previousStatus: previousState.status,
6236
+ health
6237
+ });
6238
+ }
6239
+ await setAgentHealthState(ctx, agent.id, newState);
6240
+ switch (health) {
6241
+ case "healthy":
6242
+ healthy++;
6243
+ break;
6244
+ case "degraded":
6245
+ degraded++;
6246
+ break;
6247
+ case "error":
6248
+ errorCount++;
6249
+ break;
6250
+ }
6251
+ }
6252
+ const result = {
6253
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
6254
+ totalAgents: allAgents.length,
6255
+ healthy,
6256
+ degraded,
6257
+ error: errorCount
6258
+ };
6259
+ await ctx.state.set(
6260
+ { scopeKind: "instance", stateKey: STATE_KEYS.lastHealthCheck },
6261
+ result
6262
+ );
6263
+ if (changedAgents.length > 0) {
6264
+ for (const { agent, previousStatus, health } of changedAgents) {
6265
+ ctx.streams.emit(STREAM_CHANNELS.fleetStatus, {
6266
+ type: "health-check-change",
6267
+ agentId: agent.id,
6268
+ agentName: agent.name,
6269
+ companyId: agent.companyId,
6270
+ previousStatus,
6271
+ currentStatus: agent.status,
6272
+ health,
6273
+ checkedAt: result.checkedAt
6274
+ });
6275
+ }
6276
+ }
6277
+ ctx.streams.emit(STREAM_CHANNELS.fleetStatus, {
6278
+ type: "health-check-complete",
6279
+ ...result
6280
+ });
6281
+ return result;
6282
+ }
6283
+ function registerJobHandlers(ctx) {
6284
+ ctx.jobs.register(JOB_KEYS.fleetHealth, async (job) => {
6285
+ ctx.logger.info("Starting fleet health check", {
6286
+ runId: job.runId,
6287
+ trigger: job.trigger
6288
+ });
6289
+ try {
6290
+ const result = await performFleetHealthCheck(ctx);
6291
+ ctx.logger.info("Fleet health check completed", {
6292
+ runId: job.runId,
6293
+ totalAgents: result.totalAgents,
6294
+ healthy: result.healthy,
6295
+ degraded: result.degraded,
6296
+ error: result.error
6297
+ });
6298
+ } catch (error) {
6299
+ ctx.logger.error("Fleet health check failed", {
6300
+ runId: job.runId,
6301
+ error: summarizeError(error)
6302
+ });
6303
+ throw error;
6304
+ }
6305
+ });
6306
+ }
6307
+ function registerEventHandlers(ctx) {
6308
+ ctx.events.on("agent.status_changed", async (event) => {
6309
+ const agentId = event.entityId;
6310
+ if (!agentId) return;
6311
+ const payload = event.payload;
6312
+ ctx.logger.info("Agent status changed", { agentId, payload });
6313
+ const runCounts = await getRunCounts(ctx, agentId);
6314
+ const previousState = await getAgentHealthState(ctx, agentId);
6315
+ const newStatus = payload.newStatus ?? payload.status ?? previousState?.status ?? "idle";
6316
+ await setAgentHealthState(ctx, agentId, {
6317
+ status: newStatus,
6318
+ lastHeartbeatAt: previousState?.lastHeartbeatAt ?? null,
6319
+ lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
6320
+ runCounts,
6321
+ lastRunAt: previousState?.lastRunAt ?? null
6322
+ });
6323
+ ctx.streams.emit(STREAM_CHANNELS.fleetStatus, {
6324
+ type: "agent-status-changed",
6325
+ agentId,
6326
+ companyId: event.companyId,
6327
+ previousStatus: previousState?.status ?? null,
6328
+ newStatus,
6329
+ occurredAt: event.occurredAt
6135
6330
  });
6136
- ctx.data.register("health", async () => {
6137
- return { status: "ok", checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
6331
+ });
6332
+ ctx.events.on("agent.run.started", async (event) => {
6333
+ const agentId = event.entityId;
6334
+ if (!agentId) return;
6335
+ const payload = event.payload;
6336
+ ctx.logger.info("Agent run started", { agentId, runId: payload.runId });
6337
+ const counts = await getRunCounts(ctx, agentId);
6338
+ counts.started++;
6339
+ await setRunCounts(ctx, agentId, counts);
6340
+ const healthState = await getAgentHealthState(ctx, agentId);
6341
+ if (healthState) {
6342
+ healthState.lastRunAt = event.occurredAt;
6343
+ healthState.runCounts = counts;
6344
+ await setAgentHealthState(ctx, agentId, healthState);
6345
+ }
6346
+ ctx.streams.emit(STREAM_CHANNELS.fleetStatus, {
6347
+ type: "run-started",
6348
+ agentId,
6349
+ companyId: event.companyId,
6350
+ runId: payload.runId,
6351
+ occurredAt: event.occurredAt
6352
+ });
6353
+ });
6354
+ ctx.events.on("agent.run.finished", async (event) => {
6355
+ const agentId = event.entityId;
6356
+ if (!agentId) return;
6357
+ const payload = event.payload;
6358
+ ctx.logger.info("Agent run finished", { agentId, runId: payload.runId });
6359
+ const counts = await getRunCounts(ctx, agentId);
6360
+ counts.completed++;
6361
+ await setRunCounts(ctx, agentId, counts);
6362
+ const healthState = await getAgentHealthState(ctx, agentId);
6363
+ if (healthState) {
6364
+ healthState.lastRunAt = event.occurredAt;
6365
+ healthState.runCounts = counts;
6366
+ await setAgentHealthState(ctx, agentId, healthState);
6367
+ }
6368
+ ctx.streams.emit(STREAM_CHANNELS.fleetStatus, {
6369
+ type: "run-finished",
6370
+ agentId,
6371
+ companyId: event.companyId,
6372
+ runId: payload.runId,
6373
+ occurredAt: event.occurredAt
6374
+ });
6375
+ });
6376
+ ctx.events.on("agent.run.failed", async (event) => {
6377
+ const agentId = event.entityId;
6378
+ if (!agentId) return;
6379
+ const payload = event.payload;
6380
+ ctx.logger.warn("Agent run failed", {
6381
+ agentId,
6382
+ runId: payload.runId,
6383
+ error: payload.error
6384
+ });
6385
+ const counts = await getRunCounts(ctx, agentId);
6386
+ counts.failed++;
6387
+ await setRunCounts(ctx, agentId, counts);
6388
+ const healthState = await getAgentHealthState(ctx, agentId);
6389
+ if (healthState) {
6390
+ healthState.lastRunAt = event.occurredAt;
6391
+ healthState.runCounts = counts;
6392
+ await setAgentHealthState(ctx, agentId, healthState);
6393
+ }
6394
+ ctx.streams.emit(STREAM_CHANNELS.fleetStatus, {
6395
+ type: "run-failed",
6396
+ agentId,
6397
+ companyId: event.companyId,
6398
+ runId: payload.runId,
6399
+ error: payload.error ?? null,
6400
+ occurredAt: event.occurredAt
6138
6401
  });
6139
- ctx.actions.register("ping", async () => {
6140
- ctx.logger.info("Ping action invoked");
6141
- return { pong: true, at: (/* @__PURE__ */ new Date()).toISOString() };
6402
+ });
6403
+ }
6404
+ function registerDataHandlers(ctx) {
6405
+ ctx.data.register(DATA_KEYS.fleetOverview, async (params) => {
6406
+ const companyId = requireParam(params, "companyId");
6407
+ const agents = await ctx.agents.list({ companyId, limit: 200, offset: 0 });
6408
+ const fleet = await Promise.all(
6409
+ agents.map(async (agent) => {
6410
+ const healthState = await getAgentHealthState(ctx, agent.id);
6411
+ const health = classifyHealth(agent);
6412
+ return {
6413
+ id: agent.id,
6414
+ name: agent.name,
6415
+ role: agent.role,
6416
+ title: agent.title,
6417
+ icon: agent.icon,
6418
+ status: agent.status,
6419
+ health,
6420
+ lastHeartbeatAt: agent.lastHeartbeatAt ? new Date(agent.lastHeartbeatAt).toISOString() : null,
6421
+ budgetMonthlyCents: agent.budgetMonthlyCents,
6422
+ spentMonthlyCents: agent.spentMonthlyCents,
6423
+ runCounts: healthState?.runCounts ?? { started: 0, completed: 0, failed: 0 },
6424
+ lastRunAt: healthState?.lastRunAt ?? null
6425
+ };
6426
+ })
6427
+ );
6428
+ let healthy = 0;
6429
+ let degraded = 0;
6430
+ let errorCount = 0;
6431
+ for (const entry of fleet) {
6432
+ switch (entry.health) {
6433
+ case "healthy":
6434
+ healthy++;
6435
+ break;
6436
+ case "degraded":
6437
+ degraded++;
6438
+ break;
6439
+ case "error":
6440
+ errorCount++;
6441
+ break;
6442
+ }
6443
+ }
6444
+ const lastCheck = await ctx.state.get({
6445
+ scopeKind: "instance",
6446
+ stateKey: STATE_KEYS.lastHealthCheck
6142
6447
  });
6448
+ return {
6449
+ totalAgents: agents.length,
6450
+ healthy,
6451
+ degraded,
6452
+ error: errorCount,
6453
+ lastHealthCheckAt: lastCheck?.checkedAt ?? null,
6454
+ fleet
6455
+ };
6456
+ });
6457
+ ctx.data.register(DATA_KEYS.agentDetail, async (params) => {
6458
+ const agentId = requireParam(params, "agentId");
6459
+ const companyId = requireParam(params, "companyId");
6460
+ const agent = await ctx.agents.get(agentId, companyId);
6461
+ if (!agent) {
6462
+ throw new Error(`Agent ${agentId} not found`);
6463
+ }
6464
+ const healthState = await getAgentHealthState(ctx, agentId);
6465
+ const health = classifyHealth(agent);
6466
+ return {
6467
+ id: agent.id,
6468
+ name: agent.name,
6469
+ role: agent.role,
6470
+ title: agent.title,
6471
+ icon: agent.icon,
6472
+ status: agent.status,
6473
+ health,
6474
+ reportsTo: agent.reportsTo,
6475
+ capabilities: agent.capabilities,
6476
+ adapterType: agent.adapterType,
6477
+ budgetMonthlyCents: agent.budgetMonthlyCents,
6478
+ spentMonthlyCents: agent.spentMonthlyCents,
6479
+ lastHeartbeatAt: agent.lastHeartbeatAt ? new Date(agent.lastHeartbeatAt).toISOString() : null,
6480
+ runCounts: healthState?.runCounts ?? { started: 0, completed: 0, failed: 0 },
6481
+ lastRunAt: healthState?.lastRunAt ?? null,
6482
+ lastCheckedAt: healthState?.lastCheckedAt ?? null,
6483
+ createdAt: agent.createdAt ? new Date(agent.createdAt).toISOString() : null,
6484
+ updatedAt: agent.updatedAt ? new Date(agent.updatedAt).toISOString() : null
6485
+ };
6486
+ });
6487
+ }
6488
+ function registerActionHandlers(ctx) {
6489
+ ctx.actions.register(ACTION_KEYS.pauseAgent, async (params) => {
6490
+ const agentId = requireParam(params, "agentId");
6491
+ const companyId = requireParam(params, "companyId");
6492
+ ctx.logger.info("Pausing agent", { agentId, companyId });
6493
+ const agent = await ctx.agents.pause(agentId, companyId);
6494
+ return {
6495
+ ok: true,
6496
+ agentId: agent.id,
6497
+ name: agent.name,
6498
+ status: agent.status
6499
+ };
6500
+ });
6501
+ ctx.actions.register(ACTION_KEYS.resumeAgent, async (params) => {
6502
+ const agentId = requireParam(params, "agentId");
6503
+ const companyId = requireParam(params, "companyId");
6504
+ ctx.logger.info("Resuming agent", { agentId, companyId });
6505
+ const agent = await ctx.agents.resume(agentId, companyId);
6506
+ return {
6507
+ ok: true,
6508
+ agentId: agent.id,
6509
+ name: agent.name,
6510
+ status: agent.status
6511
+ };
6512
+ });
6513
+ ctx.actions.register(ACTION_KEYS.invokeAgent, async (params) => {
6514
+ const agentId = requireParam(params, "agentId");
6515
+ const companyId = requireParam(params, "companyId");
6516
+ const prompt = requireParam(params, "prompt");
6517
+ const reason = typeof params.reason === "string" ? params.reason : void 0;
6518
+ ctx.logger.info("Invoking agent", { agentId, companyId, reason });
6519
+ const { runId } = await ctx.agents.invoke(agentId, companyId, { prompt, reason });
6520
+ return {
6521
+ ok: true,
6522
+ agentId,
6523
+ runId
6524
+ };
6525
+ });
6526
+ }
6527
+ var plugin = definePlugin({
6528
+ async setup(ctx) {
6529
+ currentContext = ctx;
6530
+ ctx.logger.info("Tortuga plugin starting setup");
6531
+ registerJobHandlers(ctx);
6532
+ registerEventHandlers(ctx);
6533
+ registerDataHandlers(ctx);
6534
+ registerActionHandlers(ctx);
6535
+ ctx.logger.info("Tortuga plugin setup complete");
6143
6536
  },
6144
6537
  async onHealth() {
6145
- return { status: "ok", message: "Plugin worker is running" };
6538
+ const ctx = currentContext;
6539
+ if (!ctx) {
6540
+ return { status: "error", message: "Plugin context not initialized" };
6541
+ }
6542
+ try {
6543
+ const lastCheck = await ctx.state.get({
6544
+ scopeKind: "instance",
6545
+ stateKey: STATE_KEYS.lastHealthCheck
6546
+ });
6547
+ if (!lastCheck) {
6548
+ return {
6549
+ status: "degraded",
6550
+ message: "No fleet health check has been performed yet",
6551
+ details: { lastCheck: null }
6552
+ };
6553
+ }
6554
+ const checkAge = Date.now() - new Date(lastCheck.checkedAt).getTime();
6555
+ const fifteenMinutes = 15 * 60 * 1e3;
6556
+ if (checkAge > fifteenMinutes) {
6557
+ return {
6558
+ status: "degraded",
6559
+ message: `Last fleet health check was ${Math.round(checkAge / 6e4)} minutes ago`,
6560
+ details: {
6561
+ lastCheckAt: lastCheck.checkedAt,
6562
+ totalAgents: lastCheck.totalAgents,
6563
+ healthy: lastCheck.healthy,
6564
+ degraded: lastCheck.degraded,
6565
+ error: lastCheck.error,
6566
+ checkAgeMs: checkAge
6567
+ }
6568
+ };
6569
+ }
6570
+ if (lastCheck.error > 0) {
6571
+ return {
6572
+ status: "degraded",
6573
+ message: `${lastCheck.error} agent(s) in error state. ${lastCheck.healthy}/${lastCheck.totalAgents} healthy.`,
6574
+ details: {
6575
+ lastCheckAt: lastCheck.checkedAt,
6576
+ totalAgents: lastCheck.totalAgents,
6577
+ healthy: lastCheck.healthy,
6578
+ degraded: lastCheck.degraded,
6579
+ error: lastCheck.error
6580
+ }
6581
+ };
6582
+ }
6583
+ return {
6584
+ status: "ok",
6585
+ message: `Fleet healthy. ${lastCheck.healthy}/${lastCheck.totalAgents} agents OK.`,
6586
+ details: {
6587
+ lastCheckAt: lastCheck.checkedAt,
6588
+ totalAgents: lastCheck.totalAgents,
6589
+ healthy: lastCheck.healthy,
6590
+ degraded: lastCheck.degraded,
6591
+ error: lastCheck.error
6592
+ }
6593
+ };
6594
+ } catch (error) {
6595
+ return {
6596
+ status: "error",
6597
+ message: `Health check failed: ${summarizeError(error)}`
6598
+ };
6599
+ }
6600
+ },
6601
+ async onShutdown() {
6602
+ if (currentContext) {
6603
+ for (const channel of openStreams) {
6604
+ try {
6605
+ currentContext.streams.close(channel);
6606
+ } catch {
6607
+ }
6608
+ }
6609
+ openStreams.clear();
6610
+ }
6611
+ currentContext = null;
6146
6612
  }
6147
6613
  });
6148
6614
  var worker_default = plugin;