ai-whisper 0.2.0 → 0.2.1

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.
@@ -1031,6 +1031,7 @@ function rowToRecord(row) {
1031
1031
  clipSample: row.clip_sample,
1032
1032
  turnSample: row.turn_sample,
1033
1033
  abortedByRaceGuard: row.aborted_by_race_guard === 1,
1034
+ interferenceDetected: row.interference_detected === 1,
1034
1035
  createdAt: row.created_at
1035
1036
  };
1036
1037
  }
@@ -1038,8 +1039,9 @@ function insertCaptureDiagnostic(db, input) {
1038
1039
  db.prepare(`INSERT INTO relay_capture_diagnostics
1039
1040
  (capture_id, handoff_id, collab_id, chain_id, workflow_id, target_provider,
1040
1041
  capture_status, clip_len, turn_len, turn_confidence, jaccard_score,
1041
- containment_score, clip_sample, turn_sample, aborted_by_race_guard, created_at)
1042
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.captureId, input.handoffId, input.collabId, input.chainId, input.workflowId, input.targetProvider, input.captureStatus, input.clipLen, input.turnLen, input.turnConfidence, input.jaccardScore, input.containmentScore, input.clipSample, input.turnSample, input.abortedByRaceGuard ? 1 : 0, input.createdAt);
1042
+ containment_score, clip_sample, turn_sample, aborted_by_race_guard,
1043
+ interference_detected, created_at)
1044
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.captureId, input.handoffId, input.collabId, input.chainId, input.workflowId, input.targetProvider, input.captureStatus, input.clipLen, input.turnLen, input.turnConfidence, input.jaccardScore, input.containmentScore, input.clipSample, input.turnSample, input.abortedByRaceGuard ? 1 : 0, input.interferenceDetected ? 1 : 0, input.createdAt);
1043
1045
  }
1044
1046
  function listCaptureDiagnosticsByCollab(db, collabId2, limit, opts) {
1045
1047
  const filter = opts?.workflowFilter;
@@ -3865,6 +3867,7 @@ function createControlService(db, events) {
3865
3867
  clipSample: input.clipSample,
3866
3868
  turnSample: input.turnSample,
3867
3869
  abortedByRaceGuard: input.abortedByRaceGuard,
3870
+ interferenceDetected: input.interferenceDetected ?? false,
3868
3871
  createdAt: input.now
3869
3872
  });
3870
3873
  return { captureId };
@@ -3941,8 +3944,130 @@ function createBrokerApp(input) {
3941
3944
  return app;
3942
3945
  }
3943
3946
 
3947
+ // ../broker/dist/storage/enforce-one-active-collab.js
3948
+ function defaultIsPidAlive(pid) {
3949
+ try {
3950
+ process.kill(pid, 0);
3951
+ return true;
3952
+ } catch (err) {
3953
+ return err.code === "EPERM";
3954
+ }
3955
+ }
3956
+ function dedupeActiveCollabs(db, opts) {
3957
+ const rows = db.prepare(`SELECT c.collab_id, c.workspace_id, c.created_at, d.pid AS pid,
3958
+ (SELECT COUNT(*) FROM workflows w
3959
+ WHERE w.collab_id = c.collab_id AND w.status = 'running') AS running_workflows
3960
+ FROM collab c
3961
+ LEFT JOIN broker_daemon d ON d.collab_id = c.collab_id
3962
+ WHERE c.status = 'active' AND c.workspace_id IS NOT NULL`).all();
3963
+ const byWorkspace = /* @__PURE__ */ new Map();
3964
+ for (const row of rows) {
3965
+ const key = row.workspace_id;
3966
+ const list = byWorkspace.get(key);
3967
+ if (list)
3968
+ list.push(row);
3969
+ else
3970
+ byWorkspace.set(key, [row]);
3971
+ }
3972
+ const conflicted = [];
3973
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3974
+ const stop = db.prepare("UPDATE collab SET status = 'stopped', stopped_at = ?, updated_at = ? WHERE collab_id = ?");
3975
+ for (const [workspaceId, group] of byWorkspace) {
3976
+ if (group.length < 2)
3977
+ continue;
3978
+ const workflowOwners = group.filter((r) => r.running_workflows > 0);
3979
+ if (workflowOwners.length >= 2) {
3980
+ conflicted.push(workspaceId);
3981
+ opts.warn(`Workspace ${workspaceId} has ${workflowOwners.length} active collabs that each own a running workflow: ${workflowOwners.map((r) => r.collab_id).join(", ")}. Refusing to auto-stop any (that would orphan a live run). Stop the extra collab(s) manually with \`whisper collab stop --collab <id>\`. The one-active-collab-per-workspace index will be created automatically once only one remains.`);
3982
+ continue;
3983
+ }
3984
+ let survivor;
3985
+ if (workflowOwners.length === 1) {
3986
+ survivor = workflowOwners[0];
3987
+ } else {
3988
+ const live = group.filter((r) => r.pid !== null && opts.isPidAlive(r.pid));
3989
+ if (live.length >= 1) {
3990
+ survivor = live.reduce((a, b) => a.created_at >= b.created_at ? a : b);
3991
+ } else {
3992
+ survivor = group.reduce((a, b) => a.created_at >= b.created_at ? a : b);
3993
+ }
3994
+ }
3995
+ for (const row of group) {
3996
+ if (row.collab_id === survivor.collab_id)
3997
+ continue;
3998
+ stop.run(now, now, row.collab_id);
3999
+ }
4000
+ }
4001
+ return conflicted;
4002
+ }
4003
+ var CREATE_INDEX_SQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_collab_one_active_per_workspace ON collab(workspace_id) WHERE status = 'active'";
4004
+ function residualDuplicateCount(db) {
4005
+ const row = db.prepare(`SELECT COUNT(*) AS n FROM (
4006
+ SELECT workspace_id FROM collab
4007
+ WHERE status = 'active' AND workspace_id IS NOT NULL
4008
+ GROUP BY workspace_id HAVING COUNT(*) > 1
4009
+ )`).get();
4010
+ return row.n;
4011
+ }
4012
+ function enforceOneActiveCollabPerWorkspace(db, options = {}) {
4013
+ const opts = {
4014
+ isPidAlive: options.isPidAlive ?? defaultIsPidAlive,
4015
+ warn: options.warn ?? ((m) => console.error(m))
4016
+ };
4017
+ const tx = db.transaction(() => {
4018
+ dedupeActiveCollabs(db, opts);
4019
+ if (residualDuplicateCount(db) === 0) {
4020
+ db.exec(CREATE_INDEX_SQL);
4021
+ } else {
4022
+ opts.warn("Skipping idx_collab_one_active_per_workspace: an irreducible duplicate active collab remains. The index will be created on a later startup once only one active collab per workspace exists.");
4023
+ }
4024
+ });
4025
+ tx();
4026
+ }
4027
+
4028
+ // ../broker/dist/storage/clipboard-capture-lease.js
4029
+ var LEASE_ID = 1;
4030
+ var DEFAULT_LEASE_TTL_MS = 5e3;
4031
+ function defaultIsPidAlive2(pid) {
4032
+ try {
4033
+ process.kill(pid, 0);
4034
+ return true;
4035
+ } catch (err) {
4036
+ return err.code === "EPERM";
4037
+ }
4038
+ }
4039
+ function resolveOptions(options) {
4040
+ return {
4041
+ isPidAlive: options.isPidAlive ?? defaultIsPidAlive2,
4042
+ ttlMs: options.ttlMs ?? DEFAULT_LEASE_TTL_MS,
4043
+ now: options.now ?? Date.now
4044
+ };
4045
+ }
4046
+ function isStale(row, opts) {
4047
+ if (row.holder_collab_id === null)
4048
+ return true;
4049
+ if (row.holder_pid === null || !opts.isPidAlive(row.holder_pid))
4050
+ return true;
4051
+ if (row.acquired_at === null)
4052
+ return true;
4053
+ const age = opts.now() - Date.parse(row.acquired_at);
4054
+ return age > opts.ttlMs;
4055
+ }
4056
+ function sweepStaleCaptureLease(db, options = {}) {
4057
+ const opts = resolveOptions(options);
4058
+ const tx = db.transaction(() => {
4059
+ const row = db.prepare("SELECT holder_collab_id, holder_pid, acquired_at FROM clipboard_capture_lease WHERE id = ?").get(LEASE_ID);
4060
+ if (!row || row.holder_collab_id === null)
4061
+ return;
4062
+ if (isStale(row, opts)) {
4063
+ db.prepare("UPDATE clipboard_capture_lease SET holder_collab_id = NULL, holder_pid = NULL, acquired_at = NULL WHERE id = ?").run(LEASE_ID);
4064
+ }
4065
+ });
4066
+ tx();
4067
+ }
4068
+
3944
4069
  // ../broker/dist/storage/apply-migrations.js
