@hasna/skills 0.5.10 → 0.6.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 (50) hide show
  1. package/README.md +95 -53
  2. package/bin/index.js +3467 -1460
  3. package/bin/mcp.js +2638 -1713
  4. package/bin/migrate.js +110 -7
  5. package/bin/server.js +5487 -216
  6. package/bin/worker.js +372 -17
  7. package/dist/admin-contract.d.ts +13 -13
  8. package/dist/admin-contract.js +13 -13
  9. package/dist/cli/commands/agent-integration.d.ts +2 -0
  10. package/dist/cli/commands/context.d.ts +19 -0
  11. package/dist/cli/commands/install.d.ts +1 -0
  12. package/dist/cli/commands/profile-sync.d.ts +10 -0
  13. package/dist/cli/commands/profiles.d.ts +2 -0
  14. package/dist/index.d.ts +5 -0
  15. package/dist/index.js +1273 -331
  16. package/dist/lib/agent-integration.d.ts +74 -0
  17. package/dist/lib/cloud-executions.d.ts +39 -0
  18. package/dist/lib/managed-policy.d.ts +6 -0
  19. package/dist/lib/profile-admin.d.ts +4 -0
  20. package/dist/lib/profile-client.d.ts +19 -0
  21. package/dist/lib/read-access.d.ts +3 -23
  22. package/dist/lib/selected-document.d.ts +6 -0
  23. package/dist/lib/selected-run.d.ts +31 -0
  24. package/dist/lib/selection-cache.d.ts +40 -0
  25. package/dist/lib/selection-resolver.d.ts +75 -0
  26. package/dist/lib/skill-context.d.ts +43 -0
  27. package/dist/lib/skillinfo.d.ts +2 -0
  28. package/dist/sdk/execution/dispatchers/ecs.d.ts +8 -0
  29. package/dist/sdk/index.d.ts +2 -0
  30. package/dist/sdk/index.js +9791 -8152
  31. package/dist/server/app.d.ts +7 -1
  32. package/dist/server/artifact-storage.d.ts +7 -10
  33. package/dist/server/auth.d.ts +2 -0
  34. package/dist/server/config.d.ts +2 -0
  35. package/dist/server/profile-api.d.ts +3 -0
  36. package/dist/server/runtime-api.d.ts +31 -0
  37. package/dist/server/runtime-policy.d.ts +25 -0
  38. package/dist/server/runtime-store.d.ts +73 -0
  39. package/dist/server/runtime-worker.d.ts +19 -0
  40. package/dist/server/selection-store.d.ts +39 -0
  41. package/dist/server/sqlite-store.d.ts +2 -0
  42. package/dist/server/store.d.ts +3 -0
  43. package/dist/server/types.d.ts +3 -0
  44. package/dist/storage.js +3 -0
  45. package/dist/types/skill-selection.d.ts +41 -0
  46. package/migrations/postgres/0007_skill_profiles.sql +19 -0
  47. package/migrations/postgres/0008_skill_runtime.sql +14 -0
  48. package/migrations/sqlite/0007_skill_profiles.sql +19 -0
  49. package/migrations/sqlite/0008_skill_runtime.sql +14 -0
  50. package/package.json +3 -2
package/bin/worker.js CHANGED
@@ -23099,11 +23099,11 @@ var init_dist_es9 = __esm(() => {
23099
23099
  });
23100
23100
 
23101
23101
  // src/server/worker.ts
23102
- import { randomUUID as randomUUID4 } from "crypto";
23102
+ import { randomUUID as randomUUID5 } from "crypto";
23103
23103
  // package.json
