@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/mcp/index.js CHANGED
@@ -8628,6 +8628,7 @@ var init_event_hooks = __esm(() => {
8628
8628
  init_runner_sandbox();
8629
8629
  init_config();
8630
8630
  LOCAL_EVENT_TYPES = [
8631
+ "task.created",
8631
8632
  "task.assigned",
8632
8633
  "task.blocked",
8633
8634
  "task.started",
@@ -8652,6 +8653,572 @@ var init_event_hooks = __esm(() => {
8652
8653
  VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
8653
8654
  });
8654
8655
 
8656
+ // node_modules/.bun/@hasna+events@0.1.7/node_modules/@hasna/events/dist/index.js
8657
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
8658
+ import { existsSync as existsSync5 } from "fs";
8659
+ import { homedir } from "os";
8660
+ import { join as join4 } from "path";
8661
+ import { createHmac, timingSafeEqual } from "crypto";
8662
+ import { randomUUID as randomUUID2 } from "crypto";
8663
+ import { spawn } from "child_process";
8664
+ import { randomUUID as randomUUID22 } from "crypto";
8665
+ function getPathValue(input, path) {
8666
+ return path.split(".").reduce((value, part) => {
8667
+ if (value && typeof value === "object" && part in value) {
8668
+ return value[part];
8669
+ }
8670
+ return;
8671
+ }, input);
8672
+ }
8673
+ function wildcardToRegExp(pattern) {
8674
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
8675
+ return new RegExp(`^${escaped}$`);
8676
+ }
8677
+ function matchString(value, matcher) {
8678
+ if (matcher === undefined)
8679
+ return true;
8680
+ if (value === undefined)
8681
+ return false;
8682
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
8683
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
8684
+ }
8685
+ function matchRecord(input, matcher) {
8686
+ if (!matcher)
8687
+ return true;
8688
+ return Object.entries(matcher).every(([path, expected]) => {
8689
+ const actual = getPathValue(input, path);
8690
+ if (typeof expected === "string" || Array.isArray(expected)) {
8691
+ return matchString(actual === undefined ? undefined : String(actual), expected);
8692
+ }
8693
+ return actual === expected;
8694
+ });
8695
+ }
8696
+ function eventMatchesFilter(event, filter) {
8697
+ 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);
8698
+ }
8699
+ function channelMatchesEvent(channel, event) {
8700
+ if (!channel.enabled)
8701
+ return false;
8702
+ if (!channel.filters || channel.filters.length === 0)
8703
+ return true;
8704
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
8705
+ }
8706
+ function getEventsDataDir(override) {
8707
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir(), ".hasna", "events");
8708
+ }
8709
+
8710
+ class JsonEventsStore {
8711
+ dataDir;
8712
+ channelsPath;
8713
+ eventsPath;
8714
+ deliveriesPath;
8715
+ constructor(dataDir = getEventsDataDir()) {
8716
+ this.dataDir = dataDir;
8717
+ this.channelsPath = join4(dataDir, "channels.json");
8718
+ this.eventsPath = join4(dataDir, "events.json");
8719
+ this.deliveriesPath = join4(dataDir, "deliveries.json");
8720
+ }
8721
+ async init() {
8722
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
8723
+ await chmod(this.dataDir, 448).catch(() => {
8724
+ return;
8725
+ });
8726
+ await this.ensureArrayFile(this.channelsPath);
8727
+ await this.ensureArrayFile(this.eventsPath);
8728
+ await this.ensureArrayFile(this.deliveriesPath);
8729
+ }
8730
+ async addChannel(channel) {
8731
+ await this.init();
8732
+ const channels = await this.readJson(this.channelsPath, []);
8733
+ const index = channels.findIndex((item) => item.id === channel.id);
8734
+ if (index >= 0) {
8735
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
8736
+ } else {
8737
+ channels.push(channel);
8738
+ }
8739
+ await this.writeJson(this.channelsPath, channels);
8740
+ return index >= 0 ? channels[index] : channel;
8741
+ }
8742
+ async listChannels() {
8743
+ await this.init();
8744
+ return this.readJson(this.channelsPath, []);
8745
+ }
8746
+ async getChannel(id) {
8747
+ const channels = await this.listChannels();
8748
+ return channels.find((channel) => channel.id === id);
8749
+ }
8750
+ async removeChannel(id) {
8751
+ await this.init();
8752
+ const channels = await this.readJson(this.channelsPath, []);
8753
+ const next = channels.filter((channel) => channel.id !== id);
8754
+ await this.writeJson(this.channelsPath, next);
8755
+ return next.length !== channels.length;
8756
+ }
8757
+ async appendEvent(event) {
8758
+ await this.init();
8759
+ const events = await this.readJson(this.eventsPath, []);
8760
+ events.push(event);
8761
+ await this.writeJson(this.eventsPath, events);
8762
+ return event;
8763
+ }
8764
+ async listEvents() {
8765
+ await this.init();
8766
+ return this.readJson(this.eventsPath, []);
8767
+ }
8768
+ async findEventByIdentity(identity) {
8769
+ const events = await this.listEvents();
8770
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
8771
+ }
8772
+ async appendDelivery(result) {
8773
+ await this.init();
8774
+ const deliveries = await this.readJson(this.deliveriesPath, []);
8775
+ deliveries.push(result);
8776
+ await this.writeJson(this.deliveriesPath, deliveries);
8777
+ return result;
8778
+ }
8779
+ async listDeliveries() {
8780
+ await this.init();
8781
+ return this.readJson(this.deliveriesPath, []);
8782
+ }
8783
+ async exportData() {
8784
+ return {
8785
+ channels: await this.listChannels(),
8786
+ events: await this.listEvents(),
8787
+ deliveries: await this.listDeliveries()
8788
+ };
8789
+ }
8790
+ async ensureArrayFile(path) {
8791
+ if (!existsSync5(path)) {
8792
+ await writeFile(path, `[]
8793
+ `, { encoding: "utf-8", mode: 384 });
8794
+ }
8795
+ await chmod(path, 384).catch(() => {
8796
+ return;
8797
+ });
8798
+ }
8799
+ async readJson(path, fallback) {
8800
+ try {
8801
+ const raw = await readFile(path, "utf-8");
8802
+ if (!raw.trim())
8803
+ return fallback;
8804
+ return JSON.parse(raw);
8805
+ } catch (error) {
8806
+ if (error.code === "ENOENT")
8807
+ return fallback;
8808
+ throw error;
8809
+ }
8810
+ }
8811
+ async writeJson(path, value) {
8812
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
8813
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
8814
+ `, { encoding: "utf-8", mode: 384 });
8815
+ await rename(tempPath, path);
8816
+ await chmod(path, 384).catch(() => {
8817
+ return;
8818
+ });
8819
+ }
8820
+ }
8821
+ function buildSignatureBase(timestamp, body) {
8822
+ return `${timestamp}.${body}`;
8823
+ }
8824
+ function signPayload(secret, timestamp, body) {
8825
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
8826
+ return `sha256=${digest}`;
8827
+ }
8828
+ function now2() {
8829
+ return new Date().toISOString();
8830
+ }
8831
+ function truncate(value, max = 4096) {
8832
+ return value.length > max ? `${value.slice(0, max)}...` : value;
8833
+ }
8834
+ function buildWebhookRequest(event, channel) {
8835
+ if (!channel.webhook)
8836
+ throw new Error(`Channel ${channel.id} has no webhook config`);
8837
+ const body = JSON.stringify(event);
8838
+ const timestamp = event.time;
8839
+ const headers = {
8840
+ "Content-Type": "application/json",
8841
+ "User-Agent": "@hasna/events",
8842
+ "X-Hasna-Event-Id": event.id,
8843
+ "X-Hasna-Event-Type": event.type,
8844
+ "X-Hasna-Timestamp": timestamp,
8845
+ ...channel.webhook.headers
8846
+ };
8847
+ if (channel.webhook.secret) {
8848
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
8849
+ }
8850
+ return { body, headers };
8851
+ }
8852
+ async function dispatchWebhook(event, channel, options = {}) {
8853
+ if (!channel.webhook)
8854
+ throw new Error(`Channel ${channel.id} has no webhook config`);
8855
+ const startedAt = now2();
8856
+ const { body, headers } = buildWebhookRequest(event, channel);
8857
+ const controller = new AbortController;
8858
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
8859
+ try {
8860
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
8861
+ method: "POST",
8862
+ headers,
8863
+ body,
8864
+ signal: controller.signal
8865
+ });
8866
+ const responseBody = truncate(await response.text());
8867
+ return {
8868
+ attempt: 1,
8869
+ status: response.ok ? "success" : "failed",
8870
+ startedAt,
8871
+ completedAt: now2(),
8872
+ responseStatus: response.status,
8873
+ responseBody,
8874
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
8875
+ };
8876
+ } catch (error) {
8877
+ return {
8878
+ attempt: 1,
8879
+ status: "failed",
8880
+ startedAt,
8881
+ completedAt: now2(),
8882
+ error: error instanceof Error ? error.message : String(error)
8883
+ };
8884
+ } finally {
8885
+ clearTimeout(timeout);
8886
+ }
8887
+ }
8888
+ async function dispatchCommand(event, channel) {
8889
+ if (!channel.command)
8890
+ throw new Error(`Channel ${channel.id} has no command config`);
8891
+ const startedAt = now2();
8892
+ const eventJson = JSON.stringify(event);
8893
+ const env = {
8894
+ ...process.env,
8895
+ ...channel.command.env,
8896
+ HASNA_CHANNEL_ID: channel.id,
8897
+ HASNA_EVENT_ID: event.id,
8898
+ HASNA_EVENT_TYPE: event.type,
8899
+ HASNA_EVENT_SOURCE: event.source,
8900
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
8901
+ HASNA_EVENT_SEVERITY: event.severity,
8902
+ HASNA_EVENT_TIME: event.time,
8903
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
8904
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
8905
+ HASNA_EVENT_JSON: eventJson
8906
+ };
8907
+ return new Promise((resolve6) => {
8908
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
8909
+ cwd: channel.command.cwd,
8910
+ env,
8911
+ stdio: ["pipe", "pipe", "pipe"]
8912
+ });
8913
+ let stdout = "";
8914
+ let stderr = "";
8915
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
8916
+ child.stdin.end(eventJson);
8917
+ child.stdout.on("data", (chunk) => {
8918
+ stdout += chunk.toString();
8919
+ });
8920
+ child.stderr.on("data", (chunk) => {
8921
+ stderr += chunk.toString();
8922
+ });
8923
+ child.on("error", (error) => {
8924
+ clearTimeout(timeout);
8925
+ resolve6({
8926
+ attempt: 1,
8927
+ status: "failed",
8928
+ startedAt,
8929
+ completedAt: now2(),
8930
+ stdout: truncate(stdout),
8931
+ stderr: truncate(stderr),
8932
+ error: error.message
8933
+ });
8934
+ });
8935
+ child.on("close", (code, signal) => {
8936
+ clearTimeout(timeout);
8937
+ const success = code === 0;
8938
+ resolve6({
8939
+ attempt: 1,
8940
+ status: success ? "success" : "failed",
8941
+ startedAt,
8942
+ completedAt: now2(),
8943
+ stdout: truncate(stdout),
8944
+ stderr: truncate(stderr),
8945
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
8946
+ });
8947
+ });
8948
+ });
8949
+ }
8950
+ async function dispatchChannel(event, channel, options = {}) {
8951
+ if (channel.transport === "webhook")
8952
+ return dispatchWebhook(event, channel, options);
8953
+ if (channel.transport === "command")
8954
+ return dispatchCommand(event, channel);
8955
+ return {
8956
+ attempt: 1,
8957
+ status: "skipped",
8958
+ startedAt: now2(),
8959
+ completedAt: now2(),
8960
+ error: `Unsupported transport: ${channel.transport}`
8961
+ };
8962
+ }
8963
+ function createDeliveryResult(event, channel, attempts) {
8964
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
8965
+ return {
8966
+ id: randomUUID2(),
8967
+ eventId: event.id,
8968
+ channelId: channel.id,
8969
+ transport: channel.transport,
8970
+ status,
8971
+ attempts,
8972
+ createdAt: attempts[0]?.startedAt ?? now2(),
8973
+ completedAt: attempts.at(-1)?.completedAt ?? now2()
8974
+ };
8975
+ }
8976
+ function createEvent(input) {
8977
+ return {
8978
+ id: input.id ?? randomUUID22(),
8979
+ source: input.source,
8980
+ type: input.type,
8981
+ time: normalizeTime(input.time),
8982
+ subject: input.subject,
8983
+ severity: input.severity ?? "info",
8984
+ data: input.data ?? {},
8985
+ message: input.message,
8986
+ dedupeKey: input.dedupeKey,
8987
+ schemaVersion: input.schemaVersion ?? "1.0",
8988
+ metadata: input.metadata ?? {}
8989
+ };
8990
+ }
8991
+
8992
+ class EventsClient {
8993
+ store;
8994
+ redactors;
8995
+ transportOptions;
8996
+ constructor(options = {}) {
8997
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
8998
+ this.redactors = options.redactors ?? [];
8999
+ this.transportOptions = { fetchImpl: options.fetchImpl };
9000
+ }
9001
+ async addChannel(input) {
9002
+ const timestamp = new Date().toISOString();
9003
+ return this.store.addChannel({
9004
+ ...input,
9005
+ createdAt: input.createdAt ?? timestamp,
9006
+ updatedAt: input.updatedAt ?? timestamp
9007
+ });
9008
+ }
9009
+ async listChannels() {
9010
+ return this.store.listChannels();
9011
+ }
9012
+ async removeChannel(id) {
9013
+ return this.store.removeChannel(id);
9014
+ }
9015
+ async emit(input, options = {}) {
9016
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
9017
+ if (options.dedupe !== false) {
9018
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
9019
+ if (existing) {
9020
+ return { event: existing, deliveries: [], deduped: true };
9021
+ }
9022
+ }
9023
+ await this.store.appendEvent(event);
9024
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
9025
+ return { event, deliveries, deduped: false };
9026
+ }
9027
+ async listEvents() {
9028
+ return this.store.listEvents();
9029
+ }
9030
+ async listDeliveries() {
9031
+ return this.store.listDeliveries();
9032
+ }
9033
+ async deliver(event) {
9034
+ const channels = await this.store.listChannels();
9035
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
9036
+ const deliveries = [];
9037
+ for (const channel of selected) {
9038
+ const eventForChannel = await this.applyRedaction(event, channel);
9039
+ const result = await this.deliverWithRetry(eventForChannel, channel);
9040
+ await this.store.appendDelivery(result);
9041
+ deliveries.push(result);
9042
+ }
9043
+ return deliveries;
9044
+ }
9045
+ async testChannel(id, input = {}) {
9046
+ const channel = await this.store.getChannel(id);
9047
+ if (!channel)
9048
+ throw new Error(`Channel not found: ${id}`);
9049
+ const event = createEvent({
9050
+ source: input.source ?? "hasna.events",
9051
+ type: input.type ?? "events.test",
9052
+ subject: input.subject ?? id,
9053
+ severity: input.severity ?? "info",
9054
+ data: input.data ?? { test: true },
9055
+ message: input.message ?? "Hasna events test delivery",
9056
+ dedupeKey: input.dedupeKey,
9057
+ schemaVersion: input.schemaVersion,
9058
+ metadata: input.metadata,
9059
+ time: input.time,
9060
+ id: input.id
9061
+ });
9062
+ const eventForChannel = await this.applyRedaction(event, channel);
9063
+ const result = await this.deliverWithRetry(eventForChannel, channel);
9064
+ await this.store.appendDelivery(result);
9065
+ return result;
9066
+ }
9067
+ async replay(options = {}) {
9068
+ const events = (await this.store.listEvents()).filter((event) => {
9069
+ if (options.eventId && event.id !== options.eventId)
9070
+ return false;
9071
+ if (options.source && event.source !== options.source)
9072
+ return false;
9073
+ if (options.type && event.type !== options.type)
9074
+ return false;
9075
+ return true;
9076
+ });
9077
+ if (options.dryRun)
9078
+ return { events, deliveries: [] };
9079
+ const deliveries = [];
9080
+ for (const event of events) {
9081
+ deliveries.push(...await this.deliver(event));
9082
+ }
9083
+ return { events, deliveries };
9084
+ }
9085
+ async applyRedaction(event, channel) {
9086
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
9087
+ for (const redactor of this.redactors) {
9088
+ next = await redactor(next, channel);
9089
+ }
9090
+ return next;
9091
+ }
9092
+ async deliverWithRetry(event, channel) {
9093
+ const policy = normalizeRetryPolicy(channel.retry);
9094
+ const attempts = [];
9095
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
9096
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
9097
+ attempt.attempt = index + 1;
9098
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
9099
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
9100
+ }
9101
+ attempts.push(attempt);
9102
+ if (attempt.status !== "failed")
9103
+ break;
9104
+ if (attempt.nextBackoffMs)
9105
+ await Bun.sleep(attempt.nextBackoffMs);
9106
+ }
9107
+ return createDeliveryResult(event, channel, attempts);
9108
+ }
9109
+ }
9110
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
9111
+ if (paths.length === 0)
9112
+ return event;
9113
+ const copy = structuredClone(event);
9114
+ for (const path of paths) {
9115
+ setPath(copy, path, replacement);
9116
+ }
9117
+ return copy;
9118
+ }
9119
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
9120
+ return redactValue2(event, replacement);
9121
+ }
9122
+ function shouldRedactKey(key) {
9123
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
9124
+ }
9125
+ function redactValue2(value, replacement) {
9126
+ if (Array.isArray(value))
9127
+ return value.map((item) => redactValue2(item, replacement));
9128
+ if (!value || typeof value !== "object")
9129
+ return value;
9130
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
9131
+ key,
9132
+ shouldRedactKey(key) ? replacement : redactValue2(item, replacement)
9133
+ ]));
9134
+ }
9135
+ function setPath(input, path, replacement) {
9136
+ const parts = path.split(".");
9137
+ let cursor = input;
9138
+ for (const part of parts.slice(0, -1)) {
9139
+ const next = cursor[part];
9140
+ if (!next || typeof next !== "object")
9141
+ return;
9142
+ cursor = next;
9143
+ }
9144
+ const last = parts.at(-1);
9145
+ if (last && last in cursor)
9146
+ cursor[last] = replacement;
9147
+ }
9148
+ function normalizeTime(value) {
9149
+ if (!value)
9150
+ return new Date().toISOString();
9151
+ return value instanceof Date ? value.toISOString() : value;
9152
+ }
9153
+ function normalizeRetryPolicy(policy) {
9154
+ return {
9155
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
9156
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
9157
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
9158
+ };
9159
+ }
9160
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR", HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME", DEFAULT_SIGNATURE_TOLERANCE_MS;
9161
+ var init_dist = __esm(() => {
9162
+ DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
9163
+ });
9164
+
9165
+ // src/lib/shared-events.ts
9166
+ function taskEventData(task, extra = {}) {
9167
+ return {
9168
+ id: task.id,
9169
+ task_id: task.id,
9170
+ short_id: task.short_id,
9171
+ title: task.title,
9172
+ description: task.description,
9173
+ status: task.status,
9174
+ priority: task.priority,
9175
+ project_id: task.project_id,
9176
+ parent_id: task.parent_id,
9177
+ plan_id: task.plan_id,
9178
+ task_list_id: task.task_list_id,
9179
+ agent_id: task.agent_id,
9180
+ assigned_to: task.assigned_to,
9181
+ session_id: task.session_id,
9182
+ working_dir: task.working_dir,
9183
+ tags: task.tags,
9184
+ metadata: task.metadata,
9185
+ version: task.version,
9186
+ created_at: task.created_at,
9187
+ updated_at: task.updated_at,
9188
+ started_at: task.started_at,
9189
+ completed_at: task.completed_at,
9190
+ due_at: task.due_at,
9191
+ ...extra
9192
+ };
9193
+ }
9194
+ async function emitSharedTaskEvent(input) {
9195
+ const data = taskEventData(input.task, input.data);
9196
+ await new EventsClient().emit({
9197
+ source: SOURCE,
9198
+ type: input.type,
9199
+ subject: input.task.id,
9200
+ severity: input.severity ?? "info",
9201
+ message: input.message ?? `${input.type}: ${input.task.title}`,
9202
+ data,
9203
+ dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
9204
+ metadata: {
9205
+ package: "@hasna/todos",
9206
+ task_id: input.task.id,
9207
+ project_id: input.task.project_id,
9208
+ task_list_id: input.task.task_list_id
9209
+ }
9210
+ }, { deliver: true, dedupe: true });
9211
+ }
9212
+ function emitSharedTaskEventQuiet(input) {
9213
+ emitSharedTaskEvent(input).catch(() => {
9214
+ return;
9215
+ });
9216
+ }
9217
+ var SOURCE = "todos";
9218
+ var init_shared_events = __esm(() => {
9219
+ init_dist();
9220
+ });
9221
+
8655
9222
  // src/lib/secret-redaction.ts
8656
9223
  function isAllowlisted(text, match, allowlist) {
8657
9224
  const context = text.slice(Math.max(0, text.indexOf(match) - 20), text.indexOf(match) + match.length + 20);
@@ -9119,7 +9686,7 @@ async function deliverWebhook(wh, event, body, attempt, db) {
9119
9686
  activeDeliveries--;
9120
9687
  }
9121
9688
  }
9122
- async function dispatchWebhook(event, payload, db) {
9689
+ async function dispatchWebhook2(event, payload, db) {
9123
9690
  const d = db || getDatabase();
9124
9691
  const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
9125
9692
  const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
@@ -9236,7 +9803,10 @@ function createTask(input, db) {
9236
9803
  insertTaskTags(id, tags, d);
9237
9804
  }
9238
9805
  const task = getTask(id, d);
9239
- 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(() => {});
9806
+ const payload = taskEventData(task);
9807
+ dispatchWebhook2("task.created", payload, d).catch(() => {});
9808
+ emitLocalEventHooksQuiet({ type: "task.created", payload });
9809
+ emitSharedTaskEventQuiet({ type: "task.created", task });
9240
9810
  return task;
9241
9811
  }
9242
9812
  function getTask(id, db) {
@@ -9580,18 +10150,7 @@ function updateTask(id, input, db) {
9580
10150
  logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
9581
10151
  if (input.approved_by !== undefined)
9582
10152
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
9583
- if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
9584
- dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
9585
- emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id, assigned_to: input.assigned_to, title: task.title } });
9586
- }
9587
- if (input.status !== undefined && input.status !== task.status) {
9588
- dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
9589
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload: { id, old_status: task.status, new_status: input.status, title: task.title } });
9590
- }
9591
- if (input.approved_by !== undefined) {
9592
- emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
9593
- }
9594
- return {
10153
+ const updatedTask = {
9595
10154
  ...task,
9596
10155
  ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
9597
10156
  tags: input.tags ?? task.tags,
@@ -9609,6 +10168,22 @@ function updateTask(id, input, db) {
9609
10168
  approved_by: input.approved_by ?? task.approved_by,
9610
10169
  approved_at: input.approved_by ? timestamp : task.approved_at
9611
10170
  };
10171
+ if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
10172
+ const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
10173
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
10174
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
10175
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
10176
+ }
10177
+ if (input.status !== undefined && input.status !== task.status) {
10178
+ const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
10179
+ dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
10180
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
10181
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
10182
+ }
10183
+ if (input.approved_by !== undefined) {
10184
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
10185
+ }
10186
+ return updatedTask;
9612
10187
  }
9613
10188
  function deleteTask(id, db) {
9614
10189
  const d = db || getDatabase();
@@ -9620,6 +10195,7 @@ var init_task_crud = __esm(() => {
9620
10195
  init_database();
9621
10196
  init_completion_guard();
9622
10197
  init_event_hooks();
10198
+ init_shared_events();
9623
10199
  init_audit();
9624
10200
  init_webhooks();
9625
10201
  init_checklists();
@@ -10375,9 +10951,12 @@ function startTask(id, agentId, db) {
10375
10951
  throw new Error(`Task ${id} could not be started because it changed during claim`);
10376
10952
  }
10377
10953
  logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
10378
- dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
10379
- emitLocalEventHooksQuiet({ type: "task.started", payload: { id, agent_id: agentId, title: task.title } });
10380
- 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 };
10954
+ 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 };
10955
+ const payload = taskEventData(startedTask, { agent_id: agentId });
10956
+ dispatchWebhook2("task.started", payload, d).catch(() => {});
10957
+ emitLocalEventHooksQuiet({ type: "task.started", payload });
10958
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
10959
+ return startedTask;
10381
10960
  }
10382
10961
  function completeTask(id, agentId, db, options) {
10383
10962
  const d = db || getDatabase();
@@ -10413,8 +10992,21 @@ function completeTask(id, agentId, db, options) {
10413
10992
  });
10414
10993
  tx();
10415
10994
  logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
10416
- dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
10417
- emitLocalEventHooksQuiet({ type: "task.completed", payload: { id, agent_id: agentId, title: task.title, completed_at: timestamp } });
10995
+ const completedTaskForEvent = {
10996
+ ...task,
10997
+ status: "completed",
10998
+ locked_by: null,
10999
+ locked_at: null,
11000
+ completed_at: timestamp,
11001
+ confidence,
11002
+ version: task.version + 1,
11003
+ updated_at: timestamp,
11004
+ metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
11005
+ };
11006
+ const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
11007
+ dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
11008
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
11009
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
10418
11010
  let spawnedTask = null;
10419
11011
  if (task.recurrence_rule && !options?.skip_recurrence) {
10420
11012
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -10455,8 +11047,12 @@ function completeTask(id, agentId, db, options) {
10455
11047
  if (unblockedDeps.length > 0) {
10456
11048
  meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
10457
11049
  for (const dep of unblockedDeps) {
10458
- dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
10459
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload: { id: dep.id, unblocked_by: id, title: dep.title } });
11050
+ const depTask = getTask(dep.id, d);
11051
+ const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
11052
+ dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
11053
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
11054
+ if (depTask)
11055
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
10460
11056
  }
10461
11057
  }
10462
11058
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -10642,9 +11238,6 @@ function failTask(id, agentId, reason, options, db) {
10642
11238
  const timestamp = now();
10643
11239
  d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
10644
11240
  WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
10645
- logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
10646
- dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
10647
- emitLocalEventHooksQuiet({ type: "task.failed", payload: { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title } });
10648
11241
  const failedTask = {
10649
11242
  ...task,
10650
11243
  status: "failed",
@@ -10654,6 +11247,11 @@ function failTask(id, agentId, reason, options, db) {
10654
11247
  version: task.version + 1,
10655
11248
  updated_at: timestamp
10656
11249
  };
11250
+ logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
11251
+ const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
11252
+ dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
11253
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
11254
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
10657
11255
  let retryTask;
10658
11256
  if (options?.retry) {
10659
11257
  const retryCount = (task.retry_count || 0) + 1;
@@ -10728,9 +11326,12 @@ function stealTask(agentId, opts, db) {
10728
11326
  return null;
10729
11327
  logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
10730
11328
  logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
10731
- dispatchWebhook("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
10732
- emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to } });
10733
- return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
11329
+ const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
11330
+ const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
11331
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
11332
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
11333
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
11334
+ return stolenTask;
10734
11335
  }
10735
11336
  function claimOrSteal(agentId, filters, db) {
10736
11337
  const d = db || getDatabase();
@@ -10778,6 +11379,7 @@ var init_task_lifecycle = __esm(() => {
10778
11379
  init_database();
10779
11380
  init_completion_guard();
10780
11381
  init_event_hooks();
11382
+ init_shared_events();
10781
11383
  init_audit();
10782
11384
  init_recurrence();
10783
11385
  init_webhooks();
@@ -11465,7 +12067,7 @@ function getTaskWatchers(taskId, db) {
11465
12067
  }
11466
12068
  function notifyWatchers(taskId, event, data, db) {
11467
12069
  const watchers = getTaskWatchers(taskId, db);
11468
- dispatchWebhook(`task.watcher.${event}`, { task_id: taskId, watchers: watchers.map((w) => w.agent_id), ...data }, db).catch(() => {});
12070
+ dispatchWebhook2(`task.watcher.${event}`, { task_id: taskId, watchers: watchers.map((w) => w.agent_id), ...data }, db).catch(() => {});
11469
12071
  }
11470
12072
  function logCost(taskId, tokens, usd, db) {
11471
12073
  const d = db || getDatabase();
@@ -11959,8 +12561,8 @@ var init_boards = __esm(() => {
11959
12561
 
11960
12562
  // src/lib/artifact-store.ts
11961
12563
  import { createHash as createHash2 } from "crypto";
11962
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
11963
- import { basename, dirname as dirname4, join as join4, resolve as resolve6 } from "path";
12564
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync2, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
12565
+ import { basename, dirname as dirname4, join as join5, resolve as resolve6 } from "path";
11964
12566
  import { tmpdir } from "os";
11965
12567
  function isInMemoryDb2(path) {
11966
12568
  return path === ":memory:" || path.startsWith("file::memory:");
@@ -11972,15 +12574,15 @@ function artifactStoreRoot() {
11972
12574
  return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
11973
12575
  const dbPath = getDatabasePath();
11974
12576
  if (isInMemoryDb2(dbPath))
11975
- return join4(tmpdir(), "hasna-todos-artifacts");
11976
- return join4(dirname4(resolve6(dbPath)), "artifacts");
12577
+ return join5(tmpdir(), "hasna-todos-artifacts");
12578
+ return join5(dirname4(resolve6(dbPath)), "artifacts");
11977
12579
  }
11978
12580
  function artifactStorePath(relativePath) {
11979
12581
  const normalized = relativePath.replace(/\\/g, "/");
11980
12582
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
11981
12583
  throw new Error("Invalid artifact store path");
11982
12584
  }
11983
- return join4(artifactStoreRoot(), normalized);
12585
+ return join5(artifactStoreRoot(), normalized);
11984
12586
  }
11985
12587
  function sha256(buffer) {
11986
12588
  return createHash2("sha256").update(buffer).digest("hex");
@@ -12021,7 +12623,7 @@ function mediaTypeFor(path, textLike) {
12021
12623
  }
12022
12624
  function storeArtifactContent(input) {
12023
12625
  const sourcePath = resolve6(input.path);
12024
- if (!existsSync5(sourcePath))
12626
+ if (!existsSync6(sourcePath))
12025
12627
  return null;
12026
12628
  const sourceStat = statSync2(sourcePath);
12027
12629
  if (!sourceStat.isFile())
@@ -12038,9 +12640,9 @@ function storeArtifactContent(input) {
12038
12640
  redactionStatus = "redacted";
12039
12641
  }
12040
12642
  const storedSha = sha256(storedBuffer);
12041
- const relativePath = join4("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
12643
+ const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
12042
12644
  const destination = artifactStorePath(relativePath);
12043
- if (!existsSync5(destination)) {
12645
+ if (!existsSync6(destination)) {
12044
12646
  mkdirSync4(dirname4(destination), { recursive: true });
12045
12647
  writeFileSync2(destination, storedBuffer);
12046
12648
  }
@@ -12100,7 +12702,7 @@ function verifyStoredArtifact(input) {
12100
12702
  };
12101
12703
  }
12102
12704
  const storedPath = artifactStorePath(store.relative_path);
12103
- if (!existsSync5(storedPath)) {
12705
+ if (!existsSync6(storedPath)) {
12104
12706
  return {
12105
12707
  id: input.id,
12106
12708
  path: input.path,
@@ -13385,17 +13987,73 @@ var init_dispatch_formatter = __esm(() => {
13385
13987
  });
13386
13988
 
13387
13989
  // src/lib/tmux.ts
13990
+ async function inspectTmuxPane(target) {
13991
+ const proc = Bun.spawn([
13992
+ "tmux",
13993
+ "display-message",
13994
+ "-p",
13995
+ "-t",
13996
+ target,
13997
+ "#{pane_id}\t#{pane_current_command}\t#{pane_dead}\t#{pane_input_off}\t#{pane_in_mode}"
13998
+ ], {
13999
+ stdout: "pipe",
14000
+ stderr: "pipe"
14001
+ });
14002
+ const exitCode = await proc.exited;
14003
+ if (exitCode !== 0) {
14004
+ const stderr = await new Response(proc.stderr).text();
14005
+ throw new Error(`tmux target "${target}" not found: ${stderr.trim() || "unknown error"}`);
14006
+ }
14007
+ const stdout = (await new Response(proc.stdout).text()).trim();
14008
+ const [paneId, currentCommand = "", paneDead = "0", inputOff = "0", inMode = "0"] = stdout.split("\t");
14009
+ if (!paneId) {
14010
+ throw new Error(`tmux target "${target}" did not resolve to a pane`);
14011
+ }
14012
+ return {
14013
+ target,
14014
+ paneId,
14015
+ currentCommand,
14016
+ paneDead: paneDead === "1",
14017
+ inputOff: inputOff === "1",
14018
+ inMode: inMode === "1"
14019
+ };
14020
+ }
14021
+ function tmuxPaneBusyStatus(pane) {
14022
+ if (pane.paneDead) {
14023
+ return { busy: true, reason: "pane is dead" };
14024
+ }
14025
+ if (pane.inputOff) {
14026
+ return { busy: true, reason: "pane input is disabled" };
14027
+ }
14028
+ if (pane.inMode) {
14029
+ return { busy: true, reason: "pane is in copy or alternate mode" };
14030
+ }
14031
+ const currentCommand = pane.currentCommand.trim();
14032
+ if (currentCommand && !IDLE_TMUX_COMMANDS.has(currentCommand)) {
14033
+ return { busy: true, reason: `pane is running ${currentCommand}` };
14034
+ }
14035
+ return { busy: false, reason: null };
14036
+ }
13388
14037
  function calculateDelay(message) {
13389
14038
  const len = message.length;
13390
14039
  const extra = Math.floor(len / 100 * 40);
13391
14040
  return Math.min(DELAY_MIN + extra, DELAY_MAX);
13392
14041
  }
13393
- async function sendToTmux(target, message, delayMs, dryRun = false) {
14042
+ async function sendToTmux(target, message, delayMs, options = false) {
14043
+ const opts = typeof options === "boolean" ? { dryRun: options } : options;
14044
+ const dryRun = opts.dryRun ?? false;
13394
14045
  if (dryRun) {
13395
14046
  console.log(`[dry-run] sendToTmux target=${target} delay=${delayMs}ms`);
13396
14047
  console.log(`[dry-run] message: ${message.slice(0, 200)}`);
13397
14048
  return;
13398
14049
  }
14050
+ if (!opts.confirmBusy) {
14051
+ const pane = await inspectTmuxPane(target);
14052
+ const status = tmuxPaneBusyStatus(pane);
14053
+ if (status.busy) {
14054
+ throw new Error(`tmux target "${target}" appears busy (${status.reason}). Re-run with --confirm-busy to send anyway.`);
14055
+ }
14056
+ }
13399
14057
  const sendProc = Bun.spawn(["tmux", "send-keys", "-t", target, message, ""], {
13400
14058
  stdout: "pipe",
13401
14059
  stderr: "pipe"
@@ -13416,7 +14074,21 @@ async function sendToTmux(target, message, delayMs, dryRun = false) {
13416
14074
  throw new Error(`tmux send-keys Enter failed for target "${target}": ${stderr.trim()}`);
13417
14075
  }
13418
14076
  }
13419
- var DELAY_MIN = 3000, DELAY_MAX = 5000;
14077
+ var DELAY_MIN = 3000, DELAY_MAX = 5000, IDLE_TMUX_COMMANDS;
14078
+ var init_tmux = __esm(() => {
14079
+ IDLE_TMUX_COMMANDS = new Set([
14080
+ "bash",
14081
+ "dash",
14082
+ "elvish",
14083
+ "fish",
14084
+ "ksh",
14085
+ "nu",
14086
+ "pwsh",
14087
+ "sh",
14088
+ "tmux",
14089
+ "zsh"
14090
+ ]);
14091
+ });
13420
14092
 
13421
14093
  // src/lib/dispatch.ts
13422
14094
  var exports_dispatch = {};
@@ -13434,7 +14106,10 @@ async function executeDispatch(dispatch, opts = {}, db) {
13434
14106
  }
13435
14107
  const delayMs = dispatch.delay_ms ?? calculateDelay(message);
13436
14108
  try {
13437
- await sendToTmux(dispatch.target_window, message, delayMs, opts.dryRun ?? false);
14109
+ await sendToTmux(dispatch.target_window, message, delayMs, {
14110
+ dryRun: opts.dryRun ?? false,
14111
+ confirmBusy: opts.confirmBusy ?? false
14112
+ });
13438
14113
  createDispatchLog({
13439
14114
  dispatch_id: dispatch.id,
13440
14115
  target_window: dispatch.target_window,
@@ -13493,6 +14168,7 @@ var init_dispatch = __esm(() => {
13493
14168
  init_tasks();
13494
14169
  init_database();
13495
14170
  init_dispatch_formatter();
14171
+ init_tmux();
13496
14172
  });
13497
14173
 
13498
14174
  // src/db/task-lists.ts
@@ -13587,8 +14263,9 @@ function registerDispatchTools(server, { shouldRegisterTool, resolveId, formatEr
13587
14263
  target: exports_external.string().describe("tmux target \u2014 window name, session:window, or session:window.pane"),
13588
14264
  delay_ms: exports_external.number().optional().describe("Delay in ms between sending the message and hitting Enter. Auto-calculated from message length (3-5s) if omitted."),
13589
14265
  scheduled_at: exports_external.string().optional().describe("ISO datetime to schedule the dispatch for. Fires immediately if omitted."),
14266
+ confirm_busy: exports_external.boolean().optional().describe("Send even if the target tmux pane appears busy. Default: false."),
13590
14267
  dry_run: exports_external.boolean().optional().describe("Preview the formatted message without sending. Default: false.")
13591
- }, async ({ task_ids, target, delay_ms, scheduled_at, dry_run }) => {
14268
+ }, async ({ task_ids, target, delay_ms, scheduled_at, confirm_busy, dry_run }) => {
13592
14269
  try {
13593
14270
  const db = getDatabase();
13594
14271
  const resolvedIds = task_ids.map((id) => resolveId(id));
@@ -13604,7 +14281,7 @@ ${message}` }]
13604
14281
  }