3945
- var CURRENT_SCHEMA_VERSION = 4;
4070
+ var CURRENT_SCHEMA_VERSION = 5;
3946
4071
  var initMigrationSql = `
3947
4072
  CREATE TABLE IF NOT EXISTS broker_state (
3948
4073
  id INTEGER PRIMARY KEY CHECK (id = 1),
@@ -4229,6 +4354,13 @@ CREATE TABLE IF NOT EXISTS recovery_state (
4229
4354
  recovered_at TEXT
4230
4355
  );
4231
4356
 
4357
+ CREATE TABLE IF NOT EXISTS clipboard_capture_lease (
4358
+ id INTEGER PRIMARY KEY CHECK (id = 1),
4359
+ holder_collab_id TEXT,
4360
+ holder_pid INTEGER,
4361
+ acquired_at TEXT
4362
+ );
4363
+
4232
4364
  `;
4233
4365
  function ensureBrokerStateRow(db) {
4234
4366
  db.prepare(`INSERT INTO broker_state (id, schema_version, migrated)
@@ -4239,20 +4371,20 @@ function ensureBrokerStateRow(db) {
4239
4371
  }
4240
4372
  function applyMigrations(db) {
4241
4373
  const current = db.pragma("user_version", { simple: true });
4242
- if (current >= CURRENT_SCHEMA_VERSION) {
4243
- ensureBrokerStateRow(db);
4244
- return;
4245
- }
4246
- db.exec("BEGIN EXCLUSIVE");
4247
- try {
4248
- runMigrationBody(db);
4249
- db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
4250
- ensureBrokerStateRow(db);
4251
- db.exec("COMMIT");
4252
- } catch (err) {
4253
- db.exec("ROLLBACK");
4254
- throw err;
4374
+ if (current < CURRENT_SCHEMA_VERSION) {
4375
+ db.exec("BEGIN EXCLUSIVE");
4376
+ try {
4377
+ runMigrationBody(db);
4378
+ db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
4379
+ db.exec("COMMIT");
4380
+ } catch (err) {
4381
+ db.exec("ROLLBACK");
4382
+ throw err;
4383
+ }
4255
4384
  }
4385
+ ensureBrokerStateRow(db);
4386
+ enforceOneActiveCollabPerWorkspace(db);
4387
+ sweepStaleCaptureLease(db);
4256
4388
  }
4257
4389
  function runMigrationBody(db) {
4258
4390
  db.exec(initMigrationSql);
@@ -4389,6 +4521,7 @@ function runMigrationBody(db) {
4389
4521
  clip_sample TEXT,
4390
4522
  turn_sample TEXT,
4391
4523
  aborted_by_race_guard INTEGER NOT NULL DEFAULT 0,
4524
+ interference_detected INTEGER NOT NULL DEFAULT 0,
4392
4525
  created_at TEXT NOT NULL
4393
4526
  );
4394
4527
  CREATE INDEX IF NOT EXISTS idx_relay_capture_diagnostics_collab_created
@@ -4400,6 +4533,10 @@ function runMigrationBody(db) {
4400
4533
  CREATE INDEX IF NOT EXISTS idx_relay_capture_diagnostics_status
4401
4534
  ON relay_capture_diagnostics (capture_status);
4402
4535
  `);
4536
+ const captureDiagColumns = db.prepare("PRAGMA table_info(relay_capture_diagnostics)").all();
4537
+ if (!captureDiagColumns.some((column) => column.name === "interference_detected")) {
4538
+ db.exec("ALTER TABLE relay_capture_diagnostics ADD COLUMN interference_detected INTEGER NOT NULL DEFAULT 0");
4539
+ }
4403
4540
  db.exec(`
4404
4541
  CREATE TABLE IF NOT EXISTS relay_evaluator_diagnostics (
4405
4542
  evaluator_id TEXT PRIMARY KEY,
@@ -1064,6 +1064,7 @@ function rowToRecord(row) {
1064
1064
  clipSample: row.clip_sample,
1065
1065
  turnSample: row.turn_sample,
1066
1066
  abortedByRaceGuard: row.aborted_by_race_guard === 1,
1067
+ interferenceDetected: row.interference_detected === 1,
1067
1068
  createdAt: row.created_at
1068
1069
  };
1069
1070
  }
@@ -1071,8 +1072,9 @@ function insertCaptureDiagnostic(db, input) {
1071
1072
  db.prepare(`INSERT INTO relay_capture_diagnostics
1072
1073
  (capture_id, handoff_id, collab_id, chain_id, workflow_id, target_provider,
1073
1074
  capture_status, clip_len, turn_len, turn_confidence, jaccard_score,
1074
- containment_score, clip_sample, turn_sample, aborted_by_race_guard, created_at)
1075
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.captureId, input.handoffId, input.collabId, input.chainId, input.workflowId, input.targetProvider, input.captureStatus, input.clipLen, input.turnLen, input.turnConfidence, input.jaccardScore, input.containmentScore, input.clipSample, input.turnSample, input.abortedByRaceGuard ? 1 : 0, input.createdAt);
1075
+ containment_score, clip_sample, turn_sample, aborted_by_race_guard,
1076
+ interference_detected, created_at)
1077
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.captureId, input.handoffId, input.collabId, input.chainId, input.workflowId, input.targetProvider, input.captureStatus, input.clipLen, input.turnLen, input.turnConfidence, input.jaccardScore, input.containmentScore, input.clipSample, input.turnSample, input.abortedByRaceGuard ? 1 : 0, input.interferenceDetected ? 1 : 0, input.createdAt);
1076
1078
  }
