@hasna/todos 0.11.56 → 0.11.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/storage.js CHANGED
@@ -3050,7 +3050,7 @@ var init_redaction = __esm(() => {
3050
3050
  });
3051
3051
 
3052
3052
  // src/lib/secret-redaction.ts
3053
- import { readFileSync as readFileSync2, existsSync as existsSync5 } from "fs";
3053
+ import { readFileSync as readFileSync2, existsSync as existsSync6 } from "fs";
3054
3054
  function registerCustomRedactor(fn) {
3055
3055
  customRedactors.push(fn);
3056
3056
  }
@@ -3115,7 +3115,7 @@ function scanAndRedactText(text, options = {}) {
3115
3115
  };
3116
3116
  }
3117
3117
  function scanFileForSecrets(path, options = {}) {
3118
- if (!existsSync5(path))
3118
+ if (!existsSync6(path))
3119
3119
  throw new Error(`File not found: ${path}`);
3120
3120
  const content = readFileSync2(path, "utf8");
3121
3121
  return scanAndRedactText(content, options);
@@ -4175,6 +4175,7 @@ function explainRunnerSandbox(input = {}) {
4175
4175
  // src/lib/event-hooks.ts
4176
4176
  init_config();
4177
4177
  var LOCAL_EVENT_TYPES = [
4178
+ "task.created",
4178
4179
  "task.assigned",
4179
4180
  "task.blocked",
4180
4181
  "task.started",
@@ -4417,6 +4418,568 @@ async function testLocalEventHook(name, input) {
4417
4418
  return emitLocalEventHooks({ ...input, hooks: [hook] });
4418
4419
  }
4419
4420
 
4421
+ // node_modules/.bun/@hasna+events@0.1.7/node_modules/@hasna/events/dist/index.js
4422
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
4423
+ import { existsSync as existsSync5 } from "fs";
4424
+ import { homedir } from "os";
4425
+ import { join as join4 } from "path";
4426
+ import { createHmac, timingSafeEqual } from "crypto";
4427
+ import { randomUUID as randomUUID2 } from "crypto";
4428
+ import { spawn } from "child_process";
4429
+ import { randomUUID as randomUUID22 } from "crypto";
4430
+ function getPathValue(input, path) {
4431
+ return path.split(".").reduce((value, part) => {
4432
+ if (value && typeof value === "object" && part in value) {
4433
+ return value[part];
4434
+ }
4435
+ return;
4436
+ }, input);
4437
+ }
4438
+ function wildcardToRegExp(pattern) {
4439
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
4440
+ return new RegExp(`^${escaped}$`);
4441
+ }
4442
+ function matchString(value, matcher) {
4443
+ if (matcher === undefined)
4444
+ return true;
4445
+ if (value === undefined)
4446
+ return false;
4447
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
4448
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
4449
+ }
4450
+ function matchRecord(input, matcher) {
4451
+ if (!matcher)
4452
+ return true;
4453
+ return Object.entries(matcher).every(([path, expected]) => {
4454
+ const actual = getPathValue(input, path);
4455
+ if (typeof expected === "string" || Array.isArray(expected)) {
4456
+ return matchString(actual === undefined ? undefined : String(actual), expected);
4457
+ }
4458
+ return actual === expected;
4459
+ });
4460
+ }
4461
+ function eventMatchesFilter(event, filter) {
4462
+ return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
4463
+ }
4464
+ function channelMatchesEvent(channel, event) {
4465
+ if (!channel.enabled)
4466
+ return false;
4467
+ if (!channel.filters || channel.filters.length === 0)
4468
+ return true;
4469
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
4470
+ }
4471
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
4472
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
4473
+ function getEventsDataDir(override) {
4474
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir(), ".hasna", "events");
4475
+ }
4476
+
4477
+ class JsonEventsStore {
4478
+ dataDir;
4479
+ channelsPath;
4480
+ eventsPath;
4481
+ deliveriesPath;
4482
+ constructor(dataDir = getEventsDataDir()) {
4483
+ this.dataDir = dataDir;
4484
+ this.channelsPath = join4(dataDir, "channels.json");
4485
+ this.eventsPath = join4(dataDir, "events.json");
4486
+ this.deliveriesPath = join4(dataDir, "deliveries.json");
4487
+ }
4488
+ async init() {
4489
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
4490
+ await chmod(this.dataDir, 448).catch(() => {
4491
+ return;
4492
+ });
4493
+ await this.ensureArrayFile(this.channelsPath);
4494
+ await this.ensureArrayFile(this.eventsPath);
4495
+ await this.ensureArrayFile(this.deliveriesPath);
4496
+ }
4497
+ async addChannel(channel) {
4498
+ await this.init();
4499
+ const channels = await this.readJson(this.channelsPath, []);
4500
+ const index = channels.findIndex((item) => item.id === channel.id);
4501
+ if (index >= 0) {
4502
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
4503
+ } else {
4504
+ channels.push(channel);
4505
+ }
4506
+ await this.writeJson(this.channelsPath, channels);
4507
+ return index >= 0 ? channels[index] : channel;
4508
+ }
4509
+ async listChannels() {
4510
+ await this.init();
4511
+ return this.readJson(this.channelsPath, []);
4512
+ }
4513
+ async getChannel(id) {
4514
+ const channels = await this.listChannels();
4515
+ return channels.find((channel) => channel.id === id);
4516
+ }
4517
+ async removeChannel(id) {
4518
+ await this.init();
4519
+ const channels = await this.readJson(this.channelsPath, []);
4520
+ const next = channels.filter((channel) => channel.id !== id);
4521
+ await this.writeJson(this.channelsPath, next);
4522
+ return next.length !== channels.length;
4523
+ }
4524
+ async appendEvent(event) {
4525
+ await this.init();
4526
+ const events = await this.readJson(this.eventsPath, []);
4527
+ events.push(event);
4528
+ await this.writeJson(this.eventsPath, events);
4529
+ return event;
4530
+ }
4531
+ async listEvents() {
4532
+ await this.init();
4533
+ return this.readJson(this.eventsPath, []);
4534
+ }
4535
+ async findEventByIdentity(identity) {
4536
+ const events = await this.listEvents();
4537
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
4538
+ }
4539
+ async appendDelivery(result) {
4540
+ await this.init();
4541
+ const deliveries = await this.readJson(this.deliveriesPath, []);
4542
+ deliveries.push(result);
4543
+ await this.writeJson(this.deliveriesPath, deliveries);
4544
+ return result;
4545
+ }
4546
+ async listDeliveries() {
4547
+ await this.init();
4548
+ return this.readJson(this.deliveriesPath, []);
4549
+ }
4550
+ async exportData() {
4551
+ return {
4552
+ channels: await this.listChannels(),
4553
+ events: await this.listEvents(),
4554
+ deliveries: await this.listDeliveries()
4555
+ };
4556
+ }
4557
+ async ensureArrayFile(path) {
4558
+ if (!existsSync5(path)) {
4559
+ await writeFile(path, `[]
4560
+ `, { encoding: "utf-8", mode: 384 });
4561
+ }
4562
+ await chmod(path, 384).catch(() => {
4563
+ return;
4564
+ });
4565
+ }
4566
+ async readJson(path, fallback) {
4567
+ try {
4568
+ const raw = await readFile(path, "utf-8");
4569
+ if (!raw.trim())
4570
+ return fallback;
4571
+ return JSON.parse(raw);
4572
+ } catch (error) {
4573
+ if (error.code === "ENOENT")
4574
+ return fallback;
4575
+ throw error;
4576
+ }
4577
+ }
4578
+ async writeJson(path, value) {
4579
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
4580
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
4581
+ `, { encoding: "utf-8", mode: 384 });
4582
+ await rename(tempPath, path);
4583
+ await chmod(path, 384).catch(() => {
4584
+ return;
4585
+ });
4586
+ }
4587
+ }
4588
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
4589
+ function buildSignatureBase(timestamp, body) {
4590
+ return `${timestamp}.${body}`;
4591
+ }
4592
+ function signPayload(secret, timestamp, body) {
4593
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
4594
+ return `sha256=${digest}`;
4595
+ }
4596
+ function now2() {
4597
+ return new Date().toISOString();
4598
+ }
4599
+ function truncate(value, max = 4096) {
4600
+ return value.length > max ? `${value.slice(0, max)}...` : value;
4601
+ }
4602
+ function buildWebhookRequest(event, channel) {
4603
+ if (!channel.webhook)
4604
+ throw new Error(`Channel ${channel.id} has no webhook config`);
4605
+ const body = JSON.stringify(event);
4606
+ const timestamp = event.time;
4607
+ const headers = {
4608
+ "Content-Type": "application/json",
4609
+ "User-Agent": "@hasna/events",
4610
+ "X-Hasna-Event-Id": event.id,
4611
+ "X-Hasna-Event-Type": event.type,
4612
+ "X-Hasna-Timestamp": timestamp,
4613
+ ...channel.webhook.headers
4614
+ };
4615
+ if (channel.webhook.secret) {
4616
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
4617
+ }
4618
+ return { body, headers };
4619
+ }
4620
+ async function dispatchWebhook(event, channel, options = {}) {
4621
+ if (!channel.webhook)
4622
+ throw new Error(`Channel ${channel.id} has no webhook config`);
4623
+ const startedAt = now2();
4624
+ const { body, headers } = buildWebhookRequest(event, channel);
4625
+ const controller = new AbortController;
4626
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
4627
+ try {
4628
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
4629
+ method: "POST",
4630
+ headers,
4631
+ body,
4632
+ signal: controller.signal
4633
+ });
4634
+ const responseBody = truncate(await response.text());
4635
+ return {
4636
+ attempt: 1,
4637
+ status: response.ok ? "success" : "failed",
4638
+ startedAt,
4639
+ completedAt: now2(),
4640
+ responseStatus: response.status,
4641
+ responseBody,
4642
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
4643
+ };
4644
+ } catch (error) {
4645
+ return {
4646
+ attempt: 1,
4647
+ status: "failed",
4648
+ startedAt,
4649
+ completedAt: now2(),
4650
+ error: error instanceof Error ? error.message : String(error)
4651
+ };
4652
+ } finally {
4653
+ clearTimeout(timeout);
4654
+ }
4655
+ }
4656
+ async function dispatchCommand(event, channel) {
4657
+ if (!channel.command)
4658
+ throw new Error(`Channel ${channel.id} has no command config`);
4659
+ const startedAt = now2();
4660
+ const eventJson = JSON.stringify(event);
4661
+ const env = {
4662
+ ...process.env,
4663
+ ...channel.command.env,
4664
+ HASNA_CHANNEL_ID: channel.id,
4665
+ HASNA_EVENT_ID: event.id,
4666
+ HASNA_EVENT_TYPE: event.type,
4667
+ HASNA_EVENT_SOURCE: event.source,
4668
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
4669
+ HASNA_EVENT_SEVERITY: event.severity,
4670
+ HASNA_EVENT_TIME: event.time,
4671
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
4672
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
4673
+ HASNA_EVENT_JSON: eventJson
4674
+ };
4675
+ return new Promise((resolve6) => {
4676
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
4677
+ cwd: channel.command.cwd,
4678
+ env,
4679
+ stdio: ["pipe", "pipe", "pipe"]
4680
+ });
4681
+ let stdout = "";
4682
+ let stderr = "";
4683
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
4684
+ child.stdin.end(eventJson);
4685
+ child.stdout.on("data", (chunk) => {
4686
+ stdout += chunk.toString();
4687
+ });
4688
+ child.stderr.on("data", (chunk) => {
4689
+ stderr += chunk.toString();
4690
+ });
4691
+ child.on("error", (error) => {
4692
+ clearTimeout(timeout);
4693
+ resolve6({
4694
+ attempt: 1,
4695
+ status: "failed",
4696
+ startedAt,
4697
+ completedAt: now2(),
4698
+ stdout: truncate(stdout),
4699
+ stderr: truncate(stderr),
4700
+ error: error.message
4701
+ });
4702
+ });
4703
+ child.on("close", (code, signal) => {
4704
+ clearTimeout(timeout);
4705
+ const success = code === 0;
4706
+ resolve6({
4707
+ attempt: 1,
4708
+ status: success ? "success" : "failed",
4709
+ startedAt,
4710
+ completedAt: now2(),
4711
+ stdout: truncate(stdout),
4712
+ stderr: truncate(stderr),
4713
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
4714
+ });
4715
+ });
4716
+ });
4717
+ }
4718
+ async function dispatchChannel(event, channel, options = {}) {
4719
+ if (channel.transport === "webhook")
4720
+ return dispatchWebhook(event, channel, options);
4721
+ if (channel.transport === "command")
4722
+ return dispatchCommand(event, channel);
4723
+ return {
4724
+ attempt: 1,
4725
+ status: "skipped",
4726
+ startedAt: now2(),
4727
+ completedAt: now2(),
4728
+ error: `Unsupported transport: ${channel.transport}`
4729
+ };
4730
+ }
4731
+ function createDeliveryResult(event, channel, attempts) {
4732
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
4733
+ return {
4734
+ id: randomUUID2(),
4735
+ eventId: event.id,
4736
+ channelId: channel.id,
4737
+ transport: channel.transport,
4738
+ status,
4739
+ attempts,
4740
+ createdAt: attempts[0]?.startedAt ?? now2(),
4741
+ completedAt: attempts.at(-1)?.completedAt ?? now2()
4742
+ };
4743
+ }
4744
+ function createEvent(input) {
4745
+ return {
4746
+ id: input.id ?? randomUUID22(),
4747
+ source: input.source,
4748
+ type: input.type,
4749
+ time: normalizeTime(input.time),
4750
+ subject: input.subject,
4751
+ severity: input.severity ?? "info",
4752
+ data: input.data ?? {},
4753
+ message: input.message,
4754
+ dedupeKey: input.dedupeKey,
4755
+ schemaVersion: input.schemaVersion ?? "1.0",
4756
+ metadata: input.metadata ?? {}
4757
+ };
4758
+ }
4759
+
4760
+ class EventsClient {
4761
+ store;
4762
+ redactors;
4763
+ transportOptions;
4764
+ constructor(options = {}) {
4765
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
4766
+ this.redactors = options.redactors ?? [];
4767
+ this.transportOptions = { fetchImpl: options.fetchImpl };
4768
+ }
4769
+ async addChannel(input) {
4770
+ const timestamp = new Date().toISOString();
4771
+ return this.store.addChannel({
4772
+ ...input,
4773
+ createdAt: input.createdAt ?? timestamp,
4774
+ updatedAt: input.updatedAt ?? timestamp
4775
+ });
4776
+ }
4777
+ async listChannels() {
4778
+ return this.store.listChannels();
4779
+ }
4780
+ async removeChannel(id) {
4781
+ return this.store.removeChannel(id);
4782
+ }
4783
+ async emit(input, options = {}) {
4784
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
4785
+ if (options.dedupe !== false) {
4786
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
4787
+ if (existing) {
4788
+ return { event: existing, deliveries: [], deduped: true };
4789
+ }
4790
+ }
4791
+ await this.store.appendEvent(event);
4792
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
4793
+ return { event, deliveries, deduped: false };
4794
+ }
4795
+ async listEvents() {
4796
+ return this.store.listEvents();
4797
+ }
4798
+ async listDeliveries() {
4799
+ return this.store.listDeliveries();
4800
+ }
4801
+ async deliver(event) {
4802
+ const channels = await this.store.listChannels();
4803
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
4804
+ const deliveries = [];
4805
+ for (const channel of selected) {
4806
+ const eventForChannel = await this.applyRedaction(event, channel);
4807
+ const result = await this.deliverWithRetry(eventForChannel, channel);
4808
+ await this.store.appendDelivery(result);
4809
+ deliveries.push(result);
4810
+ }
4811
+ return deliveries;
4812
+ }
4813
+ async testChannel(id, input = {}) {
4814
+ const channel = await this.store.getChannel(id);
4815
+ if (!channel)
4816
+ throw new Error(`Channel not found: ${id}`);
4817
+ const event = createEvent({
4818
+ source: input.source ?? "hasna.events",
4819
+ type: input.type ?? "events.test",
4820
+ subject: input.subject ?? id,
4821
+ severity: input.severity ?? "info",
4822
+ data: input.data ?? { test: true },
4823
+ message: input.message ?? "Hasna events test delivery",
4824
+ dedupeKey: input.dedupeKey,
4825
+ schemaVersion: input.schemaVersion,
4826
+ metadata: input.metadata,
4827
+ time: input.time,
4828
+ id: input.id
4829
+ });
4830
+ const eventForChannel = await this.applyRedaction(event, channel);
4831
+ const result = await this.deliverWithRetry(eventForChannel, channel);
4832
+ await this.store.appendDelivery(result);
4833
+ return result;
4834
+ }
4835
+ async replay(options = {}) {
4836
+ const events = (await this.store.listEvents()).filter((event) => {
4837
+ if (options.eventId && event.id !== options.eventId)
4838
+ return false;
4839
+ if (options.source && event.source !== options.source)
4840
+ return false;
4841
+ if (options.type && event.type !== options.type)
4842
+ return false;
4843
+ return true;
4844
+ });
4845
+ if (options.dryRun)
4846
+ return { events, deliveries: [] };
4847
+ const deliveries = [];
4848
+ for (const event of events) {
4849
+ deliveries.push(...await this.deliver(event));
4850
+ }
4851
+ return { events, deliveries };
4852
+ }
4853
+ async applyRedaction(event, channel) {
4854
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
4855
+ for (const redactor of this.redactors) {
4856
+ next = await redactor(next, channel);
4857
+ }
4858
+ return next;
4859
+ }
4860
+ async deliverWithRetry(event, channel) {
4861
+ const policy = normalizeRetryPolicy(channel.retry);
4862
+ const attempts = [];
4863
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
4864
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
4865
+ attempt.attempt = index + 1;
4866
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
4867
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
4868
+ }
4869
+ attempts.push(attempt);
4870
+ if (attempt.status !== "failed")
4871
+ break;
4872
+ if (attempt.nextBackoffMs)
4873
+ await Bun.sleep(attempt.nextBackoffMs);
4874
+ }
4875
+ return createDeliveryResult(event, channel, attempts);
4876
+ }
4877
+ }
4878
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
4879
+ if (paths.length === 0)
4880
+ return event;
4881
+ const copy = structuredClone(event);
4882
+ for (const path of paths) {
4883
+ setPath(copy, path, replacement);
4884
+ }
4885
+ return copy;
4886
+ }
4887
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
4888
+ return redactValue2(event, replacement);
4889
+ }
4890
+ function shouldRedactKey(key) {
4891
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
4892
+ }
4893
+ function redactValue2(value, replacement) {
4894
+ if (Array.isArray(value))
4895
+ return value.map((item) => redactValue2(item, replacement));
4896
+ if (!value || typeof value !== "object")
4897
+ return value;
4898
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
4899
+ key,
4900
+ shouldRedactKey(key) ? replacement : redactValue2(item, replacement)
4901
+ ]));
4902
+ }
4903
+ function setPath(input, path, replacement) {
4904
+ const parts = path.split(".");
4905
+ let cursor = input;
4906
+ for (const part of parts.slice(0, -1)) {
4907
+ const next = cursor[part];
4908
+ if (!next || typeof next !== "object")
4909
+ return;
4910
+ cursor = next;
4911
+ }
4912
+ const last = parts.at(-1);
4913
+ if (last && last in cursor)
4914
+ cursor[last] = replacement;
4915
+ }
4916
+ function normalizeTime(value) {
4917
+ if (!value)
4918
+ return new Date().toISOString();
4919
+ return value instanceof Date ? value.toISOString() : value;
4920
+ }
4921
+ function normalizeRetryPolicy(policy) {
4922
+ return {
4923
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
4924
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
4925
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
4926
+ };
4927
+ }
4928
+
4929
+ // src/lib/shared-events.ts
4930
+ var SOURCE = "todos";
4931
+ function taskEventData(task, extra = {}) {
4932
+ return {
4933
+ id: task.id,
4934
+ task_id: task.id,
4935
+ short_id: task.short_id,
4936
+ title: task.title,
4937
+ description: task.description,
4938
+ status: task.status,
4939
+ priority: task.priority,
4940
+ project_id: task.project_id,
4941
+ parent_id: task.parent_id,
4942
+ plan_id: task.plan_id,
4943
+ task_list_id: task.task_list_id,
4944
+ agent_id: task.agent_id,
4945
+ assigned_to: task.assigned_to,
4946
+ session_id: task.session_id,
4947
+ working_dir: task.working_dir,
4948
+ tags: task.tags,
4949
+ metadata: task.metadata,
4950
+ version: task.version,
4951
+ created_at: task.created_at,
4952
+ updated_at: task.updated_at,
4953
+ started_at: task.started_at,
4954
+ completed_at: task.completed_at,
4955
+ due_at: task.due_at,
4956
+ ...extra
4957
+ };
4958
+ }
4959
+ async function emitSharedTaskEvent(input) {
4960
+ const data = taskEventData(input.task, input.data);
4961
+ await new EventsClient().emit({
4962
+ source: SOURCE,
4963
+ type: input.type,
4964
+ subject: input.task.id,
4965
+ severity: input.severity ?? "info",
4966
+ message: input.message ?? `${input.type}: ${input.task.title}`,
4967
+ data,
4968
+ dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
4969
+ metadata: {
4970
+ package: "@hasna/todos",
4971
+ task_id: input.task.id,
4972
+ project_id: input.task.project_id,
4973
+ task_list_id: input.task.task_list_id
4974
+ }
4975
+ }, { deliver: true, dedupe: true });
4976
+ }
4977
+ function emitSharedTaskEventQuiet(input) {
4978
+ emitSharedTaskEvent(input).catch(() => {
4979
+ return;
4980
+ });
4981
+ }
4982
+
4420
4983
  // src/db/audit.ts
4421
4984
  init_database();
4422
4985
  function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
@@ -4679,7 +5242,7 @@ async function deliverWebhook(wh, event, body, attempt, db) {
4679
5242
  activeDeliveries--;
4680
5243
  }
4681
5244
  }
4682
- async function dispatchWebhook(event, payload, db) {
5245
+ async function dispatchWebhook2(event, payload, db) {
4683
5246
  const d = db || getDatabase();
4684
5247
  const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
4685
5248
  const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
@@ -4833,7 +5396,10 @@ function createTask(input, db) {
4833
5396
  insertTaskTags(id, tags, d);
4834
5397
  }
4835
5398
  const task = getTask(id, d);
4836
- dispatchWebhook("task.created", { id: task.id, short_id: task.short_id, title: task.title, status: task.status, priority: task.priority, project_id: task.project_id, assigned_to: task.assigned_to }, d).catch(() => {});
5399
+ const payload = taskEventData(task);
5400
+ dispatchWebhook2("task.created", payload, d).catch(() => {});
5401
+ emitLocalEventHooksQuiet({ type: "task.created", payload });
5402
+ emitSharedTaskEventQuiet({ type: "task.created", task });
4837
5403
  return task;
4838
5404
  }
4839
5405
  function getTask(id, db) {
@@ -5177,18 +5743,7 @@ function updateTask(id, input, db) {
5177
5743
  logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
5178
5744
  if (input.approved_by !== undefined)
5179
5745
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
5180
- if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
5181
- dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
5182
- emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id, assigned_to: input.assigned_to, title: task.title } });
5183
- }
5184
- if (input.status !== undefined && input.status !== task.status) {
5185
- dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
5186
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload: { id, old_status: task.status, new_status: input.status, title: task.title } });
5187
- }
5188
- if (input.approved_by !== undefined) {
5189
- emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
5190
- }
5191
- return {
5746
+ const updatedTask = {
5192
5747
  ...task,
5193
5748
  ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
5194
5749
  tags: input.tags ?? task.tags,
@@ -5206,6 +5761,22 @@ function updateTask(id, input, db) {
5206
5761
  approved_by: input.approved_by ?? task.approved_by,
5207
5762
  approved_at: input.approved_by ? timestamp : task.approved_at
5208
5763
  };
5764
+ if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
5765
+ const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
5766
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
5767
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
5768
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
5769
+ }
5770
+ if (input.status !== undefined && input.status !== task.status) {
5771
+ const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
5772
+ dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
5773
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
5774
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
5775
+ }
5776
+ if (input.approved_by !== undefined) {
5777
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
5778
+ }
5779
+ return updatedTask;
5209
5780
  }
5210
5781
  function deleteTask(id, db) {
5211
5782
  const d = db || getDatabase();
@@ -5938,9 +6509,12 @@ function startTask(id, agentId, db) {
5938
6509
  throw new Error(`Task ${id} could not be started because it changed during claim`);
5939
6510
  }
5940
6511
  logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
5941
- dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
5942
- emitLocalEventHooksQuiet({ type: "task.started", payload: { id, agent_id: agentId, title: task.title } });
5943
- return { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, started_at: task.started_at || timestamp, version: task.version + 1, updated_at: timestamp };
6512
+ const startedTask = { ...task, status: "in_progress", assigned_to: agentId, locked_by: agentId, locked_at: timestamp, started_at: task.started_at || timestamp, version: task.version + 1, updated_at: timestamp };
6513
+ const payload = taskEventData(startedTask, { agent_id: agentId });
6514
+ dispatchWebhook2("task.started", payload, d).catch(() => {});
6515
+ emitLocalEventHooksQuiet({ type: "task.started", payload });
6516
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
6517
+ return startedTask;
5944
6518
  }
5945
6519
  function completeTask(id, agentId, db, options) {
5946
6520
  const d = db || getDatabase();
@@ -5976,8 +6550,21 @@ function completeTask(id, agentId, db, options) {
5976
6550
  });
5977
6551
  tx();
5978
6552
  logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
5979
- dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
5980
- emitLocalEventHooksQuiet({ type: "task.completed", payload: { id, agent_id: agentId, title: task.title, completed_at: timestamp } });
6553
+ const completedTaskForEvent = {
6554
+ ...task,
6555
+ status: "completed",
6556
+ locked_by: null,
6557
+ locked_at: null,
6558
+ completed_at: timestamp,
6559
+ confidence,
6560
+ version: task.version + 1,
6561
+ updated_at: timestamp,
6562
+ metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
6563
+ };
6564
+ const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
6565
+ dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
6566
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
6567
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
5981
6568
  let spawnedTask = null;
5982
6569
  if (task.recurrence_rule && !options?.skip_recurrence) {
5983
6570
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -6018,8 +6605,12 @@ function completeTask(id, agentId, db, options) {
6018
6605
  if (unblockedDeps.length > 0) {
6019
6606
  meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
6020
6607
  for (const dep of unblockedDeps) {
6021
- dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
6022
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload: { id: dep.id, unblocked_by: id, title: dep.title } });
6608
+ const depTask = getTask(dep.id, d);
6609
+ const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
6610
+ dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
6611
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
6612
+ if (depTask)
6613
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
6023
6614
  }
6024
6615
  }
6025
6616
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -6205,9 +6796,6 @@ function failTask(id, agentId, reason, options, db) {
6205
6796
  const timestamp = now();
6206
6797
  d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
6207
6798
  WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
6208
- logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
6209
- dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
6210
- emitLocalEventHooksQuiet({ type: "task.failed", payload: { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title } });
6211
6799
  const failedTask = {
6212
6800
  ...task,
6213
6801
  status: "failed",
@@ -6217,6 +6805,11 @@ function failTask(id, agentId, reason, options, db) {
6217
6805
  version: task.version + 1,
6218
6806
  updated_at: timestamp
6219
6807
  };
6808
+ logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
6809
+ const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
6810
+ dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
6811
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
6812
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
6220
6813
  let retryTask;
6221
6814
  if (options?.retry) {
6222
6815
  const retryCount = (task.retry_count || 0) + 1;
@@ -6291,9 +6884,12 @@ function stealTask(agentId, opts, db) {
6291
6884
  return null;
6292
6885
  logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
6293
6886
  logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
6294
- dispatchWebhook("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
6295
- emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to } });
6296
- return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
6887
+ const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
6888
+ const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
6889
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
6890
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
6891
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
6892
+ return stolenTask;
6297
6893
  }
6298
6894
  function claimOrSteal(agentId, filters, db) {
6299
6895
  const d = db || getDatabase();
@@ -7352,8 +7948,8 @@ init_database();
7352
7948
  init_database();
7353
7949
  init_redaction();
7354
7950
  import { createHash as createHash2 } from "crypto";
7355
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
7356
- import { basename, dirname as dirname4, join as join4, resolve as resolve6 } from "path";
7951
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
7952
+ import { basename, dirname as dirname4, join as join5, resolve as resolve6 } from "path";
7357
7953
  import { tmpdir } from "os";
7358
7954
  function isInMemoryDb2(path) {
7359
7955
  return path === ":memory:" || path.startsWith("file::memory:");
@@ -7365,15 +7961,15 @@ function artifactStoreRoot() {
7365
7961
  return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
7366
7962
  const dbPath = getDatabasePath();
7367
7963
  if (isInMemoryDb2(dbPath))
7368
- return join4(tmpdir(), "hasna-todos-artifacts");
7369
- return join4(dirname4(resolve6(dbPath)), "artifacts");
7964
+ return join5(tmpdir(), "hasna-todos-artifacts");
7965
+ return join5(dirname4(resolve6(dbPath)), "artifacts");
7370
7966
  }
7371
7967
  function artifactStorePath(relativePath) {
7372
7968
  const normalized = relativePath.replace(/\\/g, "/");
7373
7969
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
7374
7970
  throw new Error("Invalid artifact store path");
7375
7971
  }
7376
- return join4(artifactStoreRoot(), normalized);
7972
+ return join5(artifactStoreRoot(), normalized);
7377
7973
  }
7378
7974
  function sha256(buffer) {
7379
7975
  return createHash2("sha256").update(buffer).digest("hex");
@@ -7414,7 +8010,7 @@ function mediaTypeFor(path, textLike) {
7414
8010
  }
7415
8011
  function storeArtifactContent(input) {
7416
8012
  const sourcePath = resolve6(input.path);
7417
- if (!existsSync6(sourcePath))
8013
+ if (!existsSync7(sourcePath))
7418
8014
  return null;
7419
8015
  const sourceStat = statSync2(sourcePath);
7420
8016
  if (!sourceStat.isFile())
@@ -7431,9 +8027,9 @@ function storeArtifactContent(input) {
7431
8027
  redactionStatus = "redacted";
7432
8028
  }
7433
8029
  const storedSha = sha256(storedBuffer);
7434
- const relativePath = join4("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
8030
+ const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
7435
8031
  const destination = artifactStorePath(relativePath);
7436
- if (!existsSync6(destination)) {
8032
+ if (!existsSync7(destination)) {
7437
8033
  mkdirSync4(dirname4(destination), { recursive: true });
7438
8034
  writeFileSync2(destination, storedBuffer);
7439
8035
  }
@@ -7493,7 +8089,7 @@ function verifyStoredArtifact(input) {
7493
8089
  };
7494
8090
  }
7495
8091
  const storedPath = artifactStorePath(store.relative_path);
7496
- if (!existsSync6(storedPath)) {
8092
+ if (!existsSync7(storedPath)) {
7497
8093
  return {
7498
8094
  id: input.id,
7499
8095
  path: input.path,
@@ -7574,15 +8170,15 @@ function getArtifactStoreRoot(dbPath) {
7574
8170
  return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
7575
8171
  const path = dbPath ?? getDatabasePath();
7576
8172
  if (isInMemoryDb2(path))
7577
- return join4(tmpdir(), "hasna-todos-artifacts");
7578
- return join4(dirname4(resolve6(path)), "artifacts");
8173
+ return join5(tmpdir(), "hasna-todos-artifacts");
8174
+ return join5(dirname4(resolve6(path)), "artifacts");
7579
8175
  }
7580
8176
  function computeContentHash(path) {
7581
8177
  return sha256(readFileSync3(resolve6(path)));
7582
8178
  }
7583
8179
  function storeArtifactFile(input) {
7584
8180
  const sourcePath = resolve6(input.sourcePath);
7585
- if (!existsSync6(sourcePath)) {
8181
+ if (!existsSync7(sourcePath)) {
7586
8182
  throw new Error(`Source file not found: ${input.sourcePath}`);
7587
8183
  }
7588
8184
  if (!statSync2(sourcePath).isFile()) {
@@ -7595,7 +8191,7 @@ function storeArtifactFile(input) {
7595
8191
  let localPath = sourcePath;
7596
8192
  if (storageMode === "copy") {
7597
8193
  const fileName = input.name && input.name.trim().length > 0 ? basename(input.name) : basename(sourcePath);
7598
- const destination = join4(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
8194
+ const destination = join5(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
7599
8195
  mkdirSync4(dirname4(destination), { recursive: true });
7600
8196
  writeFileSync2(destination, buffer);
7601
8197
  localPath = destination;
@@ -7605,7 +8201,7 @@ function storeArtifactFile(input) {
7605
8201
  function deleteStoredArtifactFile(localPath, storageMode, _dbPath2) {
7606
8202
  if (storageMode === "reference")
7607
8203
  return false;
7608
- if (!localPath || !existsSync6(localPath))
8204
+ if (!localPath || !existsSync7(localPath))
7609
8205
  return false;
7610
8206
  rmSync(localPath, { force: true });
7611
8207
  try {
@@ -7617,8 +8213,8 @@ function isArtifactExpired(deletedAt, policy = {}) {
7617
8213
  if (!deletedAt)
7618
8214
  return false;
7619
8215
  const retentionDays = policy.deleted_retention_days ?? DEFAULT_DELETED_RETENTION_DAYS;
7620
- const now2 = policy.now ?? new Date;
7621
- const ageMs = now2.getTime() - new Date(deletedAt).getTime();
8216
+ const now3 = policy.now ?? new Date;
8217
+ const ageMs = now3.getTime() - new Date(deletedAt).getTime();
7622
8218
  return ageMs > retentionDays * 24 * 60 * 60 * 1000;
7623
8219
  }
7624
8220
  function buildArtifactExportManifest(artifacts, dbPath) {
@@ -9794,7 +10390,7 @@ function createHybridTodosStorageAdapter(options) {
9794
10390
  }
9795
10391
 
9796
10392
  // src/storage/postgres-adapter.ts
9797
- import { randomUUID as randomUUID2 } from "crypto";
10393
+ import { randomUUID as randomUUID3 } from "crypto";
9798
10394
  function createPostgresTodosStorageAdapter(options) {
9799
10395
  const store = new PostgresJsonRecordStore(options);
9800
10396
  const adapter = {
@@ -9977,7 +10573,7 @@ async function createTask2(input, store, context) {
9977
10573
  const timestamp = new Date().toISOString();
9978
10574
  const shortId = input.project_id ? await nextTaskShortId2(input.project_id, store, context) : null;
9979
10575
  const task = {
9980
- id: randomUUID2(),
10576
+ id: randomUUID3(),
9981
10577
  short_id: shortId,
9982
10578
  project_id: input.project_id ?? context?.projectId ?? null,
9983
10579
  parent_id: input.parent_id ?? null,
@@ -10144,7 +10740,7 @@ async function getChangedSince(since, filters, store) {
10144
10740
  async function createProject2(input, store, context) {
10145
10741
  const timestamp = new Date().toISOString();
10146
10742
  const project = {
10147
- id: randomUUID2(),
10743
+ id: randomUUID3(),
10148
10744
  name: input.name,
10149
10745
  path: input.path,
10150
10746
  description: input.description ?? null,
@@ -10164,7 +10760,7 @@ async function updateProject2(id, input, store) {
10164
10760
  async function createPlan2(input, store, context) {
10165
10761
  const timestamp = new Date().toISOString();
10166
10762
  return store.upsert("plans", {
10167
- id: randomUUID2(),
10763
+ id: randomUUID3(),
10168
10764
  project_id: input.project_id ?? context?.projectId ?? null,
10169
10765
  task_list_id: input.task_list_id ?? context?.taskListId ?? null,
10170
10766
  agent_id: input.agent_id ?? context?.agentId ?? null,
@@ -10186,7 +10782,7 @@ async function registerAgent2(input, store, context) {
10186
10782
  }
10187
10783
  const timestamp = new Date().toISOString();
10188
10784
  const agent = {
10189
- id: existing?.id ?? randomUUID2().slice(0, 8),
10785
+ id: existing?.id ?? randomUUID3().slice(0, 8),
10190
10786
  name: input.name,
10191
10787
  description: input.description ?? existing?.description ?? null,
10192
10788
  role: input.role ?? existing?.role ?? null,
@@ -10222,7 +10818,7 @@ async function updateAgent2(id, input, store) {
10222
10818
  async function createTaskList2(input, store, context) {
10223
10819
  const timestamp = new Date().toISOString();
10224
10820
  return store.upsert("task_lists", {
10225
- id: randomUUID2(),
10821
+ id: randomUUID3(),
10226
10822
  project_id: input.project_id ?? context?.projectId ?? null,
10227
10823
  slug: input.slug ?? slugify2(input.name),
10228
10824
  name: input.name,
@@ -10244,7 +10840,7 @@ async function updateTaskList2(id, input, store) {
10244
10840
  async function createTemplate2(input, store, context) {
10245
10841
  const timestamp = new Date().toISOString();
10246
10842
  return store.upsert("templates", {
10247
- id: randomUUID2(),
10843
+ id: randomUUID3(),
10248
10844
  name: input.name,
10249
10845
  title_pattern: input.title_pattern,
10250
10846
  description: input.description ?? null,
@@ -10273,7 +10869,7 @@ async function updateTemplate2(id, input, store) {
10273
10869
  }
10274
10870
  async function logTaskChange2(taskId, action, field, oldValue, newValue, agentId, store, context) {
10275
10871
  const entry2 = {
10276
- id: randomUUID2(),
10872
+ id: randomUUID3(),
10277
10873
  task_id: taskId,
10278
10874
  action,
10279
10875
  field: field ?? null,
@@ -10286,7 +10882,7 @@ async function logTaskChange2(taskId, action, field, oldValue, newValue, agentId
10286
10882
  }
10287
10883
  async function addComment2(input, store, context) {
10288
10884
  const comment = {
10289
- id: randomUUID2(),
10885
+ id: randomUUID3(),
10290
10886
  task_id: input.task_id,
10291
10887
  agent_id: input.agent_id ?? context?.agentId ?? null,
10292
10888
  session_id: input.session_id ?? context?.sessionId ?? null,
@@ -10461,10 +11057,10 @@ function assertRemoteAdapterCapabilities(adapter, mode) {
10461
11057
  }
10462
11058
  }
10463
11059
  // src/storage/s3-artifacts.ts
10464
- import { createHash as createHash3, createHmac } from "crypto";
11060
+ import { createHash as createHash3, createHmac as createHmac2 } from "crypto";
10465
11061
  function createTodosS3ArtifactStore(options) {
10466
11062
  const requestFetch = options.fetch ?? fetch;
10467
- const now2 = options.now ?? (() => new Date);
11063
+ const now3 = options.now ?? (() => new Date);
10468
11064
  return {
10469
11065
  objectKey: (relativePath) => buildS3ObjectKey(options.config, relativePath),
10470
11066
  objectUrl: (relativePath) => buildS3ObjectUrl(options.config, buildS3ObjectKey(options.config, relativePath)),
@@ -10486,7 +11082,7 @@ function createTodosS3ArtifactStore(options) {
10486
11082
  headers,
10487
11083
  body,
10488
11084
  credentials: options.credentials,
10489
- now: now2()
11085
+ now: now3()
10490
11086
  });
10491
11087
  const response = await requestFetch(url, { method: "PUT", headers: signed.headers, body });
10492
11088
  if (!response.ok)
@@ -10507,7 +11103,7 @@ function createTodosS3ArtifactStore(options) {
10507
11103
  service: "s3",
10508
11104
  headers: {},
10509
11105
  credentials: options.credentials,
10510
- now: now2()
11106
+ now: now3()
10511
11107
  });
10512
11108
  const response = await requestFetch(url, { method: "GET", headers: signed.headers });
10513
11109
  if (!response.ok)
@@ -10523,7 +11119,7 @@ function createTodosS3ArtifactStore(options) {
10523
11119
  service: "s3",
10524
11120
  headers: {},
10525
11121
  credentials: options.credentials,
10526
- now: now2()
11122
+ now: now3()
10527
11123
  });
10528
11124
  const response = await requestFetch(url, { method: "DELETE", headers: signed.headers });
10529
11125
  if (!response.ok && response.status !== 404)
@@ -10636,10 +11232,10 @@ function sha256Hex(value) {
10636
11232
  return createHash3("sha256").update(value).digest("hex");
10637
11233
  }
10638
11234
  function hmac(key, value) {
10639
- return createHmac("sha256", key).update(value).digest();
11235
+ return createHmac2("sha256", key).update(value).digest();
10640
11236
  }
10641
11237
  function hmacHex(key, value) {
10642
- return createHmac("sha256", key).update(value).digest("hex");
11238
+ return createHmac2("sha256", key).update(value).digest("hex");
10643
11239
  }
10644
11240
  function getSigningKey(secretAccessKey, dateStamp, region, service) {
10645
11241
  const dateKey = hmac(`AWS4${secretAccessKey}`, dateStamp);
@@ -10651,7 +11247,7 @@ function getSigningKey(secretAccessKey, dateStamp, region, service) {
10651
11247
  init_database();
10652
11248
  async function uploadRunArtifactsToS3(options) {
10653
11249
  const db = options.db ?? getDatabase();
10654
- const now2 = options.now ?? (() => new Date);
11250
+ const now3 = options.now ?? (() => new Date);
10655
11251
  const result = emptyResult();
10656
11252
  for (const artifact of listRunArtifacts(db, options.filter)) {
10657
11253
  try {
@@ -10690,7 +11286,7 @@ async function uploadRunArtifactsToS3(options) {
10690
11286
  url: ref.url,
10691
11287
  sha256: content.sha256,
10692
11288
  size_bytes: content.size_bytes,
10693
- uploaded_at: now2().toISOString()
11289
+ uploaded_at: now3().toISOString()
10694
11290
  };
10695
11291
  updateArtifactMetadata(db, artifact.id, {
10696
11292
  ...metadata,
@@ -10765,7 +11361,7 @@ function planRunArtifactsS3Sync(options) {
10765
11361
  }
10766
11362
  async function downloadRunArtifactsFromS3(options) {
10767
11363
  const db = options.db ?? getDatabase();
10768
- const now2 = options.now ?? (() => new Date);
11364
+ const now3 = options.now ?? (() => new Date);
10769
11365
  const result = emptyResult();
10770
11366
  for (const artifact of listRunArtifacts(db, options.filter)) {
10771
11367
  try {
@@ -10801,7 +11397,7 @@ async function downloadRunArtifactsFromS3(options) {
10801
11397
  ...metadata,
10802
11398
  remote_artifact_store: {
10803
11399
  ...remote,
10804
- downloaded_at: now2().toISOString()
11400
+ downloaded_at: now3().toISOString()
10805
11401
  }
10806
11402
  });
10807
11403
  result.downloaded += 1;