13605
14282
  const dispatch = createDispatch({ task_ids: resolvedIds, target_window: target, message, delay_ms: effectiveDelay, scheduled_at }, db);
13606
14283
  if (!scheduled_at)
13607
- await executeDispatch(dispatch, {}, db);
14284
+ await executeDispatch(dispatch, { confirmBusy: confirm_busy ?? false }, db);
13608
14285
  return {
13609
14286
  content: [{
13610
14287
  type: "text",
@@ -13630,8 +14307,9 @@ ${message}`
13630
14307
  filter_status: exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"])).optional().describe("Only include tasks with these statuses. Default: pending."),
13631
14308
  delay_ms: exports_external.number().optional().describe("Delay in ms between sending and Enter. Auto-calculated if omitted."),
13632
14309
  scheduled_at: exports_external.string().optional().describe("ISO datetime to schedule. Fires immediately if omitted."),
14310
+ confirm_busy: exports_external.boolean().optional().describe("Send even if the target tmux pane appears busy. Default: false."),
13633
14311
  dry_run: exports_external.boolean().optional().describe("Preview without sending. Default: false.")
13634
- }, async ({ task_list_id, target, filter_status, delay_ms, scheduled_at, dry_run }) => {
14312
+ }, async ({ task_list_id, target, filter_status, delay_ms, scheduled_at, confirm_busy, dry_run }) => {
13635
14313
  try {
13636
14314
  const db = getDatabase();
13637
14315
  const resolvedListId = resolveId(task_list_id, "task_lists");
@@ -13651,7 +14329,7 @@ ${message}` }]
13651
14329
  }