1077
1079
  function listCaptureDiagnosticsByCollab(db, collabId, limit, opts) {
1078
1080
  const filter = opts?.workflowFilter;
@@ -3898,6 +3900,7 @@ function createControlService(db, events) {
3898
3900
  clipSample: input.clipSample,
3899
3901
  turnSample: input.turnSample,
3900
3902
  abortedByRaceGuard: input.abortedByRaceGuard,
3903
+ interferenceDetected: input.interferenceDetected ?? false,
3901
3904
  createdAt: input.now
3902
3905
  });
3903
3906
  return { captureId };
@@ -3974,8 +3977,130 @@ function createBrokerApp(input) {
3974
3977
  return app;
3975
3978
  }
3976
3979
 
3980
+ // ../broker/dist/storage/enforce-one-active-collab.js
3981
+ function defaultIsPidAlive(pid) {
3982
+ try {
3983
+ process.kill(pid, 0);
3984
+ return true;
3985
+ } catch (err) {
3986
+ return err.code === "EPERM";
3987
+ }
3988
+ }
3989
+ function dedupeActiveCollabs(db, opts) {
3990
+ const rows = db.prepare(`SELECT c.collab_id, c.workspace_id, c.created_at, d.pid AS pid,
3991
+ (SELECT COUNT(*) FROM workflows w
3992
+ WHERE w.collab_id = c.collab_id AND w.status = 'running') AS running_workflows
3993
+ FROM collab c
3994
+ LEFT JOIN broker_daemon d ON d.collab_id = c.collab_id
3995
+ WHERE c.status = 'active' AND c.workspace_id IS NOT NULL`).all();
3996
+ const byWorkspace = /* @__PURE__ */ new Map();
3997
+ for (const row of rows) {
3998
+ const key = row.workspace_id;
3999
+ const list = byWorkspace.get(key);
4000
+ if (list)
4001
+ list.push(row);
4002
+ else
4003
+ byWorkspace.set(key, [row]);
4004
+ }
4005
+ const conflicted = [];
4006
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4007
+ const stop = db.prepare("UPDATE collab SET status = 'stopped', stopped_at = ?, updated_at = ? WHERE collab_id = ?");
4008
+ for (const [workspaceId, group] of byWorkspace) {
4009
+ if (group.length < 2)
4010
+ continue;
4011
+ const workflowOwners = group.filter((r) => r.running_workflows > 0);
4012
+ if (workflowOwners.length >= 2) {
4013
+ conflicted.push(workspaceId);
4014
+ opts.warn(`Workspace ${workspaceId} has ${workflowOwners.length} active collabs that each own a running workflow: ${workflowOwners.map((r) => r.collab_id).join(", ")}. Refusing to auto-stop any (that would orphan a live run). Stop the extra collab(s) manually with \`whisper collab stop --collab <id>\`. The one-active-collab-per-workspace index will be created automatically once only one remains.`);
4015
+ continue;
4016
+ }
4017
+ let survivor;
4018
+ if (workflowOwners.length === 1) {
4019
+ survivor = workflowOwners[0];
4020
+ } else {
4021
+ const live = group.filter((r) => r.pid !== null && opts.isPidAlive(r.pid));
4022
+ if (live.length >= 1) {
4023
+ survivor = live.reduce((a, b) => a.created_at >= b.created_at ? a : b);
4024
+ } else {
4025
+ survivor = group.reduce((a, b) => a.created_at >= b.created_at ? a : b);
4026
+ }
4027
+ }
4028
+ for (const row of group) {
4029
+ if (row.collab_id === survivor.collab_id)
4030
+ continue;
4031
+ stop.run(now, now, row.collab_id);
4032
+ }
4033
+ }
4034
+ return conflicted;
4035
+ }
4036
+ var CREATE_INDEX_SQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_collab_one_active_per_workspace ON collab(workspace_id) WHERE status = 'active'";
4037
+ function residualDuplicateCount(db) {
4038
+ const row = db.prepare(`SELECT COUNT(*) AS n FROM (
4039
+ SELECT workspace_id FROM collab
4040
+ WHERE status = 'active' AND workspace_id IS NOT NULL
4041
+ GROUP BY workspace_id HAVING COUNT(*) > 1
4042
+ )`).get();
4043
+ return row.n;
4044
+ }
4045
+ function enforceOneActiveCollabPerWorkspace(db, options = {}) {
4046
+ const opts = {
4047
+ isPidAlive: options.isPidAlive ?? defaultIsPidAlive,
4048
+ warn: options.warn ?? ((m) => console.error(m))
4049
+ };
4050
+ const tx = db.transaction(() => {
4051
+ dedupeActiveCollabs(db, opts);
4052
+ if (residualDuplicateCount(db) === 0) {
4053
+ db.exec(CREATE_INDEX_SQL);
4054
+ } else {
4055
+ opts.warn("Skipping idx_collab_one_active_per_workspace: an irreducible duplicate active collab remains. The index will be created on a later startup once only one active collab per workspace exists.");
4056
+ }
4057
+ });
4058
+ tx();
4059
+ }
4060
+
4061
+ // ../broker/dist/storage/clipboard-capture-lease.js
4062
+ var LEASE_ID = 1;
4063
+ var DEFAULT_LEASE_TTL_MS = 5e3;
4064
+ function defaultIsPidAlive2(pid) {
4065
+ try {
4066
+ process.kill(pid, 0);
4067
+ return true;
4068
+ } catch (err) {
4069
+ return err.code === "EPERM";
4070
+ }
4071
+ }
4072
+ function resolveOptions(options) {
4073
+ return {
4074
+ isPidAlive: options.isPidAlive ?? defaultIsPidAlive2,
4075
+ ttlMs: options.ttlMs ?? DEFAULT_LEASE_TTL_MS,
4076
+ now: options.now ?? Date.now
4077
+ };
4078
+ }
4079
+ function isStale(row, opts) {
4080
+ if (row.holder_collab_id === null)
4081
+ return true;
4082
+ if (row.holder_pid === null || !opts.isPidAlive(row.holder_pid))
4083
+ return true;
4084
+ if (row.acquired_at === null)
4085
+ return true;
4086
+ const age = opts.now() - Date.parse(row.acquired_at);
4087
+ return age > opts.ttlMs;
4088
+ }
4089
+ function sweepStaleCaptureLease(db, options = {}) {
4090
+ const opts = resolveOptions(options);
4091
+ const tx = db.transaction(() => {
4092
+ const row = db.prepare("SELECT holder_collab_id, holder_pid, acquired_at FROM clipboard_capture_lease WHERE id = ?").get(LEASE_ID);
4093
+ if (!row || row.holder_collab_id === null)
4094
+ return;
4095
+ if (isStale(row, opts)) {
4096
+ db.prepare("UPDATE clipboard_capture_lease SET holder_collab_id = NULL, holder_pid = NULL, acquired_at = NULL WHERE id = ?").run(LEASE_ID);
4097
+ }
4098
+ });
4099
+ tx();
4100
+ }
4101
+
3977
4102
  // ../broker/dist/storage/apply-migrations.js
