@sema-agent/server 1.321.0 → 1.322.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.
@@ -156,6 +156,13 @@ export const PG_SCHEMA_STATEMENTS = [
156
156
  result TEXT NOT NULL,
157
157
  created_at BIGINT NOT NULL,
158
158
  PRIMARY KEY (run_id, ordinal)
159
+ )`,
160
+ `CREATE TABLE IF NOT EXISTS workflow_resume_claim (
161
+ source_run_id VARCHAR(191) NOT NULL,
162
+ scope VARCHAR(190) NOT NULL,
163
+ new_run_id VARCHAR(191) NOT NULL,
164
+ claimed_at BIGINT NOT NULL,
165
+ PRIMARY KEY (source_run_id, scope)
159
166
  )`,
160
167
  `CREATE TABLE IF NOT EXISTS workflow_run (
161
168
  id VARCHAR(191) NOT NULL,
@@ -312,6 +312,13 @@ export const SCHEMA_STATEMENTS = [
312
312
  result MEDIUMTEXT NOT NULL,
313
313
  created_at BIGINT NOT NULL,
314
314
  PRIMARY KEY (run_id, ordinal)
315
+ )`,
316
+ `CREATE TABLE IF NOT EXISTS workflow_resume_claim (
317
+ source_run_id VARCHAR(191) NOT NULL,
318
+ scope VARCHAR(190) NOT NULL,
319
+ new_run_id VARCHAR(191) NOT NULL,
320
+ claimed_at BIGINT NOT NULL,
321
+ PRIMARY KEY (source_run_id, scope)
315
322
  )`,
316
323
  `CREATE TABLE IF NOT EXISTS workflow_run (
317
324
  id VARCHAR(191) NOT NULL,
@@ -2,9 +2,13 @@ import type { Pool as MySqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
3
  import { type WorkflowJournalEntry, type WorkflowJournalStore } from "@sema-agent/core";
4
4
  import { type SqlDriver } from "./sql-driver.js";
5
+ export interface SqlWorkflowJournalStoreOptions {
6
+ resumeClaimTtlMs?: number;
7
+ }
5
8
  export declare class SqlWorkflowJournalStore implements WorkflowJournalStore {
6
9
  private readonly db;
7
- constructor(db: SqlDriver);
10
+ private readonly resumeClaimTtlMs;
11
+ constructor(db: SqlDriver, opts?: SqlWorkflowJournalStoreOptions);
8
12
  private q;
9
13
  append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
10
14
  load(runId: string, scope: string): Promise<WorkflowJournalEntry[]>;
@@ -19,12 +23,25 @@ export declare class SqlWorkflowJournalStore implements WorkflowJournalStore {
19
23
  resultBytes: number;
20
24
  }>>;
21
25
  deleteByRun(runId: string): Promise<number>;
26
+ resumeClaim(input: {
27
+ sourceRunId: string;
28
+ newRunId: string;
29
+ scope: string;
30
+ }): Promise<{
31
+ granted: boolean;
32
+ holder?: string;
33
+ }>;
34
+ releaseResumeClaim(input: {
35
+ sourceRunId: string;
36
+ newRunId: string;
37
+ scope: string;
38
+ }): Promise<void>;
22
39
  reapExpired(now: number, maxAgeMs: number): Promise<number>;
23
40
  }
24
41
  export declare class TiDBWorkflowJournalStore extends SqlWorkflowJournalStore {
25
- constructor(pool: MySqlPool);
42
+ constructor(pool: MySqlPool, opts?: SqlWorkflowJournalStoreOptions);
26
43
  }
27
44
  export declare class PgWorkflowJournalStore extends SqlWorkflowJournalStore {
28
- constructor(pool: PgPool);
45
+ constructor(pool: PgPool, opts?: SqlWorkflowJournalStoreOptions);
29
46
  }
30
47
  //# sourceMappingURL=workflow-journal-store-sql.d.ts.map
@@ -1,10 +1,13 @@
1
1
  import { callKeyOrdinal } from "@sema-agent/core";
2
2
  import { oversizeJournalResult } from "./workflow-journal-limits.js";
3
3
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
4
+ const DEFAULT_RESUME_CLAIM_TTL_MS = 15 * 60 * 1000;
4
5
  export class SqlWorkflowJournalStore {
5
6
  db;
6
- constructor(db) {
7
+ resumeClaimTtlMs;
8
+ constructor(db, opts) {
7
9
  this.db = db;
10
+ this.resumeClaimTtlMs = opts?.resumeClaimTtlMs ?? DEFAULT_RESUME_CLAIM_TTL_MS;
8
11
  }
9
12
  q(tidb, pg) {
10
13
  return this.db.dialect === "tidb" ? tidb : pg;
@@ -35,19 +38,40 @@ export class SqlWorkflowJournalStore {
35
38
  const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE run_id = ?", "DELETE FROM workflow_journal WHERE run_id = $1"), [runId]);
36
39
  return res.affected;
37
40
  }
41
+ async resumeClaim(input) {
42
+ const now = Date.now();
43
+ const ins = await this.db.query(this.q("INSERT IGNORE INTO workflow_resume_claim (source_run_id, scope, new_run_id, claimed_at) VALUES (?, ?, ?, ?)", "INSERT INTO workflow_resume_claim (source_run_id, scope, new_run_id, claimed_at) VALUES ($1, $2, $3, $4) ON CONFLICT (source_run_id, scope) DO NOTHING"), [input.sourceRunId, input.scope, input.newRunId, now]);
44
+ if (ins.affected === 1)
45
+ return { granted: true };
46
+ const refresh = await this.db.query(this.q("UPDATE workflow_resume_claim SET claimed_at = ? WHERE source_run_id = ? AND scope = ? AND new_run_id = ?", "UPDATE workflow_resume_claim SET claimed_at = $1 WHERE source_run_id = $2 AND scope = $3 AND new_run_id = $4"), [now, input.sourceRunId, input.scope, input.newRunId]);
47
+ if (refresh.affected >= 1)
48
+ return { granted: true };
49
+ const takeover = await this.db.query(this.q("UPDATE workflow_resume_claim SET new_run_id = ?, claimed_at = ? WHERE source_run_id = ? AND scope = ? AND claimed_at < ?", "UPDATE workflow_resume_claim SET new_run_id = $1, claimed_at = $2 WHERE source_run_id = $3 AND scope = $4 AND claimed_at < $5"), [input.newRunId, now, input.sourceRunId, input.scope, now - this.resumeClaimTtlMs]);
50
+ if (takeover.affected >= 1)
51
+ return { granted: true };
52
+ const holder = await this.db.query(this.q("SELECT new_run_id FROM workflow_resume_claim WHERE source_run_id = ? AND scope = ?", "SELECT new_run_id FROM workflow_resume_claim WHERE source_run_id = $1 AND scope = $2"), [input.sourceRunId, input.scope]);
53
+ const row = holder.rows[0];
54
+ const holderId = row?.new_run_id === undefined ? undefined : String(row.new_run_id);
55
+ if (holderId === input.newRunId)
56
+ return { granted: true };
57
+ return { granted: false, holder: holderId };
58
+ }
59
+ async releaseResumeClaim(input) {
60
+ await this.db.query(this.q("DELETE FROM workflow_resume_claim WHERE source_run_id = ? AND scope = ? AND new_run_id = ?", "DELETE FROM workflow_resume_claim WHERE source_run_id = $1 AND scope = $2 AND new_run_id = $3"), [input.sourceRunId, input.scope, input.newRunId]);
61
+ }
38
62
  async reapExpired(now, maxAgeMs) {
39
63
  const res = await this.db.query(this.q("DELETE FROM workflow_journal WHERE created_at < ?", "DELETE FROM workflow_journal WHERE created_at < $1"), [now - maxAgeMs]);
40
64
  return res.affected;
41
65
  }
42
66
  }
43
67
  export class TiDBWorkflowJournalStore extends SqlWorkflowJournalStore {
44
- constructor(pool) {
45
- super(mysqlDriver(pool));
68
+ constructor(pool, opts) {
69
+ super(mysqlDriver(pool), opts);
46
70
  }
47
71
  }
48
72
  export class PgWorkflowJournalStore extends SqlWorkflowJournalStore {
49
- constructor(pool) {
50
- super(pgDriver(pool));
73
+ constructor(pool, opts) {
74
+ super(pgDriver(pool), opts);
51
75
  }
52
76
  }
53
77
  //# sourceMappingURL=workflow-journal-store-sql.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "1.321.0",
3
+ "version": "1.322.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",