23104
23104
  var package_default = {
23105
23105
  name: "@hasna/skills",
23106
- version: "0.5.10",
23106
+ version: "0.6.0",
23107
23107
  description: "Skills library for AI coding agents",
23108
23108
  type: "module",
23109
23109
  bin: {
@@ -23219,7 +23219,8 @@ var package_default = {
23219
23219
  },
23220
23220
  repository: {
23221
23221
  type: "git",
23222
- url: "git+https://github.com/hasna/apps.git"
23222
+ url: "https://github.com/hasna/apps.git",
23223
+ directory: "apps/skills"
23223
23224
  },
23224
23225
  homepage: "https://github.com/hasna/skills",
23225
23226
  bugs: {
@@ -33130,10 +33131,12 @@ var SHA256_HEX = /^[0-9a-f]{64}$/;
33130
33131
  class ArtifactStorage {
33131
33132
  bucket;
33132
33133
  prefix;
33134
+ runPrefix;
33133
33135
  s3;
33134
33136
  constructor(options = {}) {
33135
33137
  this.bucket = options.bucket;
33136
33138
  this.prefix = (options.prefix || "skills/artifacts").replace(/^\/+|\/+$/g, "");
33139
+ this.runPrefix = (options.runPrefix || this.prefix).replace(/^\/+|\/+$/g, "");
33137
33140
  this.s3 = this.bucket ? options.client ?? new S3Client({ region: options.region || process.env.AWS_REGION || "us-east-1" }) : undefined;
33138
33141
  }
33139
33142
  get usesS3() {
@@ -33141,10 +33144,10 @@ class ArtifactStorage {
33141
33144
  }
33142
33145
  objectKeyFor(tenantId, runId, relativePath) {
33143
33146
  const safeRelativePath = relativePath.replace(/^\/+/, "").replace(/\.\.(?:\/|$)/g, "");
33144
- return `${this.prefix}/${tenantId}/${runId}/${safeRelativePath}`;
33147
+ return `${this.runPrefix}/${tenantId}/${runId}/${safeRelativePath}`;
33145
33148
  }
33146
33149
  quarantineKeyFor(tenantId, runId, artifactId) {
33147
- return `${this.prefix}/quarantine/${tenantId}/${runId}/${artifactId}`;
33150
+ return `${this.runPrefix}/quarantine/${tenantId}/${runId}/${artifactId}`;
33148
33151
  }
33149
33152
  async materialize(run, artifact, body) {
33150
33153
  if (!this.bucket) {
@@ -33581,7 +33584,132 @@ function looksLikeSqlitePath(value) {
33581
33584
 
33582
33585
  // src/server/sqlite-store.ts
33583
33586
  import { Database } from "bun:sqlite";
33584
- import { randomUUID as randomUUID2 } from "crypto";
33587
+
33588
+ // src/server/selection-store.ts
33589
+ import { randomUUID } from "crypto";
33590
+ function selections(value) {
33591
+ return typeof value === "string" ? JSON.parse(value) : structuredClone(value);
33592
+ }
33593
+ function time(value) {
33594
+ return value instanceof Date ? value.toISOString() : String(value);
33595
+ }
33596
+ function profile(row) {
33597
+ return {
33598
+ id: String(row.profile_id),
33599
+ workspaceId: String(row.org_id),
33600
+ revision: String(row.revision),
33601
+ selections: selections(row.selections_json),
33602
+ updatedAt: time(row.updated_at)
33603
+ };
33604
+ }
33605
+ function state(row) {
33606
+ return {
33607
+ stationId: String(row.station_id),
33608
+ workspaceId: String(row.org_id),
33609
+ actorId: String(row.actor_id),
33610
+ profileId: String(row.profile_id),
33611
+ profileRevision: String(row.profile_revision),
33612
+ selections: selections(row.selections_json),
33613
+ appliedAt: time(row.applied_at)
33614
+ };
33615
+ }
33616
+
33617
+ class MemorySkillSelectionStore {
33618
+ profiles = new Map;
33619
+ states = new Map;
33620
+ key(...values) {
33621
+ return JSON.stringify(values);
33622
+ }
33623
+ async getProfile(p2, id) {
33624
+ return structuredClone(this.profiles.get(this.key(p2.orgId, id)) ?? null);
33625
+ }
33626
+ async saveProfile(p2, id, selected, expected) {
33627
+ const key = this.key(p2.orgId, id), previous = this.profiles.get(key);
33628
+ if ((previous?.revision ?? null) !== expected)
33629
+ return null;
33630
+ const next = {
33631
+ id,
33632
+ workspaceId: p2.orgId,
33633
+ revision: randomUUID(),
33634
+ selections: structuredClone(selected),
33635
+ updatedAt: new Date().toISOString()
33636
+ };
33637
+ this.profiles.set(key, next);
33638
+ return structuredClone(next);
33639
+ }
33640
+ async getStationState(p2, id) {
33641
+ return structuredClone(this.states.get(this.key(p2.orgId, p2.userId, id)) ?? null);
33642
+ }
33643
+ async saveStationState(p2, id, input) {
33644
+ if (this.profiles.get(this.key(p2.orgId, input.profileId))?.revision !== input.profileRevision)
33645
+ return null;
33646
+ const next = {
33647
+ ...structuredClone(input),
33648
+ stationId: id,
33649
+ workspaceId: p2.orgId,
33650
+ actorId: p2.userId,
33651
+ appliedAt: new Date().toISOString()
33652
+ };
33653
+ this.states.set(this.key(p2.orgId, p2.userId, id), next);
33654
+ return structuredClone(next);
33655
+ }
33656
+ }
33657
+
33658
+ class SqliteSkillSelectionStore {
33659
+ db;
33660
+ constructor(db) {
33661
+ this.db = db;
33662
+ }
33663
+ async getProfile(p2, id) {
33664
+ const row = this.db.query("SELECT * FROM skills_profiles WHERE org_id=? AND profile_id=?").get(p2.orgId, id);
33665
+ return row ? profile(row) : null;
33666
+ }
33667
+ async saveProfile(p2, id, selected, expected) {
33668
+ const revision = randomUUID(), timestamp = new Date().toISOString();
33669
+ const row = expected === null ? this.db.query("INSERT INTO skills_profiles(org_id,profile_id,revision,selections_json,updated_at) VALUES(?,?,?,?,?) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING *").get(p2.orgId, id, revision, JSON.stringify(selected), timestamp) : this.db.query("UPDATE skills_profiles SET revision=?,selections_json=?,updated_at=? WHERE org_id=? AND profile_id=? AND revision=? RETURNING *").get(revision, JSON.stringify(selected), timestamp, p2.orgId, id, expected);
33670
+ return row ? profile(row) : null;
33671
+ }
33672
+ async getStationState(p2, id) {
33673
+ const row = this.db.query("SELECT * FROM skills_station_state WHERE org_id=? AND actor_id=? AND station_id=?").get(p2.orgId, p2.userId, id);
33674
+ return row ? state(row) : null;
33675
+ }
33676
+ async saveStationState(p2, id, input) {
33677
+ const row = this.db.query(`INSERT INTO skills_station_state(org_id,actor_id,station_id,profile_id,profile_revision,selections_json,applied_at)
33678
+ SELECT ?,?,?,?,?,?,? WHERE EXISTS(SELECT 1 FROM skills_profiles WHERE org_id=? AND profile_id=? AND revision=?)
33679
+ ON CONFLICT(org_id,actor_id,station_id) DO UPDATE SET profile_id=excluded.profile_id,profile_revision=excluded.profile_revision,selections_json=excluded.selections_json,applied_at=excluded.applied_at RETURNING *`).get(p2.orgId, p2.userId, id, input.profileId, input.profileRevision, JSON.stringify(input.selections), new Date().toISOString(), p2.orgId, input.profileId, input.profileRevision);
33680
+ return row ? state(row) : null;
33681
+ }
33682
+ }
33683
+
33684
+ class PostgresSkillSelectionStore {
33685
+ sql;
33686
+ constructor(sql) {
33687
+ this.sql = sql;
33688
+ }
33689
+ async getProfile(p2, id) {
33690
+ const rows = await this.sql`SELECT * FROM skills_profiles WHERE org_id=${p2.orgId} AND profile_id=${id}`;
33691
+ return rows[0] ? profile(rows[0]) : null;
33692
+ }
33693
+ async saveProfile(p2, id, selected, expected) {
33694
+ const revision = randomUUID(), timestamp = new Date().toISOString();
33695
+ const rows = expected === null ? await this.sql`INSERT INTO skills_profiles(org_id,profile_id,revision,selections_json,updated_at) VALUES(${p2.orgId},${id},${revision},${JSON.stringify(selected)}::jsonb,${timestamp}) ON CONFLICT(org_id,profile_id) DO NOTHING RETURNING *` : await this.sql`UPDATE skills_profiles SET revision=${revision},selections_json=${JSON.stringify(selected)}::jsonb,updated_at=${timestamp} WHERE org_id=${p2.orgId} AND profile_id=${id} AND revision=${expected} RETURNING *`;
33696
+ return rows[0] ? profile(rows[0]) : null;
33697
+ }
33698
+ async getStationState(p2, id) {
33699
+ const rows = await this.sql`SELECT * FROM skills_station_state WHERE org_id=${p2.orgId} AND actor_id=${p2.userId} AND station_id=${id}`;
33700
+ return rows[0] ? state(rows[0]) : null;
33701
+ }
33702
+ async saveStationState(p2, id, input) {
33703
+ const rows = await this.sql`INSERT INTO skills_station_state(org_id,actor_id,station_id,profile_id,profile_revision,selections_json,applied_at)
33704
+ SELECT ${p2.orgId},${p2.userId},${id},${input.profileId},${input.profileRevision},${JSON.stringify(input.selections)}::jsonb,${new Date().toISOString()}
33705
+ WHERE EXISTS(SELECT 1 FROM skills_profiles WHERE org_id=${p2.orgId} AND profile_id=${input.profileId} AND revision=${input.profileRevision})
33706
+ ON CONFLICT(org_id,actor_id,station_id) DO UPDATE SET profile_id=excluded.profile_id,profile_revision=excluded.profile_revision,selections_json=excluded.selections_json,applied_at=excluded.applied_at RETURNING *`;
33707
+ return rows[0] ? state(rows[0]) : null;
33708
+ }
33709
+ }
33710
+
33711
+ // src/server/sqlite-store.ts
33712
+ import { randomUUID as randomUUID3 } from "crypto";
33585
33713
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
33586
33714
  import { dirname as dirname4, join as join6 } from "path";
33587
33715
 
@@ -33599,7 +33727,7 @@ function publicPrincipal(partial = {}) {
33599
33727
  userId: partial.userId || "user_dev",
33600
33728
  email: partial.email || "dev@example.com",
33601
33729
  role: partial.role || "owner",
33602
- scopes: partial.scopes || ["skills:read", "runs:write"]
33730
+ scopes: partial.scopes ?? ["*"]
33603
33731
  };
33604
33732
  }
33605
33733
 
@@ -33642,7 +33770,7 @@ function defaultStartDirs() {
33642
33770
  }
33643
33771
 
33644
33772
  // src/server/rows.ts
33645
- import { randomUUID } from "crypto";
33773
+ import { randomUUID as randomUUID2 } from "crypto";
33646
33774
  function nowIso() {
33647
33775
  return new Date().toISOString();
33648
33776
  }
@@ -33650,10 +33778,10 @@ function normalizeLimit(limit) {
33650
33778
  return Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : 0;
33651
33779
  }
33652
33780
  function runId() {
33653
- return `run_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
33781
+ return `run_${Date.now().toString(36)}_${randomUUID2().slice(0, 8)}`;
33654
33782
  }
33655
33783
  function artifactId() {
33656
- return `art_${randomUUID().replace(/-/g, "").slice(0, 20)}`;
33784
+ return `art_${randomUUID2().replace(/-/g, "").slice(0, 20)}`;
33657
33785
  }
33658
33786
  function rowToRun(row) {
33659
33787
  return {
@@ -33873,6 +34001,9 @@ var NO_REVISION_SENTINEL = "0000000000000000000000000000000000000000000000000000
33873
34001
  var LAST_USED_RESOLUTION_MS = 60000;
33874
34002
 
33875
34003
  class SqliteSkillsStore {
34004
+ get selectionStore() {
34005
+ return new SqliteSkillSelectionStore(this.db);
34006
+ }
33876
34007
  backend;
33877
34008
  db;
33878
34009
  closed = false;
@@ -33974,7 +34105,7 @@ class SqliteSkillsStore {
33974
34105
  JSON.stringify(input.input),
33975
34106
  JSON.stringify(input.args),
33976
34107
  input.idempotencyKey ?? null,
33977
- randomUUID2(),
34108
+ randomUUID3(),
33978
34109
  nowIso()
33979
34110
  ]);
33980
34111
  return rowToRun(row);
@@ -35325,6 +35456,225 @@ var MAX_MANIFEST_BYTES = MAX_SKILL_MD_BYTES + 64000;
35325
35456
  var ALLOWED_PUBLISH_PARTS = new Set(["manifest", "bundle"]);
35326
35457
  var MAX_VERSION_MANIFEST_BYTES = 512 * 1024;
35327
35458
 
35459
+ // ../contracts/dist/client/transport.js
35460
+ import { createRequire } from "module";
35461
+ function envToken(name) {
35462
+ return name.toUpperCase().replace(/-/g, "_");
35463
+ }
35464
+ function clientTransportEnvKeys(name) {
35465
+ const envSegment = envToken(name);
35466
+ return {
35467
+ apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
35468
+ apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
35469
+ };
35470
+ }
35471
+ var MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
35472
+ var INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
35473
+ var CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
35474
+ var AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
35475
+ var SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
35476
+ var requireSecretsSdk = createRequire(import.meta.url);
35477
+ var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
35478
+ var AUTHORITY_OVERRIDE_HEADERS = new Set([
35479
+ "host",
35480
+ ":authority",
35481
+ "forwarded",
35482
+ "x-forwarded-host",
35483
+ "x-original-host"
35484
+ ]);
35485
+
35486
+ // src/lib/fleet-credentials.ts
35487
+ var SKILLS_APP = "skills";
35488
+ var ENV_KEYS = clientTransportEnvKeys(SKILLS_APP);
35489
+ var SKILLS_API_URL_ENV_KEYS = ENV_KEYS.apiUrlKeys;
35490
+ var SKILLS_API_KEY_ENV_KEYS = ENV_KEYS.apiKeyKeys;
35491
+ var SKILLS_API_URL_ENV = SKILLS_API_URL_ENV_KEYS[0];
35492
+ var SKILLS_API_KEY_ENV = SKILLS_API_KEY_ENV_KEYS[0];
35493
+
35494
+ // src/sdk/execution/storage.ts
35495
+ class MemoryRunExecutionStore {
35496
+ durable = false;
35497
+ runs = new Map;
35498
+ byKey = new Map;
35499
+ byDigests = new Map;
35500
+ attempts = new Map;
35501
+ transitions = [];
35502
+ receipts = new Map;
35503
+ async admit(admission) {
35504
+ const existing = this.runs.get(admission.runId);
35505
+ if (existing)
35506
+ return existing;
35507
+ const row = {
35508
+ admission,
35509
+ status: "admitted",
35510
+ currentAttemptId: null,
35511
+ terminalReceiptId: null,
35512
+ updatedAt: admission.createdAt
35513
+ };
35514
+ this.runs.set(admission.runId, row);
35515
+ this.byKey.set(admission.tenantId + "\x00" + admission.idempotencyKey, admission.runId);
35516
+ this.byDigests.set(digestKey({
35517
+ tenantId: admission.tenantId,
35518
+ skillId: admission.skillId,
35519
+ skillVersion: admission.skillVersion,
35520
+ bundleDigest: admission.bundleDigest,
35521
+ inputDigest: admission.inputDigest
35522
+ }), admission.runId);
35523
+ return row;
35524
+ }
35525
+ async getRun(runId2) {
35526
+ return this.runs.get(runId2) ?? null;
35527
+ }
35528
+ async getRunByKey(tenantId, idempotencyKey) {
35529
+ const runId2 = this.byKey.get(tenantId + "\x00" + idempotencyKey);
35530
+ return runId2 ? this.runs.get(runId2) ?? null : null;
35531
+ }
35532
+ async getRunByDigests(input) {
35533
+ const runId2 = this.byDigests.get(digestKey(input));
35534
+ return runId2 ? this.runs.get(runId2) ?? null : null;
35535
+ }
35536
+ async listAttempts(runId2) {
35537
+ return Array.from(this.attempts.values()).filter((attempt) => attempt.runId === runId2).sort((a3, b3) => a3.attemptNumber - b3.attemptNumber);
35538
+ }
35539
+ async createAttempt(input) {
35540
+ const attemptId = `${input.runId}/attempt/${input.attemptNumber}`;
35541
+ const attempt = {
35542
+ runId: input.runId,
35543
+ attemptId,
35544
+ attemptNumber: input.attemptNumber,
35545
+ leaseGeneration: 0,
35546
+ workerId: null,
35547
+ claimedAt: null,
35548
+ status: "pending",
35549
+ clientToken: null,
35550
+ requestDigest: null,
35551
+ taskId: null,
35552
+ launchState: "unlaunched",
35553
+ startedBy: null
35554
+ };
35555
+ this.attempts.set(attemptId, attempt);
35556
+ return attempt;
35557
+ }
35558
+ async claimAttempt(input) {
35559
+ const run = this.runs.get(input.runId);
35560
+ if (!run || isMemoryTerminal(run.status)) {
35561
+ return { ok: false, reason: run?.status === "cancelled" ? "RUN_CANCELLED" : "RUN_TERMINAL" };
35562
+ }
35563
+ const attempt = this.attempts.get(input.attemptId);
35564
+ if (!attempt)
35565
+ return { ok: false, reason: "NO_SUCH_ATTEMPT" };
35566
+ if (attempt.status === "terminal")
35567
+ return { ok: false, reason: "ATTEMPT_TERMINAL" };
35568
+ if (attempt.leaseGeneration !== input.expectedLeaseGeneration) {
35569
+ return { ok: false, reason: "STALE_GENERATION" };
35570
+ }
35571
+ const next = {
35572
+ ...attempt,
35573
+ leaseGeneration: attempt.leaseGeneration + 1,
35574
+ workerId: input.workerId,
35575
+ claimedAt: new Date().toISOString(),
35576
+ status: "leased"
35577
+ };
35578
+ this.attempts.set(attempt.attemptId, next);
35579
+ return { ok: true, attempt: next, leaseGeneration: next.leaseGeneration };
35580
+ }
35581
+ async recordLaunchIntent(input) {
35582
+ const run = this.runs.get(input.runId);
35583
+ if (!run || isMemoryTerminal(run.status)) {
35584
+ return { ok: false, reason: run?.status === "cancelled" ? "RUN_CANCELLED" : "RUN_TERMINAL" };
35585
+ }
35586
+ const attempt = this.attempts.get(input.attemptId);
35587
+ if (!attempt)
35588
+ return { ok: false, reason: "NO_SUCH_ATTEMPT" };
35589
+ if (attempt.status === "terminal")
35590
+ return { ok: false, reason: "ATTEMPT_TERMINAL" };
35591
+ const next = {
35592
+ ...attempt,
35593
+ clientToken: input.clientToken,
35594
+ requestDigest: input.requestDigest,
35595
+ startedBy: input.startedBy,
35596
+ launchState: "launching"
35597
+ };
35598
+ this.attempts.set(attempt.attemptId, next);
35599
+ this.runs.set(input.runId, { ...run, currentAttemptId: attempt.attemptId, updatedAt: new Date().toISOString() });
35600
+ return { ok: true, attempt: next };
35601
+ }
35602
+ async recordLaunchState(input) {
35603
+ const attempt = this.attempts.get(input.attemptId);
35604
+ if (!attempt)
35605
+ return null;
35606
+ const next = {
35607
+ ...attempt,
35608
+ launchState: input.launchState,
35609
+ ...input.taskId === undefined ? {} : { taskId: input.taskId }
35610
+ };
35611
+ this.attempts.set(attempt.attemptId, next);
35612
+ return next;
35613
+ }
35614
+ async recordTransition(transition) {
35615
+ this.transitions.push(transition);
35616
+ }
35617
+ async markAttemptTerminal(runId2, attemptId) {
35618
+ const attempt = this.attempts.get(attemptId);
35619
+ if (!attempt)
35620
+ return null;
35621
+ const next = { ...attempt, status: "terminal" };
35622
+ this.attempts.set(attemptId, next);
35623
+ return next;
35624
+ }
35625
+ async writeReceipt(receipt) {
35626
+ this.receipts.set(receipt.runId + "\x00" + receipt.attemptId, receipt);
35627
+ return receipt;
35628
+ }
35629
+ async getReceipt(runId2, attemptId) {
35630
+ return this.receipts.get(runId2 + "\x00" + attemptId) ?? null;
35631
+ }
35632
+ async finalizeRun(runId2, status, receiptId) {
35633
+ const run = this.runs.get(runId2);
35634
+ if (!run)
35635
+ return null;
35636
+ for (const [attemptId, attempt] of this.attempts) {
35637
+ if (attempt.runId === runId2 && attempt.status !== "terminal") {
35638
+ this.attempts.set(attemptId, { ...attempt, status: "terminal" });
35639
+ }
35640
+ }
35641
+ const next = {
35642
+ ...run,
35643
+ status,
35644
+ terminalReceiptId: receiptId,
35645
+ updatedAt: new Date().toISOString()
35646
+ };
35647
+ this.runs.set(runId2, next);
35648
+ return next;
35649
+ }
35650
+ async setRunStatus(runId2, status) {
35651
+ const run = this.runs.get(runId2);
35652
+ if (!run)
35653
+ return null;
35654
+ const next = { ...run, status, updatedAt: new Date().toISOString() };
35655
+ this.runs.set(runId2, next);
35656
+ return next;
35657
+ }
35658
+ }
35659
+ function isMemoryTerminal(status) {
35660
+ return status === "succeeded" || status === "failed" || status === "cancelled";
35661
+ }
35662
+ function digestKey(input) {
35663
+ return [input.tenantId, input.skillId, input.skillVersion, input.bundleDigest, input.inputDigest].join("\x00");
35664
+ }
35665
+
35666
+ // src/sdk/execution/admission.ts
35667
+ var DEFAULT_RUN_LIMITS = {
35668
+ maxDurationMs: 10 * 60 * 1000,
35669
+ maxMemoryMb: 512,
35670
+ maxCpuUnits: 256,
35671
+ maxArtifactsBytes: 10 * 1024 * 1024,
35672
+ maxConcurrency: 1
35673
+ };
35674
+
35675
+ // src/sdk/execution/dispatchers/ecs.ts
35676
+ var LIVE_TASK_STATUSES = new Set(["PROVISIONING", "PENDING", "ACTIVATING", "RUNNING", "DEACTIVATING", "STOPPING", "DEPROVISIONING"]);
35677
+
35328
35678
  // src/server/config.ts
35329
35679
  var DATABASE_URL_ENV = "HASNA_SKILLS_DATABASE_URL";
35330
35680
  var SKILLS_ENV_NAMESPACE = "SKILLS";
@@ -35344,6 +35694,7 @@ function resolveServerConfig(env = process.env) {
35344
35694
  seedBundledCorpus: (env.HASNA_SKILLS_SEED_BUNDLED_CORPUS ?? "1") !== "0",
35345
35695
  artifactBucket: env.HASNA_SKILLS_S3_BUCKET || env.SKILLS_S3_BUCKET || undefined,
35346
35696
  artifactPrefix: normalizePrefix(env.HASNA_SKILLS_S3_PREFIX || env.SKILLS_S3_PREFIX || "skills/artifacts"),
35697
+ runArtifactPrefix: env.HASNA_SKILLS_S3_RUN_PREFIX ? normalizePrefix(env.HASNA_SKILLS_S3_RUN_PREFIX) : undefined,
35347
35698
  inlineWorker: env.HASNA_SKILLS_INLINE_WORKER === "1",
35348
35699
  bundleSigningKey: env.HASNA_SKILLS_API_SIGNING_KEY || env.HASNA_SKILLS_SIGNING_KEY || undefined,
35349
35700
  requestBodyLimitBytes: parsePositiveInt(env.HASNA_SKILLS_REQUEST_BODY_LIMIT_BYTES, 1e6),
@@ -35370,7 +35721,7 @@ function normalizePrefix(value) {
35370
35721
  import { createHash as createHash5 } from "crypto";
35371
35722
 
35372
35723
  // src/server/store.ts
35373
- import { randomUUID as randomUUID3 } from "crypto";
35724
+ import { randomUUID as randomUUID4 } from "crypto";
35374
35725
  function recordFieldsOf(input, carriedBundle, carriedSkillMd) {
35375
35726
  return {
35376
35727
  slug: input.slug,
@@ -35415,6 +35766,7 @@ function instantiateStore(target, sqliteOptions) {
35415
35766
  }
35416
35767
 
35417
35768
  class MemorySkillsStore {
35769
+ selectionStore = new MemorySkillSelectionStore;
35418
35770
  backend = { kind: "memory", durable: false, label: "memory (non-durable)" };
35419
35771
  apiKeys = new Map;
35420
35772
  runs = new Map;
@@ -35459,7 +35811,7 @@ class MemorySkillsStore {
35459
35811
  input: input.input,
35460
35812
  args: input.args,
35461
35813
  ...idemKey ? { idempotencyKey: idemKey } : {},
35462
- correlationId: randomUUID3(),
35814
+ correlationId: randomUUID4(),
35463
35815
  costCents: 0,
35464
35816
  leaseGeneration: 0,
35465
35817
  createdAt: now
@@ -35725,6 +36077,9 @@ function versionKey(orgId, slug, version2) {
35725
36077
  }
35726
36078
 
35727
36079
  class PostgresSkillsStore {
36080
+ get selectionStore() {
36081
+ return new PostgresSkillSelectionStore(this.sql);
36082
+ }
35728
36083
  backend = { kind: "postgres", durable: true, label: "postgres" };
35729
36084
  sql;
35730
36085
  constructor(databaseUrl) {
@@ -35824,7 +36179,7 @@ class PostgresSkillsStore {
35824
36179
  const id = runId();
35825
36180
  const rows = await tx`
35826
36181
  INSERT INTO skills_runs (id, org_id, user_id, skill_slug, requested_slug, status, input_json, args_json, idempotency_key, correlation_id)
35827
- VALUES (${id}, ${input.principal.orgId}, ${input.principal.userId}, ${input.slug}, ${input.slug}, ${"queued"}, ${JSON.stringify(input.input)}::jsonb, ${JSON.stringify(input.args)}::jsonb, ${input.idempotencyKey ?? null}, ${randomUUID3()})
36182
+ VALUES (${id}, ${input.principal.orgId}, ${input.principal.userId}, ${input.slug}, ${input.slug}, ${"queued"}, ${JSON.stringify(input.input)}::jsonb, ${JSON.stringify(input.args)}::jsonb, ${input.idempotencyKey ?? null}, ${randomUUID4()})
35828
36183
  RETURNING *
35829
36184
  `;
35830
36185
  return rowToRun(rows[0]);
@@ -36474,7 +36829,7 @@ function assertDurableTarget(target, config) {
36474
36829
  }
36475
36830
 
36476
36831
  // src/server/worker.ts
36477
- async function runWorkerOnce(store, workerId = `worker_${randomUUID4().slice(0, 8)}`, storage = new ArtifactStorage) {
36832
+ async function runWorkerOnce(store, workerId = `worker_${randomUUID5().slice(0, 8)}`, storage = new ArtifactStorage) {
36478
36833
  const run = await store.claimNextRun({ workerId });
36479
36834
  if (!run)
36480
36835
  return false;
@@ -36509,9 +36864,9 @@ Environment:
36509
36864
  const config = resolveServerConfig();
36510
36865
  const store = await createStore({ databaseUrl: config.databaseUrl, bootstrapApiKey: config.bootstrapApiKey });
36511
36866
  assertDurableStore(store, config);
36512
- const storage = new ArtifactStorage({ bucket: config.artifactBucket, prefix: config.artifactPrefix });
36867
+ const storage = new ArtifactStorage({ bucket: config.artifactBucket, prefix: config.artifactPrefix, runPrefix: config.runArtifactPrefix });
36513
36868
  console.log(`skills worker ${process.env.HASNA_SKILLS_WORKER_ID || ""} storage: ${resolveDatabaseTarget(config.databaseUrl).label}`);
36514
- const workerId = process.env.HASNA_SKILLS_WORKER_ID || `worker_${randomUUID4().slice(0, 8)}`;
36869
+ const workerId = process.env.HASNA_SKILLS_WORKER_ID || `worker_${randomUUID5().slice(0, 8)}`;
36515
36870
  const once = process.argv.includes("--once");
36516
36871
  const idleMs = Number.parseInt(process.env.HASNA_SKILLS_WORKER_IDLE_MS || "1000", 10);
36517
36872
  const pause = Number.isFinite(idleMs) ? idleMs : 1000;
@@ -4246,7 +4246,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
4246
4246
  }, z.ZodTypeAny, "passthrough">>>;
4247
4247
  }, z.ZodTypeAny, "passthrough">>;
4248
4248
  readonly successStatuses: readonly [200];
4249
- readonly errorStatuses: readonly [401, 403, 404, 429, 500, 502, 503, 504];
4249
+ readonly errorStatuses: readonly [400, 401, 403, 404, 429, 500, 502, 503, 504];
4250
4250
  readonly querySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4251
4251
  readonly bodySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4252
4252
  readonly additiveResponseFields: "allowed";
@@ -4318,7 +4318,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
4318
4318
  }, z.ZodTypeAny, "passthrough">>;
4319
4319
  }, z.ZodTypeAny, "passthrough">>;
4320
4320
  readonly successStatuses: readonly [200];
4321
- readonly errorStatuses: readonly [401, 403, 404, 429, 500, 502, 503, 504];
4321
+ readonly errorStatuses: readonly [400, 401, 403, 404, 429, 500, 502, 503, 504];
4322
4322
  readonly querySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4323
4323
  readonly additiveResponseFields: "allowed";
4324
4324
  };
@@ -4389,7 +4389,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
4389
4389
  }, z.ZodTypeAny, "passthrough">>;
4390
4390
  }, z.ZodTypeAny, "passthrough">>;
4391
4391
  readonly successStatuses: readonly [200];
4392
- readonly errorStatuses: readonly [401, 403, 404, 429, 500, 502, 503, 504];
4392
+ readonly errorStatuses: readonly [400, 401, 403, 404, 429, 500, 502, 503, 504];
4393
4393
  readonly querySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4394
4394
  readonly additiveResponseFields: "allowed";
4395
4395
  };
@@ -4505,7 +4505,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
4505
4505
  offset: z.ZodNumber;
4506
4506
  }, z.ZodTypeAny, "passthrough">>;
4507
4507
  readonly successStatuses: readonly [200];
4508
- readonly errorStatuses: readonly [401, 403, 429, 500, 502, 503, 504];
4508
+ readonly errorStatuses: readonly [400, 401, 403, 429, 500, 502, 503, 504];
4509
4509
  readonly pathSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4510
4510
  readonly bodySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4511
4511
  readonly additiveResponseFields: "allowed";
@@ -4645,7 +4645,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
4645
4645
  }, z.ZodTypeAny, "passthrough">>;
4646
4646
  }, z.ZodTypeAny, "passthrough">>;
4647
4647
  readonly successStatuses: readonly [200];
4648
- readonly errorStatuses: readonly [401, 403, 404, 429, 500, 502, 503, 504];
4648
+ readonly errorStatuses: readonly [400, 401, 403, 404, 429, 500, 502, 503, 504];
4649
4649
  readonly querySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4650
4650
  readonly additiveResponseFields: "allowed";
4651
4651
  };
@@ -4716,7 +4716,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
4716
4716
  }, z.ZodTypeAny, "passthrough">>;
4717
4717
  }, z.ZodTypeAny, "passthrough">>;
4718
4718
  readonly successStatuses: readonly [200];
4719
- readonly errorStatuses: readonly [401, 403, 404, 429, 500, 502, 503, 504];
4719
+ readonly errorStatuses: readonly [400, 401, 403, 404, 429, 500, 502, 503, 504];
4720
4720
  readonly querySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4721
4721
  readonly additiveResponseFields: "allowed";
4722
4722
  };
@@ -4868,7 +4868,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
4868
4868
  offset: z.ZodNumber;
4869
4869
  }, z.ZodTypeAny, "passthrough">>;
4870
4870
  readonly successStatuses: readonly [200];
4871
- readonly errorStatuses: readonly [401, 403, 429, 500, 502, 503, 504];
4871
+ readonly errorStatuses: readonly [400, 401, 403, 429, 500, 502, 503, 504];
4872
4872
  readonly pathSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4873
4873
  readonly bodySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
4874
4874
  readonly additiveResponseFields: "allowed";
@@ -5599,7 +5599,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
5599
5599
  offset: z.ZodNumber;
5600
5600
  }, z.ZodTypeAny, "passthrough">>;
5601
5601
  readonly successStatuses: readonly [200];
5602
- readonly errorStatuses: readonly [401, 403, 429, 500, 502, 503, 504];
5602
+ readonly errorStatuses: readonly [400, 401, 403, 429, 500, 502, 503, 504];
5603
5603
  readonly pathSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
5604
5604
  readonly bodySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
5605
5605
  readonly additiveResponseFields: "allowed";
@@ -5757,7 +5757,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
5757
5757
  }, z.ZodTypeAny, "passthrough">>;
5758
5758
  }, z.ZodTypeAny, "passthrough">>;
5759
5759
  readonly successStatuses: readonly [200];
5760
- readonly errorStatuses: readonly [401, 403, 404, 429, 500, 502, 503, 504];
5760
+ readonly errorStatuses: readonly [400, 401, 403, 404, 429, 500, 502, 503, 504];
5761
5761
  readonly querySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
5762
5762
  readonly bodySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
5763
5763
  readonly additiveResponseFields: "allowed";
@@ -5817,7 +5817,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
5817
5817
  }, z.ZodTypeAny, "passthrough">>;
5818
5818
  }, z.ZodTypeAny, "passthrough">>;
5819
5819
  readonly successStatuses: readonly [200];
5820
- readonly errorStatuses: readonly [401, 403, 404, 409, 429, 500, 502, 503, 504];
5820
+ readonly errorStatuses: readonly [400, 401, 403, 404, 409, 429, 500, 502, 503, 504];
5821
5821
  readonly querySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
5822
5822
  readonly additiveResponseFields: "allowed";
5823
5823
  };
@@ -5951,7 +5951,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
5951
5951
  offset: z.ZodNumber;
5952
5952
  }, z.ZodTypeAny, "passthrough">>;
5953
5953
  readonly successStatuses: readonly [200];
5954
- readonly errorStatuses: readonly [401, 403, 429, 500, 502, 503, 504];
5954
+ readonly errorStatuses: readonly [400, 401, 403, 429, 500, 502, 503, 504];
5955
5955
  readonly pathSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
5956
5956
  readonly bodySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
5957
5957
  readonly additiveResponseFields: "allowed";
@@ -6104,7 +6104,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
6104
6104
  offset: z.ZodNumber;
6105
6105
  }, z.ZodTypeAny, "passthrough">>;
6106
6106
  readonly successStatuses: readonly [200];
6107
- readonly errorStatuses: readonly [401, 403, 429, 500, 502, 503, 504];
6107
+ readonly errorStatuses: readonly [400, 401, 403, 429, 500, 502, 503, 504];
6108
6108
  readonly pathSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
6109
6109
  readonly bodySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
6110
6110
  readonly additiveResponseFields: "allowed";
@@ -6197,7 +6197,7 @@ export declare const SKILLS_ADMIN_OPERATIONS: {
6197
6197
  offset: z.ZodNumber;
6198
6198
  }, z.ZodTypeAny, "passthrough">>;
6199
6199
  readonly successStatuses: readonly [200];
6200
- readonly errorStatuses: readonly [401, 403, 429, 500, 502, 503, 504];
6200
+ readonly errorStatuses: readonly [400, 401, 403, 429, 500, 502, 503, 504];
6201
6201
  readonly pathSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
6202
6202
  readonly bodySchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
6203
6203
  readonly additiveResponseFields: "allowed";