3978
- var CURRENT_SCHEMA_VERSION = 4;
4103
+ var CURRENT_SCHEMA_VERSION = 5;
3979
4104
  var initMigrationSql = `
3980
4105
  CREATE TABLE IF NOT EXISTS broker_state (
3981
4106
  id INTEGER PRIMARY KEY CHECK (id = 1),
@@ -4262,6 +4387,13 @@ CREATE TABLE IF NOT EXISTS recovery_state (
4262
4387
  recovered_at TEXT
4263
4388
  );
4264
4389
 
4390
+ CREATE TABLE IF NOT EXISTS clipboard_capture_lease (
4391
+ id INTEGER PRIMARY KEY CHECK (id = 1),
4392
+ holder_collab_id TEXT,
4393
+ holder_pid INTEGER,
4394
+ acquired_at TEXT
4395
+ );
4396
+
4265
4397
  `;
4266
4398
  function ensureBrokerStateRow(db) {
4267
4399
  db.prepare(`INSERT INTO broker_state (id, schema_version, migrated)
@@ -4272,20 +4404,20 @@ function ensureBrokerStateRow(db) {
4272
4404
  }
4273
4405
  function applyMigrations(db) {
4274
4406
  const current = db.pragma("user_version", { simple: true });
4275
- if (current >= CURRENT_SCHEMA_VERSION) {
4276
- ensureBrokerStateRow(db);
4277
- return;
4278
- }
4279
- db.exec("BEGIN EXCLUSIVE");
4280
- try {
4281
- runMigrationBody(db);
4282
- db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
4283
- ensureBrokerStateRow(db);
4284
- db.exec("COMMIT");
4285
- } catch (err) {
4286
- db.exec("ROLLBACK");
4287
- throw err;
4407
+ if (current < CURRENT_SCHEMA_VERSION) {
4408
+ db.exec("BEGIN EXCLUSIVE");
4409
+ try {
4410
+ runMigrationBody(db);
4411
+ db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
4412
+ db.exec("COMMIT");
4413
+ } catch (err) {
4414
+ db.exec("ROLLBACK");
4415
+ throw err;
4416
+ }
4288
4417
  }
4418
+ ensureBrokerStateRow(db);
4419
+ enforceOneActiveCollabPerWorkspace(db);
4420
+ sweepStaleCaptureLease(db);
4289
4421
  }
4290
4422
  function runMigrationBody(db) {
4291
4423
  db.exec(initMigrationSql);
@@ -4422,6 +4554,7 @@ function runMigrationBody(db) {
4422
4554
  clip_sample TEXT,
4423
4555
  turn_sample TEXT,
4424
4556
  aborted_by_race_guard INTEGER NOT NULL DEFAULT 0,
4557
+ interference_detected INTEGER NOT NULL DEFAULT 0,
4425
4558
  created_at TEXT NOT NULL
4426
4559
  );
4427
4560
  CREATE INDEX IF NOT EXISTS idx_relay_capture_diagnostics_collab_created
@@ -4433,6 +4566,10 @@ function runMigrationBody(db) {
4433
4566
  CREATE INDEX IF NOT EXISTS idx_relay_capture_diagnostics_status
4434
4567
  ON relay_capture_diagnostics (capture_status);
4435
4568
  `);
4569
+ const captureDiagColumns = db.prepare("PRAGMA table_info(relay_capture_diagnostics)").all();
4570
+ if (!captureDiagColumns.some((column) => column.name === "interference_detected")) {
4571
+ db.exec("ALTER TABLE relay_capture_diagnostics ADD COLUMN interference_detected INTEGER NOT NULL DEFAULT 0");
4572
+ }
4436
4573
  db.exec(`
4437
4574
  CREATE TABLE IF NOT EXISTS relay_evaluator_diagnostics (
4438
4575
  evaluator_id TEXT PRIMARY KEY,
@@ -1034,6 +1034,7 @@ function rowToRecord(row) {
1034
1034
  clipSample: row.clip_sample,
1035
1035
  turnSample: row.turn_sample,
1036
1036
  abortedByRaceGuard: row.aborted_by_race_guard === 1,
1037
+ interferenceDetected: row.interference_detected === 1,
1037
1038
  createdAt: row.created_at
1038
1039
  };
1039
1040
  }
@@ -1041,8 +1042,9 @@ function insertCaptureDiagnostic(db, input) {
1041
1042
  db.prepare(`INSERT INTO relay_capture_diagnostics
1042
1043
  (capture_id, handoff_id, collab_id, chain_id, workflow_id, target_provider,
1043
1044
  capture_status, clip_len, turn_len, turn_confidence, jaccard_score,
1044
- containment_score, clip_sample, turn_sample, aborted_by_race_guard, created_at)
1045
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.captureId, input.handoffId, input.collabId, input.chainId, input.workflowId, input.targetProvider, input.captureStatus, input.clipLen, input.turnLen, input.turnConfidence, input.jaccardScore, input.containmentScore, input.clipSample, input.turnSample, input.abortedByRaceGuard ? 1 : 0, input.createdAt);
1045
+ containment_score, clip_sample, turn_sample, aborted_by_race_guard,
1046
+ interference_detected, created_at)
1047
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.captureId, input.handoffId, input.collabId, input.chainId, input.workflowId, input.targetProvider, input.captureStatus, input.clipLen, input.turnLen, input.turnConfidence, input.jaccardScore, input.containmentScore, input.clipSample, input.turnSample, input.abortedByRaceGuard ? 1 : 0, input.interferenceDetected ? 1 : 0, input.createdAt);
1046
1048
  }
