@action-llama/action-llama 0.2.0 → 0.4.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 (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -90
  3. package/dist/agents/container-entry.js +183 -43
  4. package/dist/agents/container-entry.js.map +1 -1
  5. package/dist/agents/container-runner.d.ts +11 -4
  6. package/dist/agents/container-runner.d.ts.map +1 -1
  7. package/dist/agents/container-runner.js +107 -99
  8. package/dist/agents/container-runner.js.map +1 -1
  9. package/dist/agents/prompt.d.ts +2 -0
  10. package/dist/agents/prompt.d.ts.map +1 -1
  11. package/dist/agents/prompt.js +18 -10
  12. package/dist/agents/prompt.js.map +1 -1
  13. package/dist/agents/runner.d.ts +10 -1
  14. package/dist/agents/runner.d.ts.map +1 -1
  15. package/dist/agents/runner.js +95 -9
  16. package/dist/agents/runner.js.map +1 -1
  17. package/dist/cli/commands/cloud-setup.d.ts +4 -0
  18. package/dist/cli/commands/cloud-setup.d.ts.map +1 -0
  19. package/dist/cli/commands/cloud-setup.js +565 -0
  20. package/dist/cli/commands/cloud-setup.js.map +1 -0
  21. package/dist/cli/commands/cloud-teardown.d.ts +6 -0
  22. package/dist/cli/commands/cloud-teardown.d.ts.map +1 -0
  23. package/dist/cli/commands/cloud-teardown.js +152 -0
  24. package/dist/cli/commands/cloud-teardown.js.map +1 -0
  25. package/dist/cli/commands/{setup.d.ts → console.d.ts} +1 -1
  26. package/dist/cli/commands/console.d.ts.map +1 -0
  27. package/dist/cli/commands/console.js +273 -0
  28. package/dist/cli/commands/console.js.map +1 -0
  29. package/dist/cli/commands/creds.d.ts +2 -0
  30. package/dist/cli/commands/creds.d.ts.map +1 -0
  31. package/dist/cli/commands/creds.js +62 -0
  32. package/dist/cli/commands/creds.js.map +1 -0
  33. package/dist/cli/commands/doctor.d.ts +7 -0
  34. package/dist/cli/commands/doctor.d.ts.map +1 -0
  35. package/dist/cli/commands/doctor.js +405 -0
  36. package/dist/cli/commands/doctor.js.map +1 -0
  37. package/dist/cli/commands/logs.d.ts +1 -0
  38. package/dist/cli/commands/logs.d.ts.map +1 -1
  39. package/dist/cli/commands/logs.js +67 -0
  40. package/dist/cli/commands/logs.js.map +1 -1
  41. package/dist/cli/commands/new.d.ts.map +1 -1
  42. package/dist/cli/commands/new.js +30 -28
  43. package/dist/cli/commands/new.js.map +1 -1
  44. package/dist/cli/commands/run.d.ts +6 -0
  45. package/dist/cli/commands/run.d.ts.map +1 -0
  46. package/dist/cli/commands/run.js +121 -0
  47. package/dist/cli/commands/run.js.map +1 -0
  48. package/dist/cli/commands/start.d.ts +2 -1
  49. package/dist/cli/commands/start.d.ts.map +1 -1
  50. package/dist/cli/commands/start.js +41 -14
  51. package/dist/cli/commands/start.js.map +1 -1
  52. package/dist/cli/commands/status.d.ts +1 -0
  53. package/dist/cli/commands/status.d.ts.map +1 -1
  54. package/dist/cli/commands/status.js +39 -2
  55. package/dist/cli/commands/status.js.map +1 -1
  56. package/dist/cli/main.js +61 -12
  57. package/dist/cli/main.js.map +1 -1
  58. package/dist/credentials/builtins/anthropic-key.d.ts.map +1 -1
  59. package/dist/credentials/builtins/anthropic-key.js +3 -4
  60. package/dist/credentials/builtins/anthropic-key.js.map +1 -1
  61. package/dist/credentials/builtins/aws.d.ts +4 -0
  62. package/dist/credentials/builtins/aws.d.ts.map +1 -0
  63. package/dist/credentials/builtins/aws.js +33 -0
  64. package/dist/credentials/builtins/aws.js.map +1 -0
  65. package/dist/credentials/builtins/bugsnag-token.d.ts +4 -0
  66. package/dist/credentials/builtins/bugsnag-token.d.ts.map +1 -0
  67. package/dist/credentials/builtins/bugsnag-token.js +18 -0
  68. package/dist/credentials/builtins/bugsnag-token.js.map +1 -0
  69. package/dist/credentials/builtins/github-token.d.ts.map +1 -1
  70. package/dist/credentials/builtins/github-token.js +1 -2
  71. package/dist/credentials/builtins/github-token.js.map +1 -1
  72. package/dist/credentials/builtins/github-webhook-secret.js +3 -3
  73. package/dist/credentials/builtins/github-webhook-secret.js.map +1 -1
  74. package/dist/credentials/builtins/id-rsa.d.ts +2 -2
  75. package/dist/credentials/builtins/id-rsa.d.ts.map +1 -1
  76. package/dist/credentials/builtins/id-rsa.js +75 -47
  77. package/dist/credentials/builtins/id-rsa.js.map +1 -1
  78. package/dist/credentials/builtins/index.d.ts.map +1 -1
  79. package/dist/credentials/builtins/index.js +17 -7
  80. package/dist/credentials/builtins/index.js.map +1 -1
  81. package/dist/credentials/builtins/netlify-token.d.ts +4 -0
  82. package/dist/credentials/builtins/netlify-token.d.ts.map +1 -0
  83. package/dist/credentials/builtins/netlify-token.js +18 -0
  84. package/dist/credentials/builtins/netlify-token.js.map +1 -0
  85. package/dist/credentials/builtins/openai-key.d.ts +4 -0
  86. package/dist/credentials/builtins/openai-key.d.ts.map +1 -0
  87. package/dist/credentials/builtins/openai-key.js +38 -0
  88. package/dist/credentials/builtins/openai-key.js.map +1 -0
  89. package/dist/credentials/builtins/sentry-client-secret.d.ts.map +1 -1
  90. package/dist/credentials/builtins/sentry-client-secret.js +1 -2
  91. package/dist/credentials/builtins/sentry-client-secret.js.map +1 -1
  92. package/dist/credentials/builtins/sentry-token.d.ts.map +1 -1
  93. package/dist/credentials/builtins/sentry-token.js +2 -3
  94. package/dist/credentials/builtins/sentry-token.js.map +1 -1
  95. package/dist/credentials/builtins/x-twitter-api.d.ts +4 -0
  96. package/dist/credentials/builtins/x-twitter-api.d.ts.map +1 -0
  97. package/dist/credentials/builtins/x-twitter-api.js +28 -0
  98. package/dist/credentials/builtins/x-twitter-api.js.map +1 -0
  99. package/dist/credentials/prompter.d.ts +1 -1
  100. package/dist/credentials/prompter.d.ts.map +1 -1
  101. package/dist/credentials/prompter.js +14 -21
  102. package/dist/credentials/prompter.js.map +1 -1
  103. package/dist/credentials/schema.d.ts +0 -1
  104. package/dist/credentials/schema.d.ts.map +1 -1
  105. package/dist/credentials/schema.js +2 -3
  106. package/dist/credentials/schema.js.map +1 -1
  107. package/dist/docker/cloud-run-runtime.d.ts +61 -0
  108. package/dist/docker/cloud-run-runtime.d.ts.map +1 -0
  109. package/dist/docker/cloud-run-runtime.js +510 -0
  110. package/dist/docker/cloud-run-runtime.js.map +1 -0
  111. package/dist/docker/ecs-runtime.d.ts +73 -0
  112. package/dist/docker/ecs-runtime.d.ts.map +1 -0
  113. package/dist/docker/ecs-runtime.js +596 -0
  114. package/dist/docker/ecs-runtime.js.map +1 -0
  115. package/dist/docker/image.d.ts +8 -0
  116. package/dist/docker/image.d.ts.map +1 -1
  117. package/dist/docker/image.js +28 -3
  118. package/dist/docker/image.js.map +1 -1
  119. package/dist/docker/local-runtime.d.ts +19 -0
  120. package/dist/docker/local-runtime.d.ts.map +1 -0
  121. package/dist/docker/local-runtime.js +209 -0
  122. package/dist/docker/local-runtime.js.map +1 -0
  123. package/dist/docker/network.d.ts +1 -1
  124. package/dist/docker/network.d.ts.map +1 -1
  125. package/dist/docker/network.js +2 -1
  126. package/dist/docker/network.js.map +1 -1
  127. package/dist/docker/runtime.d.ts +90 -0
  128. package/dist/docker/runtime.d.ts.map +1 -0
  129. package/dist/docker/runtime.js +2 -0
  130. package/dist/docker/runtime.js.map +1 -0
  131. package/dist/gateway/index.d.ts +8 -2
  132. package/dist/gateway/index.d.ts.map +1 -1
  133. package/dist/gateway/index.js +16 -8
  134. package/dist/gateway/index.js.map +1 -1
  135. package/dist/gateway/routes/credentials.d.ts +5 -0
  136. package/dist/gateway/routes/credentials.d.ts.map +1 -0
  137. package/dist/gateway/routes/credentials.js +17 -0
  138. package/dist/gateway/routes/credentials.js.map +1 -0
  139. package/dist/gateway/routes/logs.d.ts +5 -0
  140. package/dist/gateway/routes/logs.d.ts.map +1 -0
  141. package/dist/gateway/routes/logs.js +31 -0
  142. package/dist/gateway/routes/logs.js.map +1 -0
  143. package/dist/gateway/routes/shutdown.d.ts +2 -1
  144. package/dist/gateway/routes/shutdown.d.ts.map +1 -1
  145. package/dist/gateway/routes/shutdown.js +7 -16
  146. package/dist/gateway/routes/shutdown.js.map +1 -1
  147. package/dist/gateway/routes/webhooks.d.ts +2 -1
  148. package/dist/gateway/routes/webhooks.d.ts.map +1 -1
  149. package/dist/gateway/routes/webhooks.js +11 -4
  150. package/dist/gateway/routes/webhooks.js.map +1 -1
  151. package/dist/gateway/types.d.ts +6 -0
  152. package/dist/gateway/types.d.ts.map +1 -0
  153. package/dist/gateway/types.js +2 -0
  154. package/dist/gateway/types.js.map +1 -0
  155. package/dist/scheduler/index.d.ts +3 -2
  156. package/dist/scheduler/index.d.ts.map +1 -1
  157. package/dist/scheduler/index.js +299 -59
  158. package/dist/scheduler/index.js.map +1 -1
  159. package/dist/setup/prompts.d.ts.map +1 -1
  160. package/dist/setup/prompts.js +14 -21
  161. package/dist/setup/prompts.js.map +1 -1
  162. package/dist/setup/scaffold.d.ts +2 -2
  163. package/dist/setup/scaffold.d.ts.map +1 -1
  164. package/dist/setup/scaffold.js +369 -27
  165. package/dist/setup/scaffold.js.map +1 -1
  166. package/dist/setup/validators.d.ts +14 -0
  167. package/dist/setup/validators.d.ts.map +1 -1
  168. package/dist/setup/validators.js +53 -0
  169. package/dist/setup/validators.js.map +1 -1
  170. package/dist/shared/asm-backend.d.ts +25 -0
  171. package/dist/shared/asm-backend.d.ts.map +1 -0
  172. package/dist/shared/asm-backend.js +107 -0
  173. package/dist/shared/asm-backend.js.map +1 -0
  174. package/dist/shared/aws-constants.d.ts +55 -0
  175. package/dist/shared/aws-constants.d.ts.map +1 -0
  176. package/dist/shared/aws-constants.js +55 -0
  177. package/dist/shared/aws-constants.js.map +1 -0
  178. package/dist/shared/config.d.ts +25 -5
  179. package/dist/shared/config.d.ts.map +1 -1
  180. package/dist/shared/config.js +15 -22
  181. package/dist/shared/config.js.map +1 -1
  182. package/dist/shared/credential-backend.d.ts +28 -0
  183. package/dist/shared/credential-backend.d.ts.map +1 -0
  184. package/dist/shared/credential-backend.js +2 -0
  185. package/dist/shared/credential-backend.js.map +1 -0
  186. package/dist/shared/credentials.d.ts +75 -5
  187. package/dist/shared/credentials.d.ts.map +1 -1
  188. package/dist/shared/credentials.js +141 -24
  189. package/dist/shared/credentials.js.map +1 -1
  190. package/dist/shared/filesystem-backend.d.ts +18 -0
  191. package/dist/shared/filesystem-backend.d.ts.map +1 -0
  192. package/dist/shared/filesystem-backend.js +86 -0
  193. package/dist/shared/filesystem-backend.js.map +1 -0
  194. package/dist/shared/git.js +1 -1
  195. package/dist/shared/git.js.map +1 -1
  196. package/dist/shared/gsm-backend.d.ts +35 -0
  197. package/dist/shared/gsm-backend.d.ts.map +1 -0
  198. package/dist/shared/gsm-backend.js +208 -0
  199. package/dist/shared/gsm-backend.js.map +1 -0
  200. package/dist/shared/remote.d.ts +11 -0
  201. package/dist/shared/remote.d.ts.map +1 -0
  202. package/dist/shared/remote.js +29 -0
  203. package/dist/shared/remote.js.map +1 -0
  204. package/dist/tui/App.d.ts.map +1 -1
  205. package/dist/tui/App.js +22 -7
  206. package/dist/tui/App.js.map +1 -1
  207. package/dist/tui/status-tracker.d.ts +6 -3
  208. package/dist/tui/status-tracker.d.ts.map +1 -1
  209. package/dist/tui/status-tracker.js +14 -2
  210. package/dist/tui/status-tracker.js.map +1 -1
  211. package/dist/webhooks/definitions/github.js +1 -1
  212. package/dist/webhooks/definitions/sentry.js +1 -1
  213. package/dist/webhooks/providers/github.d.ts +1 -1
  214. package/dist/webhooks/providers/github.d.ts.map +1 -1
  215. package/dist/webhooks/providers/github.js +13 -9
  216. package/dist/webhooks/providers/github.js.map +1 -1
  217. package/dist/webhooks/providers/sentry.d.ts +1 -1
  218. package/dist/webhooks/providers/sentry.d.ts.map +1 -1
  219. package/dist/webhooks/providers/sentry.js +12 -9
  220. package/dist/webhooks/providers/sentry.js.map +1 -1
  221. package/dist/webhooks/registry.d.ts +1 -1
  222. package/dist/webhooks/registry.d.ts.map +1 -1
  223. package/dist/webhooks/registry.js +20 -13
  224. package/dist/webhooks/registry.js.map +1 -1
  225. package/dist/webhooks/types.d.ts +16 -6
  226. package/dist/webhooks/types.d.ts.map +1 -1
  227. package/docker/Dockerfile +4 -11
  228. package/package.json +12 -3
  229. package/dist/cli/commands/setup.d.ts.map +0 -1
  230. package/dist/cli/commands/setup.js +0 -60
  231. package/dist/cli/commands/setup.js.map +0 -1
  232. package/dist/docker/container.d.ts +0 -19
  233. package/dist/docker/container.d.ts.map +0 -1
  234. package/dist/docker/container.js +0 -73
  235. package/dist/docker/container.js.map +0 -1
@@ -0,0 +1,73 @@
1
+ import type { ContainerRuntime, RuntimeLaunchOpts, RuntimeCredentials, BuildImageOpts, RunningAgent } from "./runtime.js";
2
+ export interface ECSFargateConfig {
3
+ awsRegion: string;
4
+ ecsCluster: string;
5
+ ecrRepository: string;
6
+ executionRoleArn: string;
7
+ taskRoleArn: string;
8
+ subnets: string[];
9
+ securityGroups?: string[];
10
+ secretPrefix?: string;
11
+ buildBucket?: string;
12
+ }
13
+ /**
14
+ * AWS ECS Fargate runtime.
15
+ *
16
+ * Launches agents as ECS Fargate tasks with AWS Secrets Manager secrets
17
+ * injected as environment variables or mounted via container definitions.
18
+ *
19
+ * Auth: AWS SDK default credential provider chain
20
+ * 1. AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY env vars
21
+ * 2. AWS_PROFILE / ~/.aws/credentials
22
+ * 3. SSO / IAM instance role (EC2/ECS/Lambda)
23
+ *
24
+ * The runtime credentials (your machine) need:
25
+ * - ecs:RegisterTaskDefinition, ecs:RunTask, ecs:DescribeTasks, ecs:StopTask
26
+ * - ecr:GetAuthorizationToken, ecr:BatchCheckLayerAvailability, ecr:PutImage, etc.
27
+ * - logs:GetLogEvents
28
+ *
29
+ * The task role (container) needs per-agent:
30
+ * - secretsmanager:GetSecretValue on its specific secrets
31
+ */
32
+ export declare class ECSFargateRuntime implements ContainerRuntime {
33
+ readonly needsGateway = false;
34
+ private config;
35
+ private prefix;
36
+ private ecsClient;
37
+ private smClient;
38
+ private logsClient;
39
+ private cbClient;
40
+ private s3Client;
41
+ private ecrClient;
42
+ constructor(config: ECSFargateConfig);
43
+ private static readonly STARTED_BY_PREFIX;
44
+ static readonly LOG_GROUP: string;
45
+ private logGroupCreated;
46
+ private ensureLogGroup;
47
+ isAgentRunning(agentName: string): Promise<boolean>;
48
+ listRunningAgents(): Promise<RunningAgent[]>;
49
+ prepareCredentials(credRefs: string[]): Promise<RuntimeCredentials>;
50
+ cleanupCredentials(_creds: RuntimeCredentials): void;
51
+ buildImage(opts: BuildImageOpts): Promise<string>;
52
+ pushImage(_localImage: string): Promise<string>;
53
+ private buildImageCodeBuild;
54
+ private ecrImageExists;
55
+ private ensureBuildBucket;
56
+ private ensureCodeBuildProject;
57
+ launch(opts: RuntimeLaunchOpts): Promise<string>;
58
+ streamLogs(taskArn: string, onLine: (line: string) => void, onStderr?: (text: string) => void): {
59
+ stop: () => void;
60
+ };
61
+ waitForExit(taskArn: string, timeoutSeconds: number): Promise<number>;
62
+ kill(taskArn: string): Promise<void>;
63
+ remove(_taskArn: string): Promise<void>;
64
+ fetchLogs(agentName: string, limit: number): Promise<string[]>;
65
+ private awsSecretName;
66
+ private listSecretFields;
67
+ private registerTaskDefinition;
68
+ private runTask;
69
+ private getLogEvents;
70
+ private deriveTaskRoleArn;
71
+ private getAccountId;
72
+ }
73
+ //# sourceMappingURL=ecs-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecs-runtime.d.ts","sourceRoot":"","sources":["../../src/docker/ecs-runtime.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAe,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIvI,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,iBAAkB,YAAW,gBAAgB;IACxD,QAAQ,CAAC,YAAY,SAAS;IAE9B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,SAAS,CAAY;gBAEjB,MAAM,EAAE,gBAAgB;IAapC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAA4B;IACrE,MAAM,CAAC,QAAQ,CAAC,SAAS,SAA2B;IACpD,OAAO,CAAC,eAAe,CAAS;YAElB,cAAc;IAYtB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUnD,iBAAiB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IA6B5C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmBzE,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAM9C,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjD,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAKvC,mBAAmB;YAmMnB,cAAc;YAgBd,iBAAiB;YAuBjB,sBAAsB;IA2C9B,MAAM,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IA0BtD,UAAU,CACR,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAC9B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAChC;QAAE,IAAI,EAAE,MAAM,IAAI,CAAA;KAAE;IAwCjB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwBrE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpC,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAcpE,OAAO,CAAC,aAAa;YAIP,gBAAgB;YAoBhB,sBAAsB;YAwDtB,OAAO;YA0BP,YAAY;IAoB1B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,YAAY;CAKrB"}
@@ -0,0 +1,596 @@
1
+ import { execFileSync } from "child_process";
2
+ import { createReadStream } from "fs";
3
+ import { ECSClient, RegisterTaskDefinitionCommand, RunTaskCommand, DescribeTasksCommand, StopTaskCommand, ListTasksCommand, } from "@aws-sdk/client-ecs";
4
+ import { SecretsManagerClient, ListSecretsCommand, } from "@aws-sdk/client-secrets-manager";
5
+ import { CloudWatchLogsClient, GetLogEventsCommand, FilterLogEventsCommand, CreateLogGroupCommand, } from "@aws-sdk/client-cloudwatch-logs";
6
+ import { CodeBuildClient, StartBuildCommand, BatchGetBuildsCommand, CreateProjectCommand, } from "@aws-sdk/client-codebuild";
7
+ import { S3Client, PutObjectCommand, CreateBucketCommand, HeadBucketCommand, } from "@aws-sdk/client-s3";
8
+ import { ECRClient, BatchGetImageCommand, } from "@aws-sdk/client-ecr";
9
+ import { parseCredentialRef } from "../shared/credentials.js";
10
+ import { AWS_CONSTANTS } from "../shared/aws-constants.js";
11
+ /**
12
+ * AWS ECS Fargate runtime.
13
+ *
14
+ * Launches agents as ECS Fargate tasks with AWS Secrets Manager secrets
15
+ * injected as environment variables or mounted via container definitions.
16
+ *
17
+ * Auth: AWS SDK default credential provider chain
18
+ * 1. AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY env vars
19
+ * 2. AWS_PROFILE / ~/.aws/credentials
20
+ * 3. SSO / IAM instance role (EC2/ECS/Lambda)
21
+ *
22
+ * The runtime credentials (your machine) need:
23
+ * - ecs:RegisterTaskDefinition, ecs:RunTask, ecs:DescribeTasks, ecs:StopTask
24
+ * - ecr:GetAuthorizationToken, ecr:BatchCheckLayerAvailability, ecr:PutImage, etc.
25
+ * - logs:GetLogEvents
26
+ *
27
+ * The task role (container) needs per-agent:
28
+ * - secretsmanager:GetSecretValue on its specific secrets
29
+ */
30
+ export class ECSFargateRuntime {
31
+ needsGateway = false;
32
+ config;
33
+ prefix;
34
+ ecsClient;
35
+ smClient;
36
+ logsClient;
37
+ cbClient;
38
+ s3Client;
39
+ ecrClient;
40
+ constructor(config) {
41
+ this.config = config;
42
+ this.prefix = config.secretPrefix || AWS_CONSTANTS.DEFAULT_SECRET_PREFIX;
43
+ const clientConfig = { region: config.awsRegion };
44
+ this.ecsClient = new ECSClient(clientConfig);
45
+ this.smClient = new SecretsManagerClient(clientConfig);
46
+ this.logsClient = new CloudWatchLogsClient(clientConfig);
47
+ this.cbClient = new CodeBuildClient(clientConfig);
48
+ this.s3Client = new S3Client(clientConfig);
49
+ this.ecrClient = new ECRClient(clientConfig);
50
+ }
51
+ static STARTED_BY_PREFIX = AWS_CONSTANTS.STARTED_BY;
52
+ static LOG_GROUP = AWS_CONSTANTS.LOG_GROUP;
53
+ logGroupCreated = false;
54
+ async ensureLogGroup() {
55
+ if (this.logGroupCreated)
56
+ return;
57
+ try {
58
+ await this.logsClient.send(new CreateLogGroupCommand({ logGroupName: ECSFargateRuntime.LOG_GROUP }));
59
+ }
60
+ catch (err) {
61
+ if (err.name !== "ResourceAlreadyExistsException")
62
+ throw err;
63
+ }
64
+ this.logGroupCreated = true;
65
+ }
66
+ // --- Agent tracking ---
67
+ async isAgentRunning(agentName) {
68
+ const family = AWS_CONSTANTS.agentFamily(agentName);
69
+ const res = await this.ecsClient.send(new ListTasksCommand({
70
+ cluster: this.config.ecsCluster,
71
+ family,
72
+ desiredStatus: "RUNNING",
73
+ }));
74
+ return (res.taskArns?.length ?? 0) > 0;
75
+ }
76
+ async listRunningAgents() {
77
+ const res = await this.ecsClient.send(new ListTasksCommand({
78
+ cluster: this.config.ecsCluster,
79
+ startedBy: ECSFargateRuntime.STARTED_BY_PREFIX,
80
+ desiredStatus: "RUNNING",
81
+ }));
82
+ const taskArns = res.taskArns ?? [];
83
+ if (taskArns.length === 0)
84
+ return [];
85
+ const desc = await this.ecsClient.send(new DescribeTasksCommand({
86
+ cluster: this.config.ecsCluster,
87
+ tasks: taskArns,
88
+ }));
89
+ return (desc.tasks ?? []).map((task) => {
90
+ const family = task.taskDefinitionArn?.split("/").pop()?.split(":")[0] ?? "";
91
+ const agentName = AWS_CONSTANTS.agentNameFromFamily(family);
92
+ return {
93
+ agentName,
94
+ taskId: task.taskArn?.split("/").pop() ?? task.taskArn ?? "unknown",
95
+ status: task.lastStatus ?? "UNKNOWN",
96
+ startedAt: task.startedAt,
97
+ };
98
+ });
99
+ }
100
+ // --- Credential preparation ---
101
+ async prepareCredentials(credRefs) {
102
+ const mounts = [];
103
+ for (const credRef of credRefs) {
104
+ const { type, instance } = parseCredentialRef(credRef);
105
+ const fields = await this.listSecretFields(type, instance);
106
+ for (const field of fields) {
107
+ const secretName = this.awsSecretName(type, instance, field);
108
+ mounts.push({
109
+ secretId: secretName,
110
+ mountPath: `/credentials/${type}/${instance}/${field}`,
111
+ });
112
+ }
113
+ }
114
+ return { strategy: "secrets-manager", mounts };
115
+ }
116
+ cleanupCredentials(_creds) {
117
+ // No-op
118
+ }
119
+ // --- Image management ---
120
+ async buildImage(opts) {
121
+ return this.buildImageCodeBuild(opts, opts.onProgress);
122
+ }
123
+ async pushImage(_localImage) {
124
+ // CodeBuild handles build + push in one step; the image is already in ECR
125
+ return `${this.config.ecrRepository}:${_localImage.replace(":", "-")}`;
126
+ }
127
+ async buildImageCodeBuild(opts, onProgress) {
128
+ onProgress?.("Preparing build context");
129
+ const { join, relative, isAbsolute } = await import("path");
130
+ const { readFileSync, writeFileSync } = await import("fs");
131
+ const { randomUUID, createHash } = await import("crypto");
132
+ // Resolve the Dockerfile path and handle two cases:
133
+ // 1. Dockerfile is outside the context dir (agent custom Dockerfiles) — copy it in
134
+ // 2. Dockerfile references a local base image — rewrite FROM to use the remote ECR URI
135
+ const resolvedDockerfile = isAbsolute(opts.dockerfile)
136
+ ? opts.dockerfile
137
+ : join(opts.contextDir, opts.dockerfile);
138
+ const relPath = relative(opts.contextDir, resolvedDockerfile);
139
+ let dockerfileTar;
140
+ let tempDockerfile;
141
+ const needsCopy = relPath.startsWith("..");
142
+ const needsRewrite = !!opts.baseImage;
143
+ if (needsCopy || needsRewrite) {
144
+ tempDockerfile = join(opts.contextDir, `.Dockerfile.${randomUUID().slice(0, 8)}`);
145
+ let content = readFileSync(resolvedDockerfile, "utf-8");
146
+ if (needsRewrite && opts.baseImage) {
147
+ content = content.replace(/^FROM\s+\S+/m, `FROM ${opts.baseImage}`);
148
+ }
149
+ writeFileSync(tempDockerfile, content);
150
+ dockerfileTar = relative(opts.contextDir, tempDockerfile);
151
+ }
152
+ else {
153
+ dockerfileTar = relPath;
154
+ }
155
+ // Hash individual file contents (sorted) for a deterministic fingerprint.
156
+ const { readdirSync, readFileSync: readFileSyncBuf } = await import("fs");
157
+ const hash = createHash("sha256");
158
+ const hashFile = (p) => {
159
+ hash.update(p);
160
+ hash.update(readFileSyncBuf(join(opts.contextDir, p)));
161
+ };
162
+ const hashDir = (dir) => {
163
+ const entries = readdirSync(join(opts.contextDir, dir), { withFileTypes: true })
164
+ .sort((a, b) => a.name.localeCompare(b.name));
165
+ for (const entry of entries) {
166
+ const p = join(dir, entry.name);
167
+ if (entry.isDirectory())
168
+ hashDir(p);
169
+ else
170
+ hashFile(p);
171
+ }
172
+ };
173
+ hashFile(dockerfileTar);
174
+ hashFile("package.json");
175
+ hashDir("dist");
176
+ // Clean up temp Dockerfile after hashing but before any early returns
177
+ if (tempDockerfile) {
178
+ try {
179
+ const { rmSync } = await import("fs");
180
+ rmSync(tempDockerfile);
181
+ }
182
+ catch { }
183
+ }
184
+ const contentHash = hash.digest("hex").slice(0, 16);
185
+ const nameTag = opts.tag.replace(":", "-");
186
+ const hashTag = `${nameTag}-${contentHash}`;
187
+ const remoteTag = `${this.config.ecrRepository}:${hashTag}`;
188
+ // Check ECR cache before doing any S3/CodeBuild work
189
+ onProgress?.(`Checking cache (${hashTag})`);
190
+ const repoName = this.config.ecrRepository.split("/").pop();
191
+ const imageExists = await this.ecrImageExists(repoName, hashTag, onProgress);
192
+ if (imageExists) {
193
+ onProgress?.("Image unchanged — reusing cached build");
194
+ return remoteTag;
195
+ }
196
+ // Cache miss — need to build. Re-create temp Dockerfile for tarball.
197
+ if (needsCopy || needsRewrite) {
198
+ tempDockerfile = join(opts.contextDir, `.Dockerfile.${randomUUID().slice(0, 8)}`);
199
+ let content = readFileSync(resolvedDockerfile, "utf-8");
200
+ if (needsRewrite && opts.baseImage) {
201
+ content = content.replace(/^FROM\s+\S+/m, `FROM ${opts.baseImage}`);
202
+ }
203
+ writeFileSync(tempDockerfile, content);
204
+ dockerfileTar = relative(opts.contextDir, tempDockerfile);
205
+ }
206
+ const { tmpdir } = await import("os");
207
+ const tarPath = join(tmpdir(), `${AWS_CONSTANTS.BUILD_S3_PREFIX}-${randomUUID().slice(0, 8)}.tar.gz`);
208
+ try {
209
+ execFileSync("tar", [
210
+ "czf", tarPath,
211
+ "-C", opts.contextDir,
212
+ dockerfileTar, "package.json", "dist",
213
+ ], {
214
+ encoding: "utf-8",
215
+ timeout: 60_000,
216
+ env: { ...process.env, COPYFILE_DISABLE: "1" },
217
+ });
218
+ }
219
+ finally {
220
+ if (tempDockerfile) {
221
+ try {
222
+ const { rmSync } = await import("fs");
223
+ rmSync(tempDockerfile);
224
+ }
225
+ catch { }
226
+ }
227
+ }
228
+ // Upload to S3
229
+ onProgress?.("Uploading to S3");
230
+ const bucket = await this.ensureBuildBucket();
231
+ const s3Key = `${AWS_CONSTANTS.BUILD_S3_PREFIX}/${nameTag}-${Date.now()}.tar.gz`;
232
+ await this.s3Client.send(new PutObjectCommand({
233
+ Bucket: bucket,
234
+ Key: s3Key,
235
+ Body: createReadStream(tarPath),
236
+ }));
237
+ // Clean up local tarball
238
+ try {
239
+ const { rmSync } = await import("fs");
240
+ rmSync(tarPath);
241
+ }
242
+ catch { }
243
+ // Ensure CodeBuild project exists
244
+ const projectName = AWS_CONSTANTS.CODEBUILD_PROJECT;
245
+ await this.ensureCodeBuildProject(projectName, bucket);
246
+ const registry = this.config.ecrRepository.split("/")[0];
247
+ // Start build
248
+ const buildspec = [
249
+ "version: 0.2",
250
+ "phases:",
251
+ " pre_build:",
252
+ " commands:",
253
+ " - tar xzf *.tar.gz && rm -f *.tar.gz",
254
+ " - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY",
255
+ " build:",
256
+ " commands:",
257
+ " - docker build -t $IMAGE_URI -f $DOCKERFILE .",
258
+ " - docker push $IMAGE_URI",
259
+ ].join("\n");
260
+ const buildRes = await this.cbClient.send(new StartBuildCommand({
261
+ projectName,
262
+ sourceTypeOverride: "S3",
263
+ sourceLocationOverride: `${bucket}/${s3Key}`,
264
+ buildspecOverride: buildspec,
265
+ environmentVariablesOverride: [
266
+ { name: "IMAGE_URI", value: remoteTag },
267
+ { name: "ECR_REGISTRY", value: registry },
268
+ { name: "DOCKERFILE", value: dockerfileTar },
269
+ ],
270
+ }));
271
+ const buildId = buildRes.build?.id;
272
+ if (!buildId)
273
+ throw new Error("CodeBuild did not return a build ID");
274
+ onProgress?.("Queued — waiting for CodeBuild");
275
+ // Poll until complete
276
+ while (true) {
277
+ await sleep(10_000);
278
+ const status = await this.cbClient.send(new BatchGetBuildsCommand({ ids: [buildId] }));
279
+ const build = status.builds?.[0];
280
+ if (!build)
281
+ throw new Error(`CodeBuild build ${buildId} not found`);
282
+ if (build.currentPhase) {
283
+ const phaseLabels = {
284
+ SUBMITTED: "Submitted",
285
+ QUEUED: "Queued",
286
+ PROVISIONING: "Provisioning build environment",
287
+ DOWNLOAD_SOURCE: "Downloading source",
288
+ INSTALL: "Installing dependencies",
289
+ PRE_BUILD: "Logging in to ECR",
290
+ BUILD: "Building and pushing image",
291
+ POST_BUILD: "Finalizing",
292
+ UPLOAD_ARTIFACTS: "Uploading artifacts",
293
+ FINALIZING: "Finalizing",
294
+ COMPLETED: "Complete",
295
+ };
296
+ const label = phaseLabels[build.currentPhase] || build.currentPhase;
297
+ onProgress?.(label);
298
+ }
299
+ if (build.buildComplete) {
300
+ if (build.buildStatus !== "SUCCEEDED") {
301
+ const logs = build.logs?.deepLink || "";
302
+ throw new Error(`CodeBuild build failed (${build.buildStatus}). Logs: ${logs}`);
303
+ }
304
+ return remoteTag;
305
+ }
306
+ }
307
+ }
308
+ async ecrImageExists(repositoryName, imageTag, onProgress) {
309
+ try {
310
+ const res = await this.ecrClient.send(new BatchGetImageCommand({
311
+ repositoryName,
312
+ imageIds: [{ imageTag }],
313
+ }));
314
+ return (res.images?.length ?? 0) > 0;
315
+ }
316
+ catch (err) {
317
+ // ImageNotFoundException is expected when the image doesn't exist
318
+ if (err.name === "ImageNotFoundException")
319
+ return false;
320
+ // Surface unexpected errors (permissions, network) so they're not silently swallowed
321
+ onProgress?.(`Cache check failed: ${err.message ?? err}`);
322
+ return false;
323
+ }
324
+ }
325
+ async ensureBuildBucket() {
326
+ if (this.config.buildBucket) {
327
+ return this.config.buildBucket;
328
+ }
329
+ // Derive bucket name from account ID + region
330
+ const accountId = this.getAccountId();
331
+ const bucket = AWS_CONSTANTS.buildBucket(accountId, this.config.awsRegion);
332
+ try {
333
+ await this.s3Client.send(new HeadBucketCommand({ Bucket: bucket }));
334
+ }
335
+ catch (err) {
336
+ if (err.name === "NotFound" || err.$metadata?.httpStatusCode === 404) {
337
+ await this.s3Client.send(new CreateBucketCommand({ Bucket: bucket }));
338
+ }
339
+ else if (err.name !== "Forbidden") {
340
+ // Forbidden means bucket exists but we don't own it — try anyway
341
+ throw err;
342
+ }
343
+ }
344
+ return bucket;
345
+ }
346
+ async ensureCodeBuildProject(projectName, bucket) {
347
+ const accountId = this.getAccountId();
348
+ const region = this.config.awsRegion;
349
+ try {
350
+ const status = await this.cbClient.send(new BatchGetBuildsCommand({ ids: [`${projectName}:dummy`] }));
351
+ // If the project doesn't exist, BatchGetBuilds returns empty — but we need a better check
352
+ // Actually, just try to create and handle the conflict
353
+ void status;
354
+ }
355
+ catch { }
356
+ const serviceRole = `arn:aws:iam::${accountId}:role/${AWS_CONSTANTS.CODEBUILD_ROLE}`;
357
+ try {
358
+ await this.cbClient.send(new CreateProjectCommand({
359
+ name: projectName,
360
+ source: {
361
+ type: "S3",
362
+ location: `${bucket}/`,
363
+ },
364
+ artifacts: { type: "NO_ARTIFACTS" },
365
+ environment: {
366
+ type: "LINUX_CONTAINER",
367
+ computeType: "BUILD_GENERAL1_MEDIUM",
368
+ image: "aws/codebuild/standard:7.0",
369
+ privilegedMode: true,
370
+ environmentVariables: [
371
+ { name: "IMAGE_URI", value: "placeholder" },
372
+ { name: "ECR_REGISTRY", value: "placeholder" },
373
+ { name: "DOCKERFILE", value: "Dockerfile" },
374
+ ],
375
+ },
376
+ serviceRole,
377
+ }));
378
+ }
379
+ catch (err) {
380
+ if (err.name !== "ResourceAlreadyExistsException") {
381
+ throw err;
382
+ }
383
+ }
384
+ }
385
+ // --- Container lifecycle ---
386
+ async launch(opts) {
387
+ await this.ensureLogGroup();
388
+ const family = AWS_CONSTANTS.agentFamily(opts.agentName);
389
+ const secretMounts = opts.credentials.strategy === "secrets-manager"
390
+ ? opts.credentials.mounts
391
+ : [];
392
+ const perAgentRole = this.deriveTaskRoleArn(opts.agentName);
393
+ const taskRoleArn = opts.serviceAccount || perAgentRole || this.config.taskRoleArn;
394
+ const taskDefArn = await this.registerTaskDefinition(family, {
395
+ image: opts.image,
396
+ env: opts.env,
397
+ secretMounts,
398
+ memory: opts.memory || "4096",
399
+ cpus: String((opts.cpus || 2) * 1024),
400
+ taskRoleArn,
401
+ streamPrefix: family,
402
+ });
403
+ const taskArn = await this.runTask(taskDefArn);
404
+ return taskArn;
405
+ }
406
+ streamLogs(taskArn, onLine, onStderr) {
407
+ let stopped = false;
408
+ let nextToken;
409
+ const poll = async () => {
410
+ const taskId = taskArn.split("/").pop();
411
+ const logGroup = ECSFargateRuntime.LOG_GROUP;
412
+ // Describe the task to get the family (used as stream prefix)
413
+ const desc = await this.ecsClient.send(new DescribeTasksCommand({
414
+ cluster: this.config.ecsCluster,
415
+ tasks: [taskArn],
416
+ }));
417
+ const family = desc.tasks?.[0]?.taskDefinitionArn?.split("/").pop()?.split(":")[0] ?? AWS_CONSTANTS.agentFamily("agent");
418
+ // awslogs stream format: <prefix>/<container-name>/<task-id>
419
+ const logStream = `${family}/agent/${taskId}`;
420
+ while (!stopped) {
421
+ try {
422
+ const result = await this.getLogEvents(logGroup, logStream, nextToken);
423
+ for (const event of result.events) {
424
+ onLine(event.message);
425
+ }
426
+ if (result.nextForwardToken) {
427
+ nextToken = result.nextForwardToken;
428
+ }
429
+ }
430
+ catch (err) {
431
+ if (!stopped && onStderr && err.name !== "ResourceNotFoundException") {
432
+ onStderr(`Log polling error: ${err.message}`);
433
+ }
434
+ }
435
+ if (!stopped)
436
+ await sleep(5000);
437
+ }
438
+ };
439
+ poll();
440
+ return { stop: () => { stopped = true; } };
441
+ }
442
+ async waitForExit(taskArn, timeoutSeconds) {
443
+ const deadline = Date.now() + timeoutSeconds * 1000;
444
+ while (Date.now() < deadline) {
445
+ const res = await this.ecsClient.send(new DescribeTasksCommand({
446
+ cluster: this.config.ecsCluster,
447
+ tasks: [taskArn],
448
+ }));
449
+ const task = res.tasks?.[0];
450
+ if (!task)
451
+ throw new Error(`Task ${taskArn} not found`);
452
+ if (task.lastStatus === "STOPPED") {
453
+ const exitCode = task.containers?.[0]?.exitCode;
454
+ return exitCode ?? 1;
455
+ }
456
+ await sleep(10_000);
457
+ }
458
+ await this.kill(taskArn);
459
+ throw new Error(`ECS task ${taskArn} timed out after ${timeoutSeconds}s`);
460
+ }
461
+ async kill(taskArn) {
462
+ try {
463
+ await this.ecsClient.send(new StopTaskCommand({
464
+ cluster: this.config.ecsCluster,
465
+ task: taskArn,
466
+ reason: "action-llama timeout",
467
+ }));
468
+ }
469
+ catch {
470
+ // Task may already be stopped
471
+ }
472
+ }
473
+ async remove(_taskArn) {
474
+ // ECS cleans up stopped tasks automatically
475
+ }
476
+ async fetchLogs(agentName, limit) {
477
+ const res = await this.logsClient.send(new FilterLogEventsCommand({
478
+ logGroupName: ECSFargateRuntime.LOG_GROUP,
479
+ logStreamNamePrefix: `${AWS_CONSTANTS.agentFamily(agentName)}/`,
480
+ limit,
481
+ }));
482
+ return (res.events ?? [])
483
+ .map((e) => e.message?.trimEnd() ?? "")
484
+ .filter(Boolean);
485
+ }
486
+ // --- Internal: Secret naming ---
487
+ awsSecretName(type, instance, field) {
488
+ return `${this.prefix}/${type}/${instance}/${field}`;
489
+ }
490
+ async listSecretFields(type, instance) {
491
+ const prefix = `${this.prefix}/${type}/${instance}/`;
492
+ const res = await this.smClient.send(new ListSecretsCommand({
493
+ Filters: [{ Key: "name", Values: [prefix] }],
494
+ MaxResults: 100,
495
+ }));
496
+ const fields = [];
497
+ for (const secret of res.SecretList || []) {
498
+ if (secret.Name?.startsWith(prefix)) {
499
+ fields.push(secret.Name.slice(prefix.length));
500
+ }
501
+ }
502
+ return fields;
503
+ }
504
+ // --- Internal: ECS operations ---
505
+ async registerTaskDefinition(family, opts) {
506
+ const environment = Object.entries(opts.env).map(([name, value]) => ({ name, value }));
507
+ const secrets = opts.secretMounts.map((mount) => {
508
+ const parts = mount.mountPath.replace("/credentials/", "").split("/");
509
+ const envName = `AL_SECRET_${parts.join("__")}`;
510
+ return {
511
+ name: envName,
512
+ valueFrom: `arn:aws:secretsmanager:${this.config.awsRegion}:${this.getAccountId()}:secret:${mount.secretId}`,
513
+ };
514
+ });
515
+ const res = await this.ecsClient.send(new RegisterTaskDefinitionCommand({
516
+ family,
517
+ networkMode: "awsvpc",
518
+ requiresCompatibilities: ["FARGATE"],
519
+ cpu: opts.cpus,
520
+ memory: opts.memory,
521
+ executionRoleArn: this.config.executionRoleArn,
522
+ taskRoleArn: opts.taskRoleArn,
523
+ containerDefinitions: [{
524
+ name: "agent",
525
+ image: opts.image,
526
+ essential: true,
527
+ environment,
528
+ secrets,
529
+ logConfiguration: {
530
+ logDriver: "awslogs",
531
+ options: {
532
+ "awslogs-group": ECSFargateRuntime.LOG_GROUP,
533
+ "awslogs-region": this.config.awsRegion,
534
+ "awslogs-stream-prefix": opts.streamPrefix,
535
+ "awslogs-create-group": "true",
536
+ },
537
+ },
538
+ user: "1000:1000",
539
+ linuxParameters: {
540
+ initProcessEnabled: true,
541
+ },
542
+ }],
543
+ }));
544
+ return res.taskDefinition.taskDefinitionArn;
545
+ }
546
+ async runTask(taskDefinitionArn) {
547
+ const res = await this.ecsClient.send(new RunTaskCommand({
548
+ cluster: this.config.ecsCluster,
549
+ taskDefinition: taskDefinitionArn,
550
+ launchType: "FARGATE",
551
+ startedBy: ECSFargateRuntime.STARTED_BY_PREFIX,
552
+ count: 1,
553
+ networkConfiguration: {
554
+ awsvpcConfiguration: {
555
+ subnets: this.config.subnets,
556
+ securityGroups: this.config.securityGroups || [],
557
+ assignPublicIp: "ENABLED",
558
+ },
559
+ },
560
+ }));
561
+ const tasks = res.tasks || [];
562
+ if (tasks.length === 0) {
563
+ const failures = res.failures || [];
564
+ const reason = failures[0]?.reason || "unknown";
565
+ throw new Error(`Failed to start ECS task: ${reason}`);
566
+ }
567
+ return tasks[0].taskArn;
568
+ }
569
+ async getLogEvents(logGroup, logStream, nextToken) {
570
+ const res = await this.logsClient.send(new GetLogEventsCommand({
571
+ logGroupName: logGroup,
572
+ logStreamName: logStream,
573
+ startFromHead: true,
574
+ nextToken,
575
+ }));
576
+ return {
577
+ events: (res.events || []).map((e) => ({ message: e.message?.trimEnd() || "" })),
578
+ nextForwardToken: res.nextForwardToken,
579
+ };
580
+ }
581
+ // --- Internal: Per-agent task role derivation ---
582
+ deriveTaskRoleArn(agentName) {
583
+ const accountId = this.getAccountId();
584
+ if (!accountId)
585
+ return undefined;
586
+ return `arn:aws:iam::${accountId}:role/${AWS_CONSTANTS.taskRoleName(agentName)}`;
587
+ }
588
+ getAccountId() {
589
+ const match = this.config.ecrRepository.match(/^(\d+)\.dkr\.ecr\./);
590
+ return match?.[1] || "";
591
+ }
592
+ }
593
+ function sleep(ms) {
594
+ return new Promise((r) => setTimeout(r, ms));
595
+ }
596
+ //# sourceMappingURL=ecs-runtime.js.map