13652
14330
  const dispatch = createDispatch({ title: `Task list: ${taskList.name}`, task_list_id: resolvedListId, target_window: target, message, delay_ms: effectiveDelay, scheduled_at }, db);
13653
14331
  if (!scheduled_at)
13654
- await executeDispatch(dispatch, {}, db);
14332
+ await executeDispatch(dispatch, { confirmBusy: confirm_busy ?? false }, db);
13655
14333
  return {
13656
14334
  content: [{
13657
14335
  type: "text",
@@ -13678,15 +14356,16 @@ ${message}`
13678
14356
  task_list_id: exports_external.string().optional().describe("Task list ID to dispatch (use this or task_ids)"),
13679
14357
  stagger_ms: exports_external.number().optional().describe("Delay between each window dispatch. Default: 500ms."),
13680
14358
  delay_ms: exports_external.number().optional().describe("Delay between message send and Enter. Auto-calculated if omitted."),
14359
+ confirm_busy: exports_external.boolean().optional().describe("Send even if target tmux panes appear busy. Default: false."),
13681
14360
  dry_run: exports_external.boolean().optional().describe("Preview without sending. Default: false.")
13682
- }, async ({ targets, task_ids, task_list_id, stagger_ms, delay_ms, dry_run }) => {
14361
+ }, async ({ targets, task_ids, task_list_id, stagger_ms, delay_ms, confirm_busy, dry_run }) => {
13683
14362
  try {
13684
14363
  if (!task_ids && !task_list_id)
13685
14364
  throw new Error("Either task_ids or task_list_id is required");
13686
14365
  const db = getDatabase();
13687
14366
  const resolvedTaskIds = task_ids ? task_ids.map((id) => resolveId(id)) : undefined;
13688
14367
  const resolvedListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
13689
- const dispatches = await dispatchToMultiple({ targets, task_ids: resolvedTaskIds, task_list_id: resolvedListId, delay_ms, stagger_ms }, { dryRun: dry_run }, db);
14368
+ const dispatches = await dispatchToMultiple({ targets, task_ids: resolvedTaskIds, task_list_id: resolvedListId, delay_ms, stagger_ms }, { dryRun: dry_run, confirmBusy: confirm_busy ?? false }, db);
13690
14369
  const lines = dispatches.map((d) => `${d.target_window}: ${d.id} [${d.status}]`);
13691
14370
  return { content: [{ type: "text", text: `Dispatched to ${dispatches.length} target(s):
13692
14371
  ${lines.join(`
@@ -13731,11 +14410,12 @@ ${lines.join(`
13731
14410
  if (shouldRegisterTool("run_due_dispatches")) {
13732
14411
  server.tool("run_due_dispatches", "Manually trigger all pending dispatches that are due (scheduled_at <= now). Returns the count fired.", {
13733
14412
  dry_run: exports_external.boolean().optional().describe("Preview without sending. Default: false."),
14413
+ confirm_busy: exports_external.boolean().optional().describe("Send even if target tmux panes appear busy. Default: false."),
13734
14414
  all: exports_external.boolean().optional().describe("Ignore scheduled_at and fire all pending dispatches immediately.")
13735
- }, async ({ dry_run }) => {
14415
+ }, async ({ dry_run, confirm_busy }) => {
13736
14416
  try {
13737
14417
  const { runDueDispatches: runDueDispatches2 } = await Promise.resolve().then(() => (init_dispatch(), exports_dispatch));
13738
- const count = await runDueDispatches2({ dryRun: dry_run });
14418
+ const count = await runDueDispatches2({ dryRun: dry_run, confirmBusy: confirm_busy ?? false });
13739
14419
  return { content: [{ type: "text", text: `Fired ${count} dispatch(es).` }] };
13740
14420
  } catch (e) {
13741
14421
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
@@ -13748,6 +14428,7 @@ var init_dispatch2 = __esm(() => {
13748
14428
  init_dispatches();
13749
14429
  init_dispatch();
13750
14430
  init_dispatch_formatter();
14431
+ init_tmux();
13751
14432
  init_tasks();
13752
14433
  init_task_lists();
13753
14434
  init_database();
@@ -14555,7 +15236,7 @@ var init_task_crud2 = __esm(() => {
14555
15236
  });
14556
15237
 
14557
15238
  // src/lib/project-bootstrap.ts
14558
- import { existsSync as existsSync6, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
15239
+ import { existsSync as existsSync7, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
14559
15240
  import { basename as basename2, dirname as dirname5, resolve as resolve7 } from "path";
14560
15241
  function safeStat(path) {
14561
15242
  try {
@@ -14574,7 +15255,7 @@ function canonicalPath(input) {
14574
15255
  function findUp(start, marker) {
14575
15256
  let current = canonicalPath(start);
14576
15257
  while (true) {
14577
- if (existsSync6(resolve7(current, marker)))
15258
+ if (existsSync7(resolve7(current, marker)))
14578
15259
  return current;
14579
15260
  const parent = dirname5(current);
14580
15261
  if (parent === current)
@@ -14586,7 +15267,7 @@ function readPackageJson(path) {
14586
15267
  if (!path)
14587
15268
  return null;
14588
15269
  const file = resolve7(path, "package.json");
14589
- if (!existsSync6(file))
15270
+ if (!existsSync7(file))
14590
15271
  return null;
14591
15272
  try {
14592
15273
  const parsed = JSON.parse(readFileSync3(file, "utf-8"));
@@ -14608,7 +15289,7 @@ function workspaceMarker(root, rootPackage) {
14608
15289
  if (rootPackage?.workspaces)
14609
15290
  markers.push("package.json#workspaces");
14610
15291
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
14611
- if (existsSync6(resolve7(root, marker)))
15292
+ if (existsSync7(resolve7(root, marker)))
14612
15293
  markers.push(marker);
14613
15294
  }
14614
15295
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -14909,7 +15590,7 @@ var init_tags = __esm(() => {
14909
15590
  });
14910
15591
 
14911
15592
  // src/lib/retention-cleanup.ts
14912
- import { existsSync as existsSync7, unlinkSync } from "fs";
15593
+ import { existsSync as existsSync8, unlinkSync } from "fs";
14913
15594
  function normalizeScopes(scopes) {
14914
15595
  if (!scopes || scopes.length === 0)
14915
15596
  return [...ALL_SCOPES];
@@ -15112,7 +15793,7 @@ function applyRetentionCleanup(input, db) {
15112
15793
  for (const artifact of report.candidates.artifact_files) {
15113
15794
  try {
15114
15795
  const path = artifactStorePath(artifact.relative_path);
15115
- if (!existsSync7(path)) {
15796
+ if (!existsSync8(path)) {
15116
15797
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
15117
15798
  continue;
15118
15799
  }
@@ -15139,8 +15820,8 @@ var init_retention_cleanup = __esm(() => {
15139
15820
  });
15140
15821
 
15141
15822
  // src/lib/mention-resolver.ts
15142
- import { existsSync as existsSync8, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
15143
- import { basename as basename3, isAbsolute, join as join5, relative as relative3, resolve as resolve8, sep } from "path";
15823
+ import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
15824
+ import { basename as basename3, isAbsolute, join as join6, relative as relative3, resolve as resolve8, sep } from "path";
15144
15825
  function blankResolution(parsed) {
15145
15826
  return {
15146
15827
  input: parsed.input,
@@ -15238,7 +15919,7 @@ function resolveFile(parsed, workspace) {
15238
15919
  return resolution;
15239
15920
  }
15240
15921
  resolution.path = relPath;
15241
- if (!existsSync8(absolutePath)) {
15922
+ if (!existsSync9(absolutePath)) {
15242
15923
  resolution.warnings.push("file does not exist in the local workspace");
15243
15924
  return resolution;
15244
15925
  }
@@ -15271,7 +15952,7 @@ function walkSourceFiles(root, current = root, files = []) {
15271
15952
  if (SKIP_DIRS.has(entry.name))
15272
15953
  continue;
15273
15954
  }
15274
- const absolutePath = join5(current, entry.name);
15955
+ const absolutePath = join6(current, entry.name);
15275
15956
  if (entry.isDirectory()) {
15276
15957
  if (!SKIP_DIRS.has(entry.name))
15277
15958
  walkSourceFiles(root, absolutePath, files);
@@ -16538,8 +17219,8 @@ var init_local_notifications = __esm(() => {
16538
17219
  });
16539
17220
 
16540
17221
  // src/lib/local-encryption.ts
16541
- import { createCipheriv, createDecipheriv, createHash as createHash3, randomBytes, scryptSync, timingSafeEqual } from "crypto";
16542
- function now2() {
17222
+ import { createCipheriv, createDecipheriv, createHash as createHash3, randomBytes, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
17223
+ function now3() {
16543
17224
  return new Date().toISOString();
16544
17225
  }
16545
17226
  function sha2562(value) {
@@ -16576,7 +17257,7 @@ function upsertEncryptionProfile(input) {
16576
17257
  const name = normalizeProfileName(input.name);
16577
17258
  const config = loadConfig();
16578
17259
  const existing = config.encryption_profiles?.[name];
16579
- const timestamp = now2();
17260
+ const timestamp = now3();
16580
17261
  const profile = {
16581
17262
  name,
16582
17263
  algorithm: "aes-256-gcm",
@@ -16633,7 +17314,7 @@ function encryptString(plaintext, options = {}) {
16633
17314
  return {
16634
17315
  schemaVersion: TODOS_ENCRYPTION_SCHEMA_VERSION,
16635
17316
  kind: TODOS_ENCRYPTED_VALUE_KIND,
16636
- encryptedAt: options.encryptedAt ?? now2(),
17317
+ encryptedAt: options.encryptedAt ?? now3(),
16637
17318
  profile: profile.name,
16638
17319
  key_env: profile.key_env,
16639
17320
  algorithm: "aes-256-gcm",
@@ -16668,7 +17349,7 @@ function decryptString(envelope, env = process.env) {
16668
17349
  ]).toString("utf8");
16669
17350
  const expected = Buffer.from(envelope.plaintext_sha256, "hex");
16670
17351
  const actual = Buffer.from(sha2562(plaintext), "hex");
16671
- if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
17352
+ if (expected.length !== actual.length || !timingSafeEqual2(expected, actual)) {
16672
17353
  throw new EncryptedPayloadError("decrypted payload checksum mismatch");
16673
17354
  }
16674
17355
  return plaintext;
@@ -18191,7 +18872,7 @@ function createRoadmap(input) {
18191
18872
  if (!name)
18192
18873
  throw new Error("Roadmap name is required");
18193
18874
  const store = readStore();
18194
- const now3 = timestamp();
18875
+ const now4 = timestamp();
18195
18876
  const roadmap = {
18196
18877
  id: newId("roadmap"),
18197
18878
  name,
@@ -18202,8 +18883,8 @@ function createRoadmap(input) {
18202
18883
  agent_id: cleanString(input.agent_id),
18203
18884
  release: cleanString(input.release),
18204
18885
  milestone_ids: [],
18205
- created_at: now3,
18206
- updated_at: now3
18886
+ created_at: now4,
18887
+ updated_at: now4
18207
18888
  };
18208
18889
  store.roadmaps[roadmap.id] = roadmap;
18209
18890
  writeStore(store);
@@ -18255,7 +18936,7 @@ function createMilestone(input) {
18255
18936
  const title = input.title.trim();
18256
18937
  if (!title)
18257
18938
  throw new Error("Milestone title is required");
18258
- const now3 = timestamp();
18939
+ const now4 = timestamp();
18259
18940
  const milestone = {
18260
18941
  id: newId("milestone"),
18261
18942
  roadmap_id: roadmapId,
@@ -18270,11 +18951,11 @@ function createMilestone(input) {
18270
18951
  run_ids: cleanList(input.run_ids),
18271
18952
  release: cleanString(input.release ?? roadmap.release ?? undefined),
18272
18953
  tags: cleanList(input.tags),
18273
- created_at: now3,
18274
- updated_at: now3
18954
+ created_at: now4,
18955
+ updated_at: now4
18275
18956
  };
18276
18957
  store.milestones[milestone.id] = milestone;
18277
- store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList([...roadmap.milestone_ids, milestone.id]), updated_at: now3 };
18958
+ store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList([...roadmap.milestone_ids, milestone.id]), updated_at: now4 };
18278
18959
  writeStore(store);
18279
18960
  return milestone;
18280
18961
  }
@@ -18329,7 +19010,7 @@ function upsertReleaseGroup(input) {
18329
19010
  throw new Error("Release group name is required");
18330
19011
  const key = releaseKey(roadmapId, name);
18331
19012
  const existing = store.releases[key];
18332
- const now3 = timestamp();
19013
+ const now4 = timestamp();
18333
19014
  const release = {
18334
19015
  name,
18335
19016
  version: input.version === undefined ? existing?.version ?? null : cleanString(input.version),
@@ -18340,8 +19021,8 @@ function upsertReleaseGroup(input) {
18340
19021
  plan_ids: input.plan_ids === undefined ? existing?.plan_ids ?? [] : cleanList(input.plan_ids),
18341
19022
  run_ids: input.run_ids === undefined ? existing?.run_ids ?? [] : cleanList(input.run_ids),
18342
19023
  notes: input.notes === undefined ? existing?.notes ?? null : cleanString(input.notes),
18343
- created_at: existing?.created_at ?? now3,
18344
- updated_at: now3
19024
+ created_at: existing?.created_at ?? now4,
19025
+ updated_at: now4
18345
19026
  };
18346
19027
  store.releases[key] = release;
18347
19028
  writeStore(store);
@@ -18540,7 +19221,7 @@ function upsertCapacityProfile(input) {
18540
19221
  const store = readStore2();
18541
19222
  const key = profileKey(agentId, projectId);
18542
19223
  const existing = store.profiles[key];
18543
- const now3 = timestamp2();
19224
+ const now4 = timestamp2();
18544
19225
  const profile = {
18545
19226
  id: existing?.id ?? key,
18546
19227
  agent_id: agentId,
@@ -18548,8 +19229,8 @@ function upsertCapacityProfile(input) {
18548
19229
  minutes_per_day: assertMinutes(input.minutes_per_day),
18549
19230
  working_days: normalizeWorkingDays(input.working_days),
18550
19231
  effective_from: cleanString2(input.effective_from),
18551
- created_at: existing?.created_at ?? now3,
18552
- updated_at: now3
19232
+ created_at: existing?.created_at ?? now4,
19233
+ updated_at: now4
18553
19234
  };
18554
19235
  store.profiles[key] = profile;
18555
19236
  writeStore2(store);
@@ -18958,7 +19639,7 @@ var init_audit_ledger = __esm(() => {
18958
19639
 
18959
19640
  // src/lib/release-compatibility.ts
18960
19641
  import { readFileSync as readFileSync5 } from "fs";
18961
- import { join as join6, resolve as resolve10 } from "path";
19642
+ import { join as join7, resolve as resolve10 } from "path";
18962
19643
  import { Database as Database2 } from "bun:sqlite";
18963
19644
  function pass(id, message, details) {
18964
19645
  return { id, status: "passed", message, details };
@@ -18970,7 +19651,7 @@ function warn(id, message, details) {
18970
19651
  return { id, status: "warning", message, details };
18971
19652
  }
18972
19653
  function readPackageJson2(root) {
18973
- return JSON.parse(readFileSync5(join6(root, "package.json"), "utf8"));
19654
+ return JSON.parse(readFileSync5(join7(root, "package.json"), "utf8"));
18974
19655
  }
18975
19656
  function sortedKeys(value) {
18976
19657
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -19239,6 +19920,9 @@ function hasFts(db) {
19239
19920
  function escapeFtsQuery(q) {
19240
19921
  return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
19241
19922
  }
19923
+ function shouldUseFts(q) {
19924
+ return /^[\p{L}\p{N}_\-\s]+$/u.test(q);
19925
+ }
19242
19926
  function searchTasks(options, projectId, taskListId, db) {
19243
19927
  const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
19244
19928
  const d = db || getDatabase();
@@ -19247,7 +19931,8 @@ function searchTasks(options, projectId, taskListId, db) {
19247
19931
  let sql;
19248
19932
  const raw = opts.query?.trim() ?? "";
19249
19933
  const q = raw === "*" ? "" : raw;
19250
- if (hasFts(d) && q) {
19934
+ const useFts = hasFts(d) && q && shouldUseFts(q);
19935
+ if (useFts) {
19251
19936
  const ftsQuery = escapeFtsQuery(q);
19252
19937
  sql = `SELECT t.* FROM tasks t
19253
19938
  INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
@@ -19255,8 +19940,16 @@ function searchTasks(options, projectId, taskListId, db) {
19255
19940
  params.push(ftsQuery);
19256
19941
  } else if (q) {
19257
19942
  const pattern = `%${q}%`;
19258
- sql = `SELECT * FROM tasks t WHERE (t.title LIKE ? OR t.description LIKE ? OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?))`;
19259
- params.push(pattern, pattern, pattern);
19943
+ sql = `SELECT * FROM tasks t WHERE (
19944
+ t.id LIKE ?
19945
+ OR t.short_id LIKE ?
19946
+ OR t.title LIKE ?
19947
+ OR t.description LIKE ?
19948
+ OR t.working_dir LIKE ?
19949
+ OR t.metadata LIKE ?
19950
+ OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?)
19951
+ )`;
19952
+ params.push(pattern, pattern, pattern, pattern, pattern, pattern, pattern);
19260
19953
  } else {
19261
19954
  sql = `SELECT * FROM tasks t WHERE 1=1`;
19262
19955
  }
@@ -19312,7 +20005,7 @@ function searchTasks(options, projectId, taskListId, db) {
19312
20005
  } else if (opts.is_blocked === false) {
19313
20006
  sql += " AND t.id NOT IN (SELECT td.task_id FROM task_dependencies td JOIN tasks dep ON dep.id = td.depends_on WHERE dep.status != 'completed')";
19314
20007
  }
19315
- if (hasFts(d) && q) {
20008
+ if (useFts) {
19316
20009
  sql += ` ORDER BY bm25(tasks_fts),
19317
20010
  CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
19318
20011
  t.created_at DESC`;
@@ -22407,7 +23100,7 @@ function registerTaskWorkflowTools(server, ctx) {
22407
23100
  const globalRole = n.agent.role ? ` [${n.agent.role}]` : "";
22408
23101
  const lead = n.is_lead ? " \u2605" : "";
22409
23102
  const lastSeen = new Date(n.agent.last_seen_at).getTime();
22410
- const active = now3 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
23103
+ const active = now4 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
22411
23104
  const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${lead}`;
22412
23105
  const children = n.reports.length > 0 ? `
22413
23106
  ` + render(n.reports, indent + 1) : "";
@@ -22420,7 +23113,7 @@ function registerTaskWorkflowTools(server, ctx) {
22420
23113
  if (format === "json") {
22421
23114
  return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
22422
23115
  }
22423
- const now3 = Date.now();
23116
+ const now4 = Date.now();
22424
23117
  const ACTIVE_MS = 30 * 60 * 1000;
22425
23118
  const text = tree.length > 0 ? render(tree) : "No agents in org chart.";
22426
23119
  return { content: [{ type: "text", text }] };
@@ -22542,8 +23235,8 @@ var exports_doctor = {};
22542
23235
  __export(exports_doctor, {
22543
23236
  runTodosDoctor: () => runTodosDoctor
22544
23237
  });
22545
- import { chmodSync, copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync5, statSync as statSync5 } from "fs";
22546
- import { basename as basename4, dirname as dirname6, join as join7 } from "path";
23238
+ import { chmodSync, copyFileSync, existsSync as existsSync10, mkdirSync as mkdirSync5, statSync as statSync5 } from "fs";
23239
+ import { basename as basename4, dirname as dirname6, join as join8 } from "path";
22547
23240
  function tableExists(db, table) {
22548
23241
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
22549
23242
  }
@@ -22637,7 +23330,7 @@ function findMissingProjectRoots(db) {
22637
23330
  continue;
22638
23331
  if (!row.path.startsWith("/"))
22639
23332
  continue;
22640
- if (!existsSync9(row.path))
23333
+ if (!existsSync10(row.path))
22641
23334
  missing++;
22642
23335
  }
22643
23336
  return missing;
@@ -22697,16 +23390,16 @@ function databasePermissionsAreUnsafe(dbPath) {
22697
23390
  function createBackup(dbPath) {
22698
23391
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
22699
23392
  return;
22700
- if (!existsSync9(dbPath))
23393
+ if (!existsSync10(dbPath))
22701
23394
  return;
22702
23395
  const stamp = now().replace(/[:.]/g, "-");
22703
- const backupDir = join7(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
23396
+ const backupDir = join8(dirname6(dbPath), `${basename4(dbPath)}.backup-${stamp}`);
22704
23397
  const files = [];
22705
23398
  mkdirSync5(backupDir, { recursive: true });
22706
23399
  for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
22707
- if (!existsSync9(source))
23400
+ if (!existsSync10(source))
22708
23401
  continue;
22709
- const target = join7(backupDir, basename4(source));
23402
+ const target = join8(backupDir, basename4(source));
22710
23403
  copyFileSync(source, target);
22711
23404
  files.push(target);
22712
23405
  }
@@ -23010,14 +23703,14 @@ function registerTaskAutoTools(server, ctx) {
23010
23703
  return { content: [{ type: "text", text: "No agent_id provided and no agent focus is active." }], isError: true };
23011
23704
  }
23012
23705
  const assigned = listTasks3({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
23013
- const now3 = Date.now();
23014
- const dueSoonCutoff = now3 + 24 * 60 * 60 * 1000;
23706
+ const now4 = Date.now();
23707
+ const dueSoonCutoff = now4 + 24 * 60 * 60 * 1000;
23015
23708
  const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
23016
23709
  const workload = {
23017
23710
  in_progress: assigned.filter((t) => t.status === "in_progress").length,
23018
23711
  pending: assigned.filter((t) => t.status === "pending").length,
23019
- completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now3 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
23020
- due_soon: assigned.filter((t) => t.due_at && new Date(t.due_at).getTime() <= dueSoonCutoff && new Date(t.due_at).getTime() >= now3 && !["completed", "cancelled", "failed"].includes(t.status)).length,
23712
+ completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now4 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
23713
+ due_soon: assigned.filter((t) => t.due_at && new Date(t.due_at).getTime() <= dueSoonCutoff && new Date(t.due_at).getTime() >= now4 && !["completed", "cancelled", "failed"].includes(t.status)).length,
23021
23714
  blocked: blocked.length
23022
23715
  };
23023
23716
  const lines = [
@@ -23244,7 +23937,7 @@ function limits(input) {
23244
23937
  stale_after_hours: clamp(input.stale_after_hours, DEFAULT_LIMITS.stale_after_hours, 24 * 365)
23245
23938
  };
23246
23939
  }
23247
- function truncate(value, max) {
23940
+ function truncate2(value, max) {
23248
23941
  if (!value)
23249
23942
  return value ?? null;
23250
23943
  const redacted = redactEvidenceText(value);
@@ -23265,9 +23958,9 @@ function acceptanceCriteria(task, maxText) {
23265
23958
  const metadata = task.metadata || {};
23266
23959
  const raw = metadata["acceptance_criteria"] ?? metadata["acceptanceCriteria"] ?? metadata["criteria"];
23267
23960
  if (Array.isArray(raw))
23268
- return raw.map((item) => truncate(String(item), maxText)).filter((item) => Boolean(item));
23961
+ return raw.map((item) => truncate2(String(item), maxText)).filter((item) => Boolean(item));
23269
23962
  if (typeof raw === "string") {
23270
- return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate(line, maxText)).filter((item) => Boolean(item));
23963
+ return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate2(line, maxText)).filter((item) => Boolean(item));
23271
23964
  }
23272
23965
  return [];
23273
23966
  }
@@ -23290,7 +23983,7 @@ function addFile(files, path, source, base) {
23290
23983
  path,
23291
23984
  status: base?.status || "active",
23292
23985
  agent_id: base?.agent_id ?? null,
23293
- note: truncate(base?.note, 240),
23986
+ note: truncate2(base?.note, 240),
23294
23987
  updated_at: base?.updated_at || "",
23295
23988
  sources: [source]
23296
23989
  });
@@ -23348,7 +24041,7 @@ function estimateTokens(value) {
23348
24041
  return Math.max(1, Math.ceil((text || "").length / 4));
23349
24042
  }
23350
24043
  function summarizeStrings(values, maxChars) {
23351
- return truncate(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
24044
+ return truncate2(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
23352
24045
  }
23353
24046
  function summarizeSection(pack, section, maxChars) {
23354
24047
  if (section === "project")
@@ -23539,8 +24232,8 @@ function createAgentContextPack(input, db) {
23539
24232
  ...taskFiles.map((file) => file.updated_at)
23540
24233
  ], task.updated_at);
23541
24234
  const warnings = [];
23542
- const now3 = input.now ? new Date(input.now) : new Date;
23543
- if (Date.parse(task.updated_at) < now3.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
24235
+ const now4 = input.now ? new Date(input.now) : new Date;
24236
+ if (Date.parse(task.updated_at) < now4.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
23544
24237
  warnings.push(`task state is older than ${limit.stale_after_hours} hours`);
23545
24238
  }
23546
24239
  if (comments.length > recentComments.length)
@@ -23555,7 +24248,7 @@ function createAgentContextPack(input, db) {
23555
24248
  id: task.id,
23556
24249
  short_id: task.short_id,
23557
24250
  title: redactEvidenceText(task.title),
23558
- description: truncate(task.description, limit.max_text_chars),
24251
+ description: truncate2(task.description, limit.max_text_chars),
23559
24252
  status: task.status,
23560
24253
  priority: task.priority,
23561
24254
  assigned_to: task.assigned_to,
@@ -23579,7 +24272,7 @@ function createAgentContextPack(input, db) {
23579
24272
  plan: plan ? {
23580
24273
  id: plan.id,
23581
24274
  name: plan.name,
23582
- description: truncate(plan.description, limit.max_text_chars),
24275
+ description: truncate2(plan.description, limit.max_text_chars),
23583
24276
  status: plan.status,
23584
24277
  agent_id: plan.agent_id,
23585
24278
  tasks: planTasks.slice(0, limit.plan_task_limit).map(taskSummary).filter((item) => Boolean(item)),
@@ -23598,7 +24291,7 @@ function createAgentContextPack(input, db) {
23598
24291
  type: comment.type,
23599
24292
  progress_pct: comment.progress_pct,
23600
24293
  created_at: comment.created_at,
23601
- content: truncate(comment.content, limit.max_text_chars) || ""
24294
+ content: truncate2(comment.content, limit.max_text_chars) || ""
23602
24295
  })),
23603
24296
  omitted: Math.max(0, comments.length - recentComments.length)
23604
24297
  },
@@ -23606,7 +24299,7 @@ function createAgentContextPack(input, db) {
23606
24299
  traceability: {
23607
24300
  commits: traceability.commits.map((commit) => ({
23608
24301
  sha: commit.sha,
23609
- message: truncate(commit.message, 240),
24302
+ message: truncate2(commit.message, 240),
23610
24303
  files_changed: commit.files_changed,
23611
24304
  committed_at: commit.committed_at
23612
24305
  })),
@@ -23614,7 +24307,7 @@ function createAgentContextPack(input, db) {
23614
24307
  verifications: verifications.map((verification) => ({
23615
24308
  command: verification.command,
23616
24309
  status: verification.status,
23617
- output_summary: truncate(verification.output_summary, limit.max_text_chars),
24310
+ output_summary: truncate2(verification.output_summary, limit.max_text_chars),
23618
24311
  artifact_path: verification.artifact_path,
23619
24312
  run_at: verification.run_at
23620
24313
  })),
@@ -23625,14 +24318,14 @@ function createAgentContextPack(input, db) {
23625
24318
  id: ledger.run.id,
23626
24319
  title: ledger.run.title,
23627
24320
  status: ledger.run.status,
23628
- summary: truncate(ledger.run.summary, limit.max_text_chars),
24321
+ summary: truncate2(ledger.run.summary, limit.max_text_chars),
23629
24322
  agent_id: ledger.run.agent_id,
23630
24323
  started_at: ledger.run.started_at,
23631
24324
  completed_at: ledger.run.completed_at,
23632
- events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate(event.message, 500), created_at: event.created_at })),
23633
- commands: ledger.commands.map((command) => ({ command: command.command, status: command.status, output_summary: truncate(command.output_summary, limit.max_text_chars), artifact_path: command.artifact_path })),
23634
- files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate(file.note, 240) })),
23635
- artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate(artifact.description, 240), sha256: artifact.sha256 }))
24325
+ events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate2(event.message, 500), created_at: event.created_at })),
24326
+ commands: ledger.commands.map((command) => ({ command: command.command, status: command.status, output_summary: truncate2(command.output_summary, limit.max_text_chars), artifact_path: command.artifact_path })),
24327
+ files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate2(file.note, 240) })),
24328
+ artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate2(artifact.description, 240), sha256: artifact.sha256 }))
23636
24329
  })),
23637
24330
  omitted: Math.max(0, runs.length - selectedRuns.length)
23638
24331
  },
@@ -23737,7 +24430,7 @@ function renderAgentContextPackCompactMarkdown(pack) {
23737
24430
  const lines = [
23738
24431
  `# Context: ${pack.task.title}`,
23739
24432
  `${pack.task.status} | ${pack.task.priority} | ${pack.task.short_id || pack.task.id.slice(0, 8)}`,
23740
- pack.task.description ? truncate(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
24433
+ pack.task.description ? truncate2(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
23741
24434
  "",
23742
24435
  "## Must Know",
23743
24436
  bullet([
@@ -24987,7 +25680,7 @@ var init_agent_run_dispatcher = __esm(() => {
24987
25680
  });
24988
25681
 
24989
25682
  // src/lib/verification-providers.ts
24990
- import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
25683
+ import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
24991
25684
  function normalizeName5(name) {
24992
25685
  const normalized = name.trim().toLowerCase();
24993
25686
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -25139,7 +25832,7 @@ Timed out after ${provider.timeout_ms}ms`);
25139
25832
  };
25140
25833
  }
25141
25834
  function runCiLogProvider(input) {
25142
- const text = input.log_text ?? (input.log_path && existsSync10(input.log_path) ? readFileSync6(input.log_path, "utf-8") : "");
25835
+ const text = input.log_text ?? (input.log_path && existsSync11(input.log_path) ? readFileSync6(input.log_path, "utf-8") : "");
25143
25836
  return {
25144
25837
  status: classifyLog(text),
25145
25838
  attempts: 1,
@@ -25151,7 +25844,7 @@ function runBrowserProvider(input) {
25151
25844
  if (!input.artifact_path) {
25152
25845
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
25153
25846
  }
25154
- if (!existsSync10(input.artifact_path)) {
25847
+ if (!existsSync11(input.artifact_path)) {
25155
25848
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
25156
25849
  }
25157
25850
  return {
@@ -26941,12 +27634,12 @@ function summarizeTask(task) {
26941
27634
  };
26942
27635
  }
26943
27636
  function overdueTasks(tasks, nowIso) {
26944
- const now3 = Date.parse(nowIso);
27637
+ const now4 = Date.parse(nowIso);
26945
27638
  return tasks.filter((task) => {
26946
27639
  if (isTerminal(task) || !task.due_at)
26947
27640
  return false;
26948
27641
  const due = Date.parse(task.due_at);
26949
- return Number.isFinite(due) && due < now3;
27642
+ return Number.isFinite(due) && due < now4;
26950
27643
  });
26951
27644
  }
26952
27645
  function isReady(task, db) {
@@ -27296,15 +27989,15 @@ var init_local_reports = __esm(() => {
27296
27989
  });
27297
27990
 
27298
27991
  // src/lib/package-version.ts
27299
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
27300
- import { dirname as dirname7, join as join8 } from "path";
27992
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
27993
+ import { dirname as dirname7, join as join9 } from "path";
27301
27994
  import { fileURLToPath } from "url";
27302
27995
  function getPackageVersion(fromUrl = import.meta.url) {
27303
27996
  try {
27304
27997
  let dir = dirname7(fileURLToPath(fromUrl));
27305
27998
  for (let i = 0;i < 5; i++) {
27306
- const pkgPath = join8(dir, "package.json");
27307
- if (existsSync11(pkgPath)) {
27999
+ const pkgPath = join9(dir, "package.json");
28000
+ if (existsSync12(pkgPath)) {
27308
28001
  return JSON.parse(readFileSync7(pkgPath, "utf-8")).version || "0.0.0";
27309
28002
  }
27310
28003
  const parent = dirname7(dir);
@@ -29395,8 +30088,8 @@ __export(exports_local_extensions, {
29395
30088
  discoverLocalExtensions: () => discoverLocalExtensions
29396
30089
  });
29397
30090
  import { createHash as createHash8, createVerify } from "crypto";
29398
- import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
29399
- import { basename as basename5, join as join9, resolve as resolve12 } from "path";
30091
+ import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
30092
+ import { basename as basename5, join as join10, resolve as resolve12 } from "path";
29400
30093
  function isObject2(value) {
29401
30094
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
29402
30095
  }
@@ -29655,10 +30348,10 @@ function verifyExtensionSignature(input) {
29655
30348
  }
29656
30349
  function inspectExtensionSource(source3) {
29657
30350
  const resolved = resolve12(source3);
29658
- if (!existsSync12(resolved))
30351
+ if (!existsSync13(resolved))
29659
30352
  throw new Error(`extension source not found: ${source3}`);
29660
30353
  const stat = statSync6(resolved);
29661
- const manifestPath = stat.isDirectory() ? [join9(resolved, "todos.extension.json"), join9(resolved, "extension.json")].find(existsSync12) : resolved;
30354
+ const manifestPath = stat.isDirectory() ? [join10(resolved, "todos.extension.json"), join10(resolved, "extension.json")].find(existsSync13) : resolved;
29662
30355
  if (!manifestPath)
29663
30356
  throw new Error(`extension directory ${source3} is missing todos.extension.json`);
29664
30357
  const raw = readFileSync9(manifestPath);
@@ -29754,20 +30447,20 @@ function projectExtensionSources(projectPath) {
29754
30447
  return [];
29755
30448
  const root = resolve12(projectPath);
29756
30449
  const candidates = [
29757
- join9(root, "todos.extension.json"),
29758
- join9(root, ".todos", "todos.extension.json")
30450
+ join10(root, "todos.extension.json"),
30451
+ join10(root, ".todos", "todos.extension.json")
29759
30452
  ];
29760
- const extensionDir = join9(root, ".todos", "extensions");
29761
- if (existsSync12(extensionDir)) {
30453
+ const extensionDir = join10(root, ".todos", "extensions");
30454
+ if (existsSync13(extensionDir)) {
29762
30455
  for (const entry of readdirSync3(extensionDir)) {
29763
30456
  if (entry.startsWith("."))
29764
30457
  continue;
29765
- const full = join9(extensionDir, entry);
30458
+ const full = join10(extensionDir, entry);
29766
30459
  if (statSync6(full).isDirectory() || entry.endsWith(".json"))
29767
30460
  candidates.push(full);
29768
30461
  }
29769
30462
  }
29770
- return candidates.filter(existsSync12);
30463
+ return candidates.filter(existsSync13);
29771
30464
  }
29772
30465
  function discoverLocalExtensions(options = {}) {
29773
30466
  const config = loadConfig();
@@ -33289,7 +33982,7 @@ ${lines.join(`
33289
33982
  const projectRoles = n.project_roles.length > 0 ? ` <${n.project_roles.join(", ")}>` : "";
33290
33983
  const lead = n.is_project_lead ? " \u2605" : "";
33291
33984
  const lastSeen = new Date(n.agent.last_seen_at).getTime();
33292
- const active = now3 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
33985
+ const active = now4 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
33293
33986
  const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${projectRoles}${lead}`;
33294
33987
  const children = n.reports.length > 0 ? `
33295
33988
  ` + render(n.reports, indent + 1) : "";
@@ -33303,7 +33996,7 @@ ${lines.join(`
33303
33996
  if (format === "json") {
33304
33997
  return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
33305
33998
  }
33306
- const now3 = Date.now();
33999
+ const now4 = Date.now();
33307
34000
  const ACTIVE_MS = 30 * 60 * 1000;
33308
34001
  const text2 = tree.length > 0 ? render(tree) : "No agents in this project's org chart.";
33309
34002
  return { content: [{ type: "text", text: text2 }] };
@@ -33853,10 +34546,10 @@ ${lines.join(`
33853
34546
  });
33854
34547
  }
33855
34548
  if (shouldRegisterTool("get_idle_focus_prompts")) {
33856
- server.tool("get_idle_focus_prompts", "Return local idle prompts for active focus sessions.", { agent_id: exports_external.string().optional(), now: exports_external.string().optional() }, async ({ agent_id, now: now3 }) => {
34549
+ server.tool("get_idle_focus_prompts", "Return local idle prompts for active focus sessions.", { agent_id: exports_external.string().optional(), now: exports_external.string().optional() }, async ({ agent_id, now: now4 }) => {
33857
34550
  try {
33858
34551
  const { getIdleFocusSessionPrompts: getIdleFocusSessionPrompts2 } = (init_tasks(), __toCommonJS(exports_tasks));
33859
- return { content: [{ type: "text", text: JSON.stringify(getIdleFocusSessionPrompts2({ agent_id, now: now3 }), null, 2) }] };
34552
+ return { content: [{ type: "text", text: JSON.stringify(getIdleFocusSessionPrompts2({ agent_id, now: now4 }), null, 2) }] };
33860
34553
  } catch (e) {
33861
34554
  return { content: [{ type: "text", text: formatError(e) }], isError: true };
33862
34555
  }
@@ -34051,9 +34744,9 @@ __export(exports_extract, {
34051
34744
  buildCodebaseIndex: () => buildCodebaseIndex,
34052
34745
  EXTRACT_TAGS: () => EXTRACT_TAGS
34053
34746
  });
34054
- import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
34747
+ import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
34055
34748
  import { createHash as createHash10 } from "crypto";
34056
- import { relative as relative5, resolve as resolve13, join as join10 } from "path";
34749
+ import { relative as relative5, resolve as resolve13, join as join11 } from "path";
34057
34750
  function stableHash(value) {
34058
34751
  return createHash10("sha256").update(value).digest("hex");
34059
34752
  }
@@ -34062,8 +34755,8 @@ function normalizePathForMatch(value) {
34062
34755
  }
34063
34756
  function readGitignorePatterns(basePath) {
34064
34757
  const root = statSync7(basePath).isFile() ? resolve13(basePath, "..") : basePath;
34065
- const gitignorePath = join10(root, ".gitignore");
34066
- if (!existsSync13(gitignorePath))
34758
+ const gitignorePath = join11(root, ".gitignore");
34759
+ if (!existsSync14(gitignorePath))
34067
34760
  return [];
34068
34761
  try {
34069
34762
  return readFileSync10(gitignorePath, "utf-8").split(`
@@ -34205,7 +34898,7 @@ function buildCodebaseIndex(options) {
34205
34898
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
34206
34899
  const indexed = [];
34207
34900
  for (const file of files) {
34208
- const fullPath = statSync7(basePath).isFile() ? basePath : join10(basePath, file);
34901
+ const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
34209
34902
  try {
34210
34903
  const source3 = readFileSync10(fullPath, "utf-8");
34211
34904
  const relPath = statSync7(basePath).isFile() ? relative5(resolve13(basePath, ".."), fullPath) : file;
@@ -34236,7 +34929,7 @@ function extractTodos(options, db) {
34236
34929
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
34237
34930
  const allComments = [];
34238
34931
  for (const file of files) {
34239
- const fullPath = statSync7(basePath).isFile() ? basePath : join10(basePath, file);
34932
+ const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
34240
34933
  try {
34241
34934
  const source3 = readFileSync10(fullPath, "utf-8");
34242
34935
  const relPath = statSync7(basePath).isFile() ? relative5(resolve13(basePath, ".."), fullPath) : file;
@@ -35129,7 +35822,7 @@ __export(exports_builtin_templates, {
35129
35822
  BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
35130
35823
  });
35131
35824
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
35132
- import { join as join11 } from "path";
35825
+ import { join as join12 } from "path";
35133
35826
  function templateMetadata(template) {
35134
35827
  return {
35135
35828
  source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
@@ -35188,7 +35881,7 @@ function writeBuiltinTemplateFiles(directory) {
35188
35881
  mkdirSync7(directory, { recursive: true });
35189
35882
  const files = [];
35190
35883
  for (const entry of exportBuiltinTemplateFiles()) {
35191
- const path = join11(directory, entry.filename);
35884
+ const path = join12(directory, entry.filename);
35192
35885
  writeFileSync4(path, `${JSON.stringify(entry.template, null, 2)}
35193
35886
  `, "utf-8");
35194
35887
  files.push(path);
@@ -35714,16 +36407,16 @@ __export(exports_environment_snapshots, {
35714
36407
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
35715
36408
  });
35716
36409
  import { createHash as createHash11 } from "crypto";
35717
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
36410
+ import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
35718
36411
  import { hostname as hostname2, platform, arch } from "os";
35719
- import { dirname as dirname9, join as join12, resolve as resolve14 } from "path";
36412
+ import { dirname as dirname9, join as join13, resolve as resolve14 } from "path";
35720
36413
  import { tmpdir as tmpdir2 } from "os";
35721
36414
  function sha2566(value) {
35722
36415
  return createHash11("sha256").update(value).digest("hex");
35723
36416
  }
35724
36417
  function fileRecord(root, relativePath) {
35725
- const path = join12(root, relativePath);
35726
- if (!existsSync14(path))
36418
+ const path = join13(root, relativePath);
36419
+ if (!existsSync15(path))
35727
36420
  return null;
35728
36421
  const stat = statSync8(path);
35729
36422
  if (!stat.isFile())
@@ -35735,7 +36428,7 @@ function manifestRecord(root, relativePath) {
35735
36428
  const base = fileRecord(root, relativePath);
35736
36429
  if (!base)
35737
36430
  return null;
35738
- const parsed = readJsonFile(join12(root, relativePath));
36431
+ const parsed = readJsonFile(join13(root, relativePath));
35739
36432
  if (!parsed)
35740
36433
  return { ...base, redacted: {} };
35741
36434
  const redacted = redactValue({
@@ -35830,8 +36523,8 @@ function commandEnv(env, includeValues) {
35830
36523
  function defaultSnapshotDir() {
35831
36524
  const dbPath = getDatabasePath();
35832
36525
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
35833
- return join12(tmpdir2(), "hasna-todos", "environment-snapshots");
35834
- return join12(dirname9(resolve14(dbPath)), "environment-snapshots");
36526
+ return join13(tmpdir2(), "hasna-todos", "environment-snapshots");
36527
+ return join13(dirname9(resolve14(dbPath)), "environment-snapshots");
35835
36528
  }
35836
36529
  function snapshotWithId(snapshot) {
35837
36530
  const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
@@ -35878,7 +36571,7 @@ function captureEnvironmentSnapshot(input = {}) {
35878
36571
  });
35879
36572
  }
35880
36573
  function writeEnvironmentSnapshot(snapshot, outputPath) {
35881
- const path = outputPath ? resolve14(outputPath) : join12(defaultSnapshotDir(), `${snapshot.id}.json`);
36574
+ const path = outputPath ? resolve14(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
35882
36575
  ensureDir2(dirname9(path));
35883
36576
  writeJsonFile(path, snapshot);
35884
36577
  return path;
@@ -36250,7 +36943,7 @@ var init_http = __esm(() => {
36250
36943
  });
36251
36944
 
36252
36945
  // src/db/api-keys.ts
36253
- import { createHash as createHash12, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "crypto";
36946
+ import { createHash as createHash12, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual3 } from "crypto";
36254
36947
  function rowToRecord(row) {
36255
36948
  return {
36256
36949
  id: row.id,
@@ -36269,7 +36962,7 @@ function hashApiKey(key) {
36269
36962
  function safeEqualHex(a, b) {
36270
36963
  if (a.length !== b.length)
36271
36964
  return false;
36272
- return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
36965
+ return timingSafeEqual3(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
36273
36966
  }
36274
36967
  function hasActiveApiKeys(db) {
36275
36968
  const d = db || getDatabase();
@@ -36430,7 +37123,7 @@ var init_headless_boundaries = __esm(() => {
36430
37123
  });
36431
37124
 
36432
37125
  // src/server/routes.ts
36433
- import { join as join13, resolve as resolve15, sep as sep2 } from "path";
37126
+ import { join as join14, resolve as resolve15, sep as sep2 } from "path";
36434
37127
  function parseFieldsParam(url) {
36435
37128
  const fieldsParam = url.searchParams.get("fields");
36436
37129
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -37134,7 +37827,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
37134
37827
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
37135
37828
  return null;
37136
37829
  if (path !== "/") {
37137
- const filePath = join13(ctx.dashboardDir, path);
37830
+ const filePath = join14(ctx.dashboardDir, path);
37138
37831
  const resolvedFile = resolve15(filePath);
37139
37832
  const resolvedBase = resolve15(ctx.dashboardDir);
37140
37833
  if (!resolvedFile.startsWith(resolvedBase + sep2) && resolvedFile !== resolvedBase) {
@@ -37144,7 +37837,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
37144
37837
  if (res2)
37145
37838
  return res2;
37146
37839
  }
37147
- const indexPath = join13(ctx.dashboardDir, "index.html");
37840
+ const indexPath = join14(ctx.dashboardDir, "index.html");
37148
37841
  const res = serveStaticFile2(indexPath);
37149
37842
  if (res)
37150
37843
  return res;
@@ -37173,27 +37866,27 @@ __export(exports_serve, {
37173
37866
  SECURITY_HEADERS: () => SECURITY_HEADERS,
37174
37867
  MIME_TYPES: () => MIME_TYPES
37175
37868
  });
37176
- import { existsSync as existsSync15 } from "fs";
37177
- import { join as join14, dirname as dirname10, extname } from "path";
37869
+ import { existsSync as existsSync16 } from "fs";
37870
+ import { join as join15, dirname as dirname10, extname } from "path";
37178
37871
  import { fileURLToPath as fileURLToPath2 } from "url";
37179
37872
  function resolveDashboardDir() {
37180
37873
  const candidates = [];
37181
37874
  try {
37182
37875
  const scriptDir = dirname10(fileURLToPath2(import.meta.url));
37183
- candidates.push(join14(scriptDir, "..", "dashboard", "dist"));
37184
- candidates.push(join14(scriptDir, "..", "..", "dashboard", "dist"));
37876
+ candidates.push(join15(scriptDir, "..", "dashboard", "dist"));
37877
+ candidates.push(join15(scriptDir, "..", "..", "dashboard", "dist"));
37185
37878
  } catch {}
37186
37879
  if (process.argv[1]) {
37187
37880
  const mainDir = dirname10(process.argv[1]);
37188
- candidates.push(join14(mainDir, "..", "dashboard", "dist"));
37189
- candidates.push(join14(mainDir, "..", "..", "dashboard", "dist"));
37881
+ candidates.push(join15(mainDir, "..", "dashboard", "dist"));
37882
+ candidates.push(join15(mainDir, "..", "..", "dashboard", "dist"));
37190
37883
  }
37191
- candidates.push(join14(process.cwd(), "dashboard", "dist"));
37884
+ candidates.push(join15(process.cwd(), "dashboard", "dist"));
37192
37885
  for (const candidate of candidates) {
37193
- if (existsSync15(candidate))
37886
+ if (existsSync16(candidate))
37194
37887
  return candidate;
37195
37888
  }
37196
- return join14(process.cwd(), "dashboard", "dist");
37889
+ return join15(process.cwd(), "dashboard", "dist");
37197
37890
  }
37198
37891
  function getProvidedApiKey(req) {
37199
37892
  const headerKey = req.headers.get("x-api-key");
@@ -37220,15 +37913,15 @@ function checkAuth(req, apiKey) {
37220
37913
  return null;
37221
37914
  }
37222
37915
  function checkRateLimit(ip) {
37223
- const now3 = Date.now();
37916
+ const now4 = Date.now();
37224
37917
  const entry = rateLimitMap.get(ip);
37225
- if (!entry || now3 > entry.resetAt) {
37226
- rateLimitMap.set(ip, { count: 1, resetAt: now3 + RATE_LIMIT_WINDOW_MS });
37918
+ if (!entry || now4 > entry.resetAt) {
37919
+ rateLimitMap.set(ip, { count: 1, resetAt: now4 + RATE_LIMIT_WINDOW_MS });
37227
37920
  return { allowed: true };
37228
37921
  }
37229
37922
  entry.count++;
37230
37923
  if (entry.count > RATE_LIMIT_MAX) {
37231
- return { allowed: false, retryAfter: Math.ceil((entry.resetAt - now3) / 1000) };
37924
+ return { allowed: false, retryAfter: Math.ceil((entry.resetAt - now4) / 1000) };
37232
37925
  }
37233
37926
  return { allowed: true };
37234
37927
  }
@@ -37243,7 +37936,7 @@ function json(data, status = 200, headers) {
37243
37936
  });
37244
37937
  }
37245
37938
  function serveStaticFile(filePath) {
37246
- if (!existsSync15(filePath))
37939
+ if (!existsSync16(filePath))
37247
37940
  return null;
37248
37941
  const ext = extname(filePath);
37249
37942
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -37322,7 +38015,7 @@ data: ${data}
37322
38015
  filteredSseClients.delete(client);
37323
38016
  }
37324
38017
  const dashboardDir = resolveDashboardDir();
37325
- const dashboardExists = existsSync15(dashboardDir);
38018
+ const dashboardExists = existsSync16(dashboardDir);
37326
38019
  if (!dashboardExists) {
37327
38020
  console.error(`
37328
38021
  Dashboard not found at: ${dashboardDir}`);