1047
1049
  function listCaptureDiagnosticsByCollab(db, collabId2, limit, opts) {
1048
1050
  const filter = opts?.workflowFilter;
@@ -3868,6 +3870,7 @@ function createControlService(db, events) {
3868
3870
  clipSample: input.clipSample,
3869
3871
  turnSample: input.turnSample,
3870
3872
  abortedByRaceGuard: input.abortedByRaceGuard,
3873
+ interferenceDetected: input.interferenceDetected ?? false,
3871
3874
  createdAt: input.now
3872
3875
  });
3873
3876
  return { captureId };
@@ -3944,8 +3947,130 @@ function createBrokerApp(input) {
3944
3947
  return app;
3945
3948
  }
3946
3949
 
3950
+ // ../broker/dist/storage/enforce-one-active-collab.js
3951
+ function defaultIsPidAlive(pid) {
3952
+ try {
3953
+ process.kill(pid, 0);
3954
+ return true;
3955
+ } catch (err) {
3956
+ return err.code === "EPERM";
3957
+ }
3958
+ }
3959
+ function dedupeActiveCollabs(db, opts) {
3960
+ const rows = db.prepare(`SELECT c.collab_id, c.workspace_id, c.created_at, d.pid AS pid,
3961
+ (SELECT COUNT(*) FROM workflows w
3962
+ WHERE w.collab_id = c.collab_id AND w.status = 'running') AS running_workflows
3963
+ FROM collab c
3964
+ LEFT JOIN broker_daemon d ON d.collab_id = c.collab_id
3965
+ WHERE c.status = 'active' AND c.workspace_id IS NOT NULL`).all();
3966
+ const byWorkspace = /* @__PURE__ */ new Map();
3967
+ for (const row of rows) {
3968
+ const key = row.workspace_id;
3969
+ const list = byWorkspace.get(key);
3970
+ if (list)
3971
+ list.push(row);
3972
+ else
3973
+ byWorkspace.set(key, [row]);
3974
+ }
3975
+ const conflicted = [];
3976
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3977
+ const stop = db.prepare("UPDATE collab SET status = 'stopped', stopped_at = ?, updated_at = ? WHERE collab_id = ?");
3978
+ for (const [workspaceId, group] of byWorkspace) {
3979
+ if (group.length < 2)
3980
+ continue;
3981
+ const workflowOwners = group.filter((r) => r.running_workflows > 0);
3982
+ if (workflowOwners.length >= 2) {
3983
+ conflicted.push(workspaceId);
3984
+ opts.warn(`Workspace ${workspaceId} has ${workflowOwners.length} active collabs that each own a running workflow: ${workflowOwners.map((r) => r.collab_id).join(", ")}. Refusing to auto-stop any (that would orphan a live run). Stop the extra collab(s) manually with \`whisper collab stop --collab <id>\`. The one-active-collab-per-workspace index will be created automatically once only one remains.`);
3985
+ continue;
3986
+ }
3987
+ let survivor;
3988
+ if (workflowOwners.length === 1) {
3989
+ survivor = workflowOwners[0];
3990
+ } else {
3991
+ const live = group.filter((r) => r.pid !== null && opts.isPidAlive(r.pid));
3992
+ if (live.length >= 1) {
3993
+ survivor = live.reduce((a, b) => a.created_at >= b.created_at ? a : b);
3994
+ } else {
3995
+ survivor = group.reduce((a, b) => a.created_at >= b.created_at ? a : b);
3996
+ }
3997
+ }
3998
+ for (const row of group) {
3999
+ if (row.collab_id === survivor.collab_id)
4000
+ continue;
4001
+ stop.run(now, now, row.collab_id);
4002
+ }
4003
+ }
4004
+ return conflicted;
4005
+ }
4006
+ var CREATE_INDEX_SQL = "CREATE UNIQUE INDEX IF NOT EXISTS idx_collab_one_active_per_workspace ON collab(workspace_id) WHERE status = 'active'";
4007
+ function residualDuplicateCount(db) {
4008
+ const row = db.prepare(`SELECT COUNT(*) AS n FROM (
4009
+ SELECT workspace_id FROM collab
4010
+ WHERE status = 'active' AND workspace_id IS NOT NULL
4011
+ GROUP BY workspace_id HAVING COUNT(*) > 1
4012
+ )`).get();
4013
+ return row.n;
4014
+ }
4015
+ function enforceOneActiveCollabPerWorkspace(db, options = {}) {
4016
+ const opts = {
4017
+ isPidAlive: options.isPidAlive ?? defaultIsPidAlive,
4018
+ warn: options.warn ?? ((m) => console.error(m))
4019
+ };
4020
+ const tx = db.transaction(() => {
4021
+ dedupeActiveCollabs(db, opts);
4022
+ if (residualDuplicateCount(db) === 0) {
4023
+ db.exec(CREATE_INDEX_SQL);
4024
+ } else {
4025
+ opts.warn("Skipping idx_collab_one_active_per_workspace: an irreducible duplicate active collab remains. The index will be created on a later startup once only one active collab per workspace exists.");
4026
+ }
4027
+ });
4028
+ tx();
4029
+ }
4030
+
4031
+ // ../broker/dist/storage/clipboard-capture-lease.js
4032
+ var LEASE_ID = 1;
4033
+ var DEFAULT_LEASE_TTL_MS = 5e3;
4034
+ function defaultIsPidAlive2(pid) {
4035
+ try {
4036
+ process.kill(pid, 0);
4037
+ return true;
4038
+ } catch (err) {
4039
+ return err.code === "EPERM";
4040
+ }
4041
+ }
4042
+ function resolveOptions(options) {
4043
+ return {
4044
+ isPidAlive: options.isPidAlive ?? defaultIsPidAlive2,
4045
+ ttlMs: options.ttlMs ?? DEFAULT_LEASE_TTL_MS,
4046
+ now: options.now ?? Date.now
4047
+ };
4048
+ }
4049
+ function isStale(row, opts) {
4050
+ if (row.holder_collab_id === null)
4051
+ return true;
4052
+ if (row.holder_pid === null || !opts.isPidAlive(row.holder_pid))
4053
+ return true;
4054
+ if (row.acquired_at === null)
4055
+ return true;
4056
+ const age = opts.now() - Date.parse(row.acquired_at);
4057
+ return age > opts.ttlMs;
4058
+ }
4059
+ function sweepStaleCaptureLease(db, options = {}) {
4060
+ const opts = resolveOptions(options);
4061
+ const tx = db.transaction(() => {
4062
+ const row = db.prepare("SELECT holder_collab_id, holder_pid, acquired_at FROM clipboard_capture_lease WHERE id = ?").get(LEASE_ID);
4063
+ if (!row || row.holder_collab_id === null)
4064
+ return;
4065
+ if (isStale(row, opts)) {
4066
+ db.prepare("UPDATE clipboard_capture_lease SET holder_collab_id = NULL, holder_pid = NULL, acquired_at = NULL WHERE id = ?").run(LEASE_ID);
4067
+ }
4068
+ });
4069
+ tx();
4070
+ }
4071
+
3947
4072
  // ../broker/dist/storage/apply-migrations.js
3948
- var CURRENT_SCHEMA_VERSION = 4;
4073
+ var CURRENT_SCHEMA_VERSION = 5;
3949
4074
  var initMigrationSql = `
3950
4075
  CREATE TABLE IF NOT EXISTS broker_state (
3951
4076
  id INTEGER PRIMARY KEY CHECK (id = 1),
@@ -4232,6 +4357,13 @@ CREATE TABLE IF NOT EXISTS recovery_state (
4232
4357
  recovered_at TEXT
4233
4358
  );
4234
4359
 
4360
+ CREATE TABLE IF NOT EXISTS clipboard_capture_lease (
4361
+ id INTEGER PRIMARY KEY CHECK (id = 1),
4362
+ holder_collab_id TEXT,
4363
+ holder_pid INTEGER,
4364
+ acquired_at TEXT
4365
+ );
4366
+
4235
4367
  `;
4236
4368
  function ensureBrokerStateRow(db) {
4237
4369
  db.prepare(`INSERT INTO broker_state (id, schema_version, migrated)
@@ -4242,20 +4374,20 @@ function ensureBrokerStateRow(db) {
4242
4374
  }
4243
4375
  function applyMigrations(db) {
4244
4376
  const current = db.pragma("user_version", { simple: true });
4245
- if (current >= CURRENT_SCHEMA_VERSION) {
4246
- ensureBrokerStateRow(db);
4247
- return;
4248
- }
4249
- db.exec("BEGIN EXCLUSIVE");
4250
- try {
4251
- runMigrationBody(db);
4252
- db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
4253
- ensureBrokerStateRow(db);
4254
- db.exec("COMMIT");
4255
- } catch (err) {
4256
- db.exec("ROLLBACK");
4257
- throw err;
4377
+ if (current < CURRENT_SCHEMA_VERSION) {
4378
+ db.exec("BEGIN EXCLUSIVE");
4379
+ try {
4380
+ runMigrationBody(db);
4381
+ db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
4382
+ db.exec("COMMIT");
4383
+ } catch (err) {
4384
+ db.exec("ROLLBACK");
4385
+ throw err;
4386
+ }
4258
4387
  }
4388
+ ensureBrokerStateRow(db);
4389
+ enforceOneActiveCollabPerWorkspace(db);
4390
+ sweepStaleCaptureLease(db);
4259
4391
  }
4260
4392
  function runMigrationBody(db) {
4261
4393
  db.exec(initMigrationSql);
@@ -4392,6 +4524,7 @@ function runMigrationBody(db) {
4392
4524
  clip_sample TEXT,
4393
4525
  turn_sample TEXT,
4394
4526
  aborted_by_race_guard INTEGER NOT NULL DEFAULT 0,
4527
+ interference_detected INTEGER NOT NULL DEFAULT 0,
4395
4528
  created_at TEXT NOT NULL
4396
4529
  );
4397
4530
  CREATE INDEX IF NOT EXISTS idx_relay_capture_diagnostics_collab_created
@@ -4403,6 +4536,10 @@ function runMigrationBody(db) {
4403
4536
  CREATE INDEX IF NOT EXISTS idx_relay_capture_diagnostics_status
4404
4537
  ON relay_capture_diagnostics (capture_status);
4405
4538
  `);
4539
+ const captureDiagColumns = db.prepare("PRAGMA table_info(relay_capture_diagnostics)").all();
4540
+ if (!captureDiagColumns.some((column) => column.name === "interference_detected")) {
4541
+ db.exec("ALTER TABLE relay_capture_diagnostics ADD COLUMN interference_detected INTEGER NOT NULL DEFAULT 0");
4542
+ }
4406
4543
  db.exec(`
4407
4544
  CREATE TABLE IF NOT EXISTS relay_evaluator_diagnostics (
4408
4545
  evaluator_id TEXT PRIMARY KEY,