@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.
@@ -3989,6 +3989,7 @@ var init_event_hooks = __esm(() => {
3989
3989
  init_runner_sandbox();
3990
3990
  init_config();
3991
3991
  LOCAL_EVENT_TYPES = [
3992
+ "task.created",
3992
3993
  "task.assigned",
3993
3994
  "task.blocked",
3994
3995
  "task.started",
@@ -4013,6 +4014,572 @@ var init_event_hooks = __esm(() => {
4013
4014
  VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
4014
4015
  });
4015
4016
 
4017
+ // node_modules/.bun/@hasna+events@0.1.7/node_modules/@hasna/events/dist/index.js
4018
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
4019
+ import { existsSync as existsSync6 } from "fs";
4020
+ import { homedir } from "os";
4021
+ import { join as join5 } from "path";
4022
+ import { createHmac, timingSafeEqual as timingSafeEqual2 } from "crypto";
4023
+ import { randomUUID as randomUUID2 } from "crypto";
4024
+ import { spawn } from "child_process";
4025
+ import { randomUUID as randomUUID22 } from "crypto";
4026
+ function getPathValue(input, path) {
4027
+ return path.split(".").reduce((value, part) => {
4028
+ if (value && typeof value === "object" && part in value) {
4029
+ return value[part];
4030
+ }
4031
+ return;
4032
+ }, input);
4033
+ }
4034
+ function wildcardToRegExp(pattern) {
4035
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
4036
+ return new RegExp(`^${escaped}$`);
4037
+ }
4038
+ function matchString(value, matcher) {
4039
+ if (matcher === undefined)
4040
+ return true;
4041
+ if (value === undefined)
4042
+ return false;
4043
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
4044
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
4045
+ }
4046
+ function matchRecord(input, matcher) {
4047
+ if (!matcher)
4048
+ return true;
4049
+ return Object.entries(matcher).every(([path, expected]) => {
4050
+ const actual = getPathValue(input, path);
4051
+ if (typeof expected === "string" || Array.isArray(expected)) {
4052
+ return matchString(actual === undefined ? undefined : String(actual), expected);
4053
+ }
4054
+ return actual === expected;
4055
+ });
4056
+ }
4057
+ function eventMatchesFilter(event, filter) {
4058
+ 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);
4059
+ }
4060
+ function channelMatchesEvent(channel, event) {
4061
+ if (!channel.enabled)
4062
+ return false;
4063
+ if (!channel.filters || channel.filters.length === 0)
4064
+ return true;
4065
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
4066
+ }
4067
+ function getEventsDataDir(override) {
4068
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir(), ".hasna", "events");
4069
+ }
4070
+
4071
+ class JsonEventsStore {
4072
+ dataDir;
4073
+ channelsPath;
4074
+ eventsPath;
4075
+ deliveriesPath;
4076
+ constructor(dataDir = getEventsDataDir()) {
4077
+ this.dataDir = dataDir;
4078
+ this.channelsPath = join5(dataDir, "channels.json");
4079
+ this.eventsPath = join5(dataDir, "events.json");
4080
+ this.deliveriesPath = join5(dataDir, "deliveries.json");
4081
+ }
4082
+ async init() {
4083
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
4084
+ await chmod(this.dataDir, 448).catch(() => {
4085
+ return;
4086
+ });
4087
+ await this.ensureArrayFile(this.channelsPath);
4088
+ await this.ensureArrayFile(this.eventsPath);
4089
+ await this.ensureArrayFile(this.deliveriesPath);
4090
+ }
4091
+ async addChannel(channel) {
4092
+ await this.init();
4093
+ const channels = await this.readJson(this.channelsPath, []);
4094
+ const index = channels.findIndex((item) => item.id === channel.id);
4095
+ if (index >= 0) {
4096
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
4097
+ } else {
4098
+ channels.push(channel);
4099
+ }
4100
+ await this.writeJson(this.channelsPath, channels);
4101
+ return index >= 0 ? channels[index] : channel;
4102
+ }
4103
+ async listChannels() {
4104
+ await this.init();
4105
+ return this.readJson(this.channelsPath, []);
4106
+ }
4107
+ async getChannel(id) {
4108
+ const channels = await this.listChannels();
4109
+ return channels.find((channel) => channel.id === id);
4110
+ }
4111
+ async removeChannel(id) {
4112
+ await this.init();
4113
+ const channels = await this.readJson(this.channelsPath, []);
4114
+ const next = channels.filter((channel) => channel.id !== id);
4115
+ await this.writeJson(this.channelsPath, next);
4116
+ return next.length !== channels.length;
4117
+ }
4118
+ async appendEvent(event) {
4119
+ await this.init();
4120
+ const events = await this.readJson(this.eventsPath, []);
4121
+ events.push(event);
4122
+ await this.writeJson(this.eventsPath, events);
4123
+ return event;
4124
+ }
4125
+ async listEvents() {
4126
+ await this.init();
4127
+ return this.readJson(this.eventsPath, []);
4128
+ }
4129
+ async findEventByIdentity(identity) {
4130
+ const events = await this.listEvents();
4131
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
4132
+ }
4133
+ async appendDelivery(result) {
4134
+ await this.init();
4135
+ const deliveries = await this.readJson(this.deliveriesPath, []);
4136
+ deliveries.push(result);
4137
+ await this.writeJson(this.deliveriesPath, deliveries);
4138
+ return result;
4139
+ }
4140
+ async listDeliveries() {
4141
+ await this.init();
4142
+ return this.readJson(this.deliveriesPath, []);
4143
+ }
4144
+ async exportData() {
4145
+ return {
4146
+ channels: await this.listChannels(),
4147
+ events: await this.listEvents(),
4148
+ deliveries: await this.listDeliveries()
4149
+ };
4150
+ }
4151
+ async ensureArrayFile(path) {
4152
+ if (!existsSync6(path)) {
4153
+ await writeFile(path, `[]
4154
+ `, { encoding: "utf-8", mode: 384 });
4155
+ }
4156
+ await chmod(path, 384).catch(() => {
4157
+ return;
4158
+ });
4159
+ }
4160
+ async readJson(path, fallback) {
4161
+ try {
4162
+ const raw = await readFile(path, "utf-8");
4163
+ if (!raw.trim())
4164
+ return fallback;
4165
+ return JSON.parse(raw);
4166
+ } catch (error) {
4167
+ if (error.code === "ENOENT")
4168
+ return fallback;
4169
+ throw error;
4170
+ }
4171
+ }
4172
+ async writeJson(path, value) {
4173
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
4174
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
4175
+ `, { encoding: "utf-8", mode: 384 });
4176
+ await rename(tempPath, path);
4177
+ await chmod(path, 384).catch(() => {
4178
+ return;
4179
+ });
4180
+ }
4181
+ }
4182
+ function buildSignatureBase(timestamp, body) {
4183
+ return `${timestamp}.${body}`;
4184
+ }
4185
+ function signPayload(secret, timestamp, body) {
4186
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
4187
+ return `sha256=${digest}`;
4188
+ }
4189
+ function now2() {
4190
+ return new Date().toISOString();
4191
+ }
4192
+ function truncate(value, max = 4096) {
4193
+ return value.length > max ? `${value.slice(0, max)}...` : value;
4194
+ }
4195
+ function buildWebhookRequest(event, channel) {
4196
+ if (!channel.webhook)
4197
+ throw new Error(`Channel ${channel.id} has no webhook config`);
4198
+ const body = JSON.stringify(event);
4199
+ const timestamp = event.time;
4200
+ const headers = {
4201
+ "Content-Type": "application/json",
4202
+ "User-Agent": "@hasna/events",
4203
+ "X-Hasna-Event-Id": event.id,
4204
+ "X-Hasna-Event-Type": event.type,
4205
+ "X-Hasna-Timestamp": timestamp,
4206
+ ...channel.webhook.headers
4207
+ };
4208
+ if (channel.webhook.secret) {
4209
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
4210
+ }
4211
+ return { body, headers };
4212
+ }
4213
+ async function dispatchWebhook(event, channel, options = {}) {
4214
+ if (!channel.webhook)
4215
+ throw new Error(`Channel ${channel.id} has no webhook config`);
4216
+ const startedAt = now2();
4217
+ const { body, headers } = buildWebhookRequest(event, channel);
4218
+ const controller = new AbortController;
4219
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
4220
+ try {
4221
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
4222
+ method: "POST",
4223
+ headers,
4224
+ body,
4225
+ signal: controller.signal
4226
+ });
4227
+ const responseBody = truncate(await response.text());
4228
+ return {
4229
+ attempt: 1,
4230
+ status: response.ok ? "success" : "failed",
4231
+ startedAt,
4232
+ completedAt: now2(),
4233
+ responseStatus: response.status,
4234
+ responseBody,
4235
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
4236
+ };
4237
+ } catch (error) {
4238
+ return {
4239
+ attempt: 1,
4240
+ status: "failed",
4241
+ startedAt,
4242
+ completedAt: now2(),
4243
+ error: error instanceof Error ? error.message : String(error)
4244
+ };
4245
+ } finally {
4246
+ clearTimeout(timeout);
4247
+ }
4248
+ }
4249
+ async function dispatchCommand(event, channel) {
4250
+ if (!channel.command)
4251
+ throw new Error(`Channel ${channel.id} has no command config`);
4252
+ const startedAt = now2();
4253
+ const eventJson = JSON.stringify(event);
4254
+ const env = {
4255
+ ...process.env,
4256
+ ...channel.command.env,
4257
+ HASNA_CHANNEL_ID: channel.id,
4258
+ HASNA_EVENT_ID: event.id,
4259
+ HASNA_EVENT_TYPE: event.type,
4260
+ HASNA_EVENT_SOURCE: event.source,
4261
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
4262
+ HASNA_EVENT_SEVERITY: event.severity,
4263
+ HASNA_EVENT_TIME: event.time,
4264
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
4265
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
4266
+ HASNA_EVENT_JSON: eventJson
4267
+ };
4268
+ return new Promise((resolve6) => {
4269
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
4270
+ cwd: channel.command.cwd,
4271
+ env,
4272
+ stdio: ["pipe", "pipe", "pipe"]
4273
+ });
4274
+ let stdout = "";
4275
+ let stderr = "";
4276
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
4277
+ child.stdin.end(eventJson);
4278
+ child.stdout.on("data", (chunk) => {
4279
+ stdout += chunk.toString();
4280
+ });
4281
+ child.stderr.on("data", (chunk) => {
4282
+ stderr += chunk.toString();
4283
+ });
4284
+ child.on("error", (error) => {
4285
+ clearTimeout(timeout);
4286
+ resolve6({
4287
+ attempt: 1,
4288
+ status: "failed",
4289
+ startedAt,
4290
+ completedAt: now2(),
4291
+ stdout: truncate(stdout),
4292
+ stderr: truncate(stderr),
4293
+ error: error.message
4294
+ });
4295
+ });
4296
+ child.on("close", (code, signal) => {
4297
+ clearTimeout(timeout);
4298
+ const success = code === 0;
4299
+ resolve6({
4300
+ attempt: 1,
4301
+ status: success ? "success" : "failed",
4302
+ startedAt,
4303
+ completedAt: now2(),
4304
+ stdout: truncate(stdout),
4305
+ stderr: truncate(stderr),
4306
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
4307
+ });
4308
+ });
4309
+ });
4310
+ }
4311
+ async function dispatchChannel(event, channel, options = {}) {
4312
+ if (channel.transport === "webhook")
4313
+ return dispatchWebhook(event, channel, options);
4314
+ if (channel.transport === "command")
4315
+ return dispatchCommand(event, channel);
4316
+ return {
4317
+ attempt: 1,
4318
+ status: "skipped",
4319
+ startedAt: now2(),
4320
+ completedAt: now2(),
4321
+ error: `Unsupported transport: ${channel.transport}`
4322
+ };
4323
+ }
4324
+ function createDeliveryResult(event, channel, attempts) {
4325
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
4326
+ return {
4327
+ id: randomUUID2(),
4328
+ eventId: event.id,
4329
+ channelId: channel.id,
4330
+ transport: channel.transport,
4331
+ status,
4332
+ attempts,
4333
+ createdAt: attempts[0]?.startedAt ?? now2(),
4334
+ completedAt: attempts.at(-1)?.completedAt ?? now2()
4335
+ };
4336
+ }
4337
+ function createEvent(input) {
4338
+ return {
4339
+ id: input.id ?? randomUUID22(),
4340
+ source: input.source,
4341
+ type: input.type,
4342
+ time: normalizeTime(input.time),
4343
+ subject: input.subject,
4344
+ severity: input.severity ?? "info",
4345
+ data: input.data ?? {},
4346
+ message: input.message,
4347
+ dedupeKey: input.dedupeKey,
4348
+ schemaVersion: input.schemaVersion ?? "1.0",
4349
+ metadata: input.metadata ?? {}
4350
+ };
4351
+ }
4352
+
4353
+ class EventsClient {
4354
+ store;
4355
+ redactors;
4356
+ transportOptions;
4357
+ constructor(options = {}) {
4358
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
4359
+ this.redactors = options.redactors ?? [];
4360
+ this.transportOptions = { fetchImpl: options.fetchImpl };
4361
+ }
4362
+ async addChannel(input) {
4363
+ const timestamp = new Date().toISOString();
4364
+ return this.store.addChannel({
4365
+ ...input,
4366
+ createdAt: input.createdAt ?? timestamp,
4367
+ updatedAt: input.updatedAt ?? timestamp
4368
+ });
4369
+ }
4370
+ async listChannels() {
4371
+ return this.store.listChannels();
4372
+ }
4373
+ async removeChannel(id) {
4374
+ return this.store.removeChannel(id);
4375
+ }
4376
+ async emit(input, options = {}) {
4377
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
4378
+ if (options.dedupe !== false) {
4379
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
4380
+ if (existing) {
4381
+ return { event: existing, deliveries: [], deduped: true };
4382
+ }
4383
+ }
4384
+ await this.store.appendEvent(event);
4385
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
4386
+ return { event, deliveries, deduped: false };
4387
+ }
4388
+ async listEvents() {
4389
+ return this.store.listEvents();
4390
+ }
4391
+ async listDeliveries() {
4392
+ return this.store.listDeliveries();
4393
+ }
4394
+ async deliver(event) {
4395
+ const channels = await this.store.listChannels();
4396
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
4397
+ const deliveries = [];
4398
+ for (const channel of selected) {
4399
+ const eventForChannel = await this.applyRedaction(event, channel);
4400
+ const result = await this.deliverWithRetry(eventForChannel, channel);
4401
+ await this.store.appendDelivery(result);
4402
+ deliveries.push(result);
4403
+ }
4404
+ return deliveries;
4405
+ }
4406
+ async testChannel(id, input = {}) {
4407
+ const channel = await this.store.getChannel(id);
4408
+ if (!channel)
4409
+ throw new Error(`Channel not found: ${id}`);
4410
+ const event = createEvent({
4411
+ source: input.source ?? "hasna.events",
4412
+ type: input.type ?? "events.test",
4413
+ subject: input.subject ?? id,
4414
+ severity: input.severity ?? "info",
4415
+ data: input.data ?? { test: true },
4416
+ message: input.message ?? "Hasna events test delivery",
4417
+ dedupeKey: input.dedupeKey,
4418
+ schemaVersion: input.schemaVersion,
4419
+ metadata: input.metadata,
4420
+ time: input.time,
4421
+ id: input.id
4422
+ });
4423
+ const eventForChannel = await this.applyRedaction(event, channel);
4424
+ const result = await this.deliverWithRetry(eventForChannel, channel);
4425
+ await this.store.appendDelivery(result);
4426
+ return result;
4427
+ }
4428
+ async replay(options = {}) {
4429
+ const events = (await this.store.listEvents()).filter((event) => {
4430
+ if (options.eventId && event.id !== options.eventId)
4431
+ return false;
4432
+ if (options.source && event.source !== options.source)
4433
+ return false;
4434
+ if (options.type && event.type !== options.type)
4435
+ return false;
4436
+ return true;
4437
+ });
4438
+ if (options.dryRun)
4439
+ return { events, deliveries: [] };
4440
+ const deliveries = [];
4441
+ for (const event of events) {
4442
+ deliveries.push(...await this.deliver(event));
4443
+ }
4444
+ return { events, deliveries };
4445
+ }
4446
+ async applyRedaction(event, channel) {
4447
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
4448
+ for (const redactor of this.redactors) {
4449
+ next = await redactor(next, channel);
4450
+ }
4451
+ return next;
4452
+ }
4453
+ async deliverWithRetry(event, channel) {
4454
+ const policy = normalizeRetryPolicy(channel.retry);
4455
+ const attempts = [];
4456
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
4457
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
4458
+ attempt.attempt = index + 1;
4459
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
4460
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
4461
+ }
4462
+ attempts.push(attempt);
4463
+ if (attempt.status !== "failed")
4464
+ break;
4465
+ if (attempt.nextBackoffMs)
4466
+ await Bun.sleep(attempt.nextBackoffMs);
4467
+ }
4468
+ return createDeliveryResult(event, channel, attempts);
4469
+ }
4470
+ }
4471
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
4472
+ if (paths.length === 0)
4473
+ return event;
4474
+ const copy = structuredClone(event);
4475
+ for (const path of paths) {
4476
+ setPath(copy, path, replacement);
4477
+ }
4478
+ return copy;
4479
+ }
4480
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
4481
+ return redactValue2(event, replacement);
4482
+ }
4483
+ function shouldRedactKey(key) {
4484
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
4485
+ }
4486
+ function redactValue2(value, replacement) {
4487
+ if (Array.isArray(value))
4488
+ return value.map((item) => redactValue2(item, replacement));
4489
+ if (!value || typeof value !== "object")
4490
+ return value;
4491
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
4492
+ key,
4493
+ shouldRedactKey(key) ? replacement : redactValue2(item, replacement)
4494
+ ]));
4495
+ }
4496
+ function setPath(input, path, replacement) {
4497
+ const parts = path.split(".");
4498
+ let cursor = input;
4499
+ for (const part of parts.slice(0, -1)) {
4500
+ const next = cursor[part];
4501
+ if (!next || typeof next !== "object")
4502
+ return;
4503
+ cursor = next;
4504
+ }
4505
+ const last = parts.at(-1);
4506
+ if (last && last in cursor)
4507
+ cursor[last] = replacement;
4508
+ }
4509
+ function normalizeTime(value) {
4510
+ if (!value)
4511
+ return new Date().toISOString();
4512
+ return value instanceof Date ? value.toISOString() : value;
4513
+ }
4514
+ function normalizeRetryPolicy(policy) {
4515
+ return {
4516
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
4517
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
4518
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
4519
+ };
4520
+ }
4521
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR", HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME", DEFAULT_SIGNATURE_TOLERANCE_MS;
4522
+ var init_dist = __esm(() => {
4523
+ DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
4524
+ });
4525
+
4526
+ // src/lib/shared-events.ts
4527
+ function taskEventData(task, extra = {}) {
4528
+ return {
4529
+ id: task.id,
4530
+ task_id: task.id,
4531
+ short_id: task.short_id,
4532
+ title: task.title,
4533
+ description: task.description,
4534
+ status: task.status,
4535
+ priority: task.priority,
4536
+ project_id: task.project_id,
4537
+ parent_id: task.parent_id,
4538
+ plan_id: task.plan_id,
4539
+ task_list_id: task.task_list_id,
4540
+ agent_id: task.agent_id,
4541
+ assigned_to: task.assigned_to,
4542
+ session_id: task.session_id,
4543
+ working_dir: task.working_dir,
4544
+ tags: task.tags,
4545
+ metadata: task.metadata,
4546
+ version: task.version,
4547
+ created_at: task.created_at,
4548
+ updated_at: task.updated_at,
4549
+ started_at: task.started_at,
4550
+ completed_at: task.completed_at,
4551
+ due_at: task.due_at,
4552
+ ...extra
4553
+ };
4554
+ }
4555
+ async function emitSharedTaskEvent(input) {
4556
+ const data = taskEventData(input.task, input.data);
4557
+ await new EventsClient().emit({
4558
+ source: SOURCE,
4559
+ type: input.type,
4560
+ subject: input.task.id,
4561
+ severity: input.severity ?? "info",
4562
+ message: input.message ?? `${input.type}: ${input.task.title}`,
4563
+ data,
4564
+ dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
4565
+ metadata: {
4566
+ package: "@hasna/todos",
4567
+ task_id: input.task.id,
4568
+ project_id: input.task.project_id,
4569
+ task_list_id: input.task.task_list_id
4570
+ }
4571
+ }, { deliver: true, dedupe: true });
4572
+ }
4573
+ function emitSharedTaskEventQuiet(input) {
4574
+ emitSharedTaskEvent(input).catch(() => {
4575
+ return;
4576
+ });
4577
+ }
4578
+ var SOURCE = "todos";
4579
+ var init_shared_events = __esm(() => {
4580
+ init_dist();
4581
+ });
4582
+
4016
4583
  // src/lib/secret-redaction.ts
4017
4584
  function isAllowlisted(text, match, allowlist) {
4018
4585
  const context = text.slice(Math.max(0, text.indexOf(match) - 20), text.indexOf(match) + match.length + 20);
@@ -4480,7 +5047,7 @@ async function deliverWebhook(wh, event, body, attempt, db) {
4480
5047
  activeDeliveries--;
4481
5048
  }
4482
5049
  }
4483
- async function dispatchWebhook(event, payload, db) {
5050
+ async function dispatchWebhook2(event, payload, db) {
4484
5051
  const d = db || getDatabase();
4485
5052
  const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
4486
5053
  const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
@@ -4597,7 +5164,10 @@ function createTask(input, db) {
4597
5164
  insertTaskTags(id, tags, d);
4598
5165
  }
4599
5166
  const task = getTask(id, d);
4600
- 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(() => {});
5167
+ const payload = taskEventData(task);
5168
+ dispatchWebhook2("task.created", payload, d).catch(() => {});
5169
+ emitLocalEventHooksQuiet({ type: "task.created", payload });
5170
+ emitSharedTaskEventQuiet({ type: "task.created", task });
4601
5171
  return task;
4602
5172
  }
4603
5173
  function getTask(id, db) {
@@ -4941,18 +5511,7 @@ function updateTask(id, input, db) {
4941
5511
  logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
4942
5512
  if (input.approved_by !== undefined)
4943
5513
  logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
4944
- if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
4945
- dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
4946
- emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id, assigned_to: input.assigned_to, title: task.title } });
4947
- }
4948
- if (input.status !== undefined && input.status !== task.status) {
4949
- dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
4950
- emitLocalEventHooksQuiet({ type: "task.status_changed", payload: { id, old_status: task.status, new_status: input.status, title: task.title } });
4951
- }
4952
- if (input.approved_by !== undefined) {
4953
- emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
4954
- }
4955
- return {
5514
+ const updatedTask = {
4956
5515
  ...task,
4957
5516
  ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
4958
5517
  tags: input.tags ?? task.tags,
@@ -4970,6 +5529,22 @@ function updateTask(id, input, db) {
4970
5529
  approved_by: input.approved_by ?? task.approved_by,
4971
5530
  approved_at: input.approved_by ? timestamp : task.approved_at
4972
5531
  };
5532
+ if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
5533
+ const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
5534
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
5535
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
5536
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
5537
+ }
5538
+ if (input.status !== undefined && input.status !== task.status) {
5539
+ const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
5540
+ dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
5541
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
5542
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
5543
+ }
5544
+ if (input.approved_by !== undefined) {
5545
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
5546
+ }
5547
+ return updatedTask;
4973
5548
  }
4974
5549
  function deleteTask(id, db) {
4975
5550
  const d = db || getDatabase();
@@ -4981,6 +5556,7 @@ var init_task_crud = __esm(() => {
4981
5556
  init_database();
4982
5557
  init_completion_guard();
4983
5558
  init_event_hooks();
5559
+ init_shared_events();
4984
5560
  init_audit();
4985
5561
  init_webhooks();
4986
5562
  init_checklists();
@@ -5736,9 +6312,12 @@ function startTask(id, agentId, db) {
5736
6312
  throw new Error(`Task ${id} could not be started because it changed during claim`);
5737
6313
  }
5738
6314
  logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
5739
- dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
5740
- emitLocalEventHooksQuiet({ type: "task.started", payload: { id, agent_id: agentId, title: task.title } });
5741
- 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 };
6315
+ 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 };
6316
+ const payload = taskEventData(startedTask, { agent_id: agentId });
6317
+ dispatchWebhook2("task.started", payload, d).catch(() => {});
6318
+ emitLocalEventHooksQuiet({ type: "task.started", payload });
6319
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
6320
+ return startedTask;
5742
6321
  }
5743
6322
  function completeTask(id, agentId, db, options) {
5744
6323
  const d = db || getDatabase();
@@ -5774,8 +6353,21 @@ function completeTask(id, agentId, db, options) {
5774
6353
  });
5775
6354
  tx();
5776
6355
  logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
5777
- dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
5778
- emitLocalEventHooksQuiet({ type: "task.completed", payload: { id, agent_id: agentId, title: task.title, completed_at: timestamp } });
6356
+ const completedTaskForEvent = {
6357
+ ...task,
6358
+ status: "completed",
6359
+ locked_by: null,
6360
+ locked_at: null,
6361
+ completed_at: timestamp,
6362
+ confidence,
6363
+ version: task.version + 1,
6364
+ updated_at: timestamp,
6365
+ metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
6366
+ };
6367
+ const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
6368
+ dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
6369
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
6370
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
5779
6371
  let spawnedTask = null;
5780
6372
  if (task.recurrence_rule && !options?.skip_recurrence) {
5781
6373
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -5816,8 +6408,12 @@ function completeTask(id, agentId, db, options) {
5816
6408
  if (unblockedDeps.length > 0) {
5817
6409
  meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
5818
6410
  for (const dep of unblockedDeps) {
5819
- dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
5820
- emitLocalEventHooksQuiet({ type: "task.unblocked", payload: { id: dep.id, unblocked_by: id, title: dep.title } });
6411
+ const depTask = getTask(dep.id, d);
6412
+ const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
6413
+ dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
6414
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
6415
+ if (depTask)
6416
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
5821
6417
  }
5822
6418
  }
5823
6419
  return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
@@ -6003,9 +6599,6 @@ function failTask(id, agentId, reason, options, db) {
6003
6599
  const timestamp = now();
6004
6600
  d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
6005
6601
  WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
6006
- logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
6007
- dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
6008
- emitLocalEventHooksQuiet({ type: "task.failed", payload: { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title } });
6009
6602
  const failedTask = {
6010
6603
  ...task,
6011
6604
  status: "failed",
@@ -6015,6 +6608,11 @@ function failTask(id, agentId, reason, options, db) {
6015
6608
  version: task.version + 1,
6016
6609
  updated_at: timestamp
6017
6610
  };
6611
+ logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
6612
+ const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
6613
+ dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
6614
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
6615
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
6018
6616
  let retryTask;
6019
6617
  if (options?.retry) {
6020
6618
  const retryCount = (task.retry_count || 0) + 1;
@@ -6089,9 +6687,12 @@ function stealTask(agentId, opts, db) {
6089
6687
  return null;
6090
6688
  logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
6091
6689
  logTaskChange(target.id, "steal", "locked_by", target.locked_by, agentId, agentId, d);
6092
- dispatchWebhook("task.assigned", { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to }, d).catch(() => {});
6093
- emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id: target.id, agent_id: agentId, title: target.title, stolen_from: target.assigned_to } });
6094
- return { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
6690
+ const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
6691
+ const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
6692
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
6693
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
6694
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
6695
+ return stolenTask;
6095
6696
  }
6096
6697
  function claimOrSteal(agentId, filters, db) {
6097
6698
  const d = db || getDatabase();
@@ -6139,6 +6740,7 @@ var init_task_lifecycle = __esm(() => {
6139
6740
  init_database();
6140
6741
  init_completion_guard();
6141
6742
  init_event_hooks();
6743
+ init_shared_events();
6142
6744
  init_audit();
6143
6745
  init_recurrence();
6144
6746
  init_webhooks();
@@ -6826,7 +7428,7 @@ function getTaskWatchers(taskId, db) {
6826
7428
  }
6827
7429
  function notifyWatchers(taskId, event, data, db) {
6828
7430
  const watchers = getTaskWatchers(taskId, db);
6829
- dispatchWebhook(`task.watcher.${event}`, { task_id: taskId, watchers: watchers.map((w) => w.agent_id), ...data }, db).catch(() => {});
7431
+ dispatchWebhook2(`task.watcher.${event}`, { task_id: taskId, watchers: watchers.map((w) => w.agent_id), ...data }, db).catch(() => {});
6830
7432
  }
6831
7433
  function logCost(taskId, tokens, usd, db) {
6832
7434
  const d = db || getDatabase();
@@ -7320,8 +7922,8 @@ var init_boards = __esm(() => {
7320
7922
 
7321
7923
  // src/lib/artifact-store.ts
7322
7924
  import { createHash as createHash3 } from "crypto";
7323
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
7324
- import { basename, dirname as dirname5, join as join5, resolve as resolve6 } from "path";
7925
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
7926
+ import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
7325
7927
  import { tmpdir } from "os";
7326
7928
  function isInMemoryDb2(path) {
7327
7929
  return path === ":memory:" || path.startsWith("file::memory:");
@@ -7333,15 +7935,15 @@ function artifactStoreRoot() {
7333
7935
  return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
7334
7936
  const dbPath = getDatabasePath();
7335
7937
  if (isInMemoryDb2(dbPath))
7336
- return join5(tmpdir(), "hasna-todos-artifacts");
7337
- return join5(dirname5(resolve6(dbPath)), "artifacts");
7938
+ return join6(tmpdir(), "hasna-todos-artifacts");
7939
+ return join6(dirname5(resolve6(dbPath)), "artifacts");
7338
7940
  }
7339
7941
  function artifactStorePath(relativePath) {
7340
7942
  const normalized = relativePath.replace(/\\/g, "/");
7341
7943
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
7342
7944
  throw new Error("Invalid artifact store path");
7343
7945
  }
7344
- return join5(artifactStoreRoot(), normalized);
7946
+ return join6(artifactStoreRoot(), normalized);
7345
7947
  }
7346
7948
  function sha256(buffer) {
7347
7949
  return createHash3("sha256").update(buffer).digest("hex");
@@ -7382,7 +7984,7 @@ function mediaTypeFor(path, textLike) {
7382
7984
  }
7383
7985
  function storeArtifactContent(input) {
7384
7986
  const sourcePath = resolve6(input.path);
7385
- if (!existsSync6(sourcePath))
7987
+ if (!existsSync7(sourcePath))
7386
7988
  return null;
7387
7989
  const sourceStat = statSync2(sourcePath);
7388
7990
  if (!sourceStat.isFile())
@@ -7399,9 +8001,9 @@ function storeArtifactContent(input) {
7399
8001
  redactionStatus = "redacted";
7400
8002
  }
7401
8003
  const storedSha = sha256(storedBuffer);
7402
- const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
8004
+ const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
7403
8005
  const destination = artifactStorePath(relativePath);
7404
- if (!existsSync6(destination)) {
8006
+ if (!existsSync7(destination)) {
7405
8007
  mkdirSync4(dirname5(destination), { recursive: true });
7406
8008
  writeFileSync2(destination, storedBuffer);
7407
8009
  }
@@ -7461,7 +8063,7 @@ function verifyStoredArtifact(input) {
7461
8063
  };
7462
8064
  }
7463
8065
  const storedPath = artifactStorePath(store.relative_path);
7464
- if (!existsSync6(storedPath)) {
8066
+ if (!existsSync7(storedPath)) {
7465
8067
  return {
7466
8068
  id: input.id,
7467
8069
  path: input.path,
@@ -9488,8 +10090,8 @@ var exports_doctor = {};
9488
10090
  __export(exports_doctor, {
9489
10091
  runTodosDoctor: () => runTodosDoctor
9490
10092
  });
9491
- import { chmodSync, copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
9492
- import { basename as basename2, dirname as dirname6, join as join6 } from "path";
10093
+ import { chmodSync, copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
10094
+ import { basename as basename2, dirname as dirname6, join as join7 } from "path";
9493
10095
  function tableExists(db, table) {
9494
10096
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
9495
10097
  }
@@ -9583,7 +10185,7 @@ function findMissingProjectRoots(db) {
9583
10185
  continue;
9584
10186
  if (!row.path.startsWith("/"))
9585
10187
  continue;
9586
- if (!existsSync7(row.path))
10188
+ if (!existsSync8(row.path))
9587
10189
  missing++;
9588
10190
  }
9589
10191
  return missing;
@@ -9643,16 +10245,16 @@ function databasePermissionsAreUnsafe(dbPath) {
9643
10245
  function createBackup(dbPath) {
9644
10246
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
9645
10247
  return;
9646
- if (!existsSync7(dbPath))
10248
+ if (!existsSync8(dbPath))
9647
10249
  return;
9648
10250
  const stamp = now().replace(/[:.]/g, "-");
9649
- const backupDir = join6(dirname6(dbPath), `${basename2(dbPath)}.backup-${stamp}`);
10251
+ const backupDir = join7(dirname6(dbPath), `${basename2(dbPath)}.backup-${stamp}`);
9650
10252
  const files = [];
9651
10253
  mkdirSync5(backupDir, { recursive: true });
9652
10254
  for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
9653
- if (!existsSync7(source))
10255
+ if (!existsSync8(source))
9654
10256
  continue;
9655
- const target = join6(backupDir, basename2(source));
10257
+ const target = join7(backupDir, basename2(source));
9656
10258
  copyFileSync(source, target);
9657
10259
  files.push(target);
9658
10260
  }
@@ -9878,7 +10480,7 @@ var init_doctor = __esm(() => {
9878
10480
  });
9879
10481
 
9880
10482
  // src/server/routes.ts
9881
- import { join as join7, resolve as resolve7, sep } from "path";
10483
+ import { join as join8, resolve as resolve7, sep } from "path";
9882
10484
  function parseFieldsParam(url) {
9883
10485
  const fieldsParam = url.searchParams.get("fields");
9884
10486
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -10582,7 +11184,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
10582
11184
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
10583
11185
  return null;
10584
11186
  if (path !== "/") {
10585
- const filePath = join7(ctx.dashboardDir, path);
11187
+ const filePath = join8(ctx.dashboardDir, path);
10586
11188
  const resolvedFile = resolve7(filePath);
10587
11189
  const resolvedBase = resolve7(ctx.dashboardDir);
10588
11190
  if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
@@ -10592,7 +11194,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
10592
11194
  if (res2)
10593
11195
  return res2;
10594
11196
  }
10595
- const indexPath = join7(ctx.dashboardDir, "index.html");
11197
+ const indexPath = join8(ctx.dashboardDir, "index.html");
10596
11198
  const res = serveStaticFile2(indexPath);
10597
11199
  if (res)
10598
11200
  return res;
@@ -13984,7 +14586,7 @@ class JSONSchemaGenerator {
13984
14586
  if (val === undefined) {
13985
14587
  if (this.unrepresentable === "throw") {
13986
14588
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
13987
- }
14589
+ } else {}
13988
14590
  } else if (typeof val === "bigint") {
13989
14591
  if (this.unrepresentable === "throw") {
13990
14592
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -31037,17 +31639,73 @@ var init_dispatch_formatter = __esm(() => {
31037
31639
  });
31038
31640
 
31039
31641
  // src/lib/tmux.ts
31642
+ async function inspectTmuxPane(target) {
31643
+ const proc = Bun.spawn([
31644
+ "tmux",
31645
+ "display-message",
31646
+ "-p",
31647
+ "-t",
31648
+ target,
31649
+ "#{pane_id}\t#{pane_current_command}\t#{pane_dead}\t#{pane_input_off}\t#{pane_in_mode}"
31650
+ ], {
31651
+ stdout: "pipe",
31652
+ stderr: "pipe"
31653
+ });
31654
+ const exitCode = await proc.exited;
31655
+ if (exitCode !== 0) {
31656
+ const stderr = await new Response(proc.stderr).text();
31657
+ throw new Error(`tmux target "${target}" not found: ${stderr.trim() || "unknown error"}`);
31658
+ }
31659
+ const stdout = (await new Response(proc.stdout).text()).trim();
31660
+ const [paneId, currentCommand = "", paneDead = "0", inputOff = "0", inMode = "0"] = stdout.split("\t");
31661
+ if (!paneId) {
31662
+ throw new Error(`tmux target "${target}" did not resolve to a pane`);
31663
+ }
31664
+ return {
31665
+ target,
31666
+ paneId,
31667
+ currentCommand,
31668
+ paneDead: paneDead === "1",
31669
+ inputOff: inputOff === "1",
31670
+ inMode: inMode === "1"
31671
+ };
31672
+ }
31673
+ function tmuxPaneBusyStatus(pane) {
31674
+ if (pane.paneDead) {
31675
+ return { busy: true, reason: "pane is dead" };
31676
+ }
31677
+ if (pane.inputOff) {
31678
+ return { busy: true, reason: "pane input is disabled" };
31679
+ }
31680
+ if (pane.inMode) {
31681
+ return { busy: true, reason: "pane is in copy or alternate mode" };
31682
+ }
31683
+ const currentCommand = pane.currentCommand.trim();
31684
+ if (currentCommand && !IDLE_TMUX_COMMANDS.has(currentCommand)) {
31685
+ return { busy: true, reason: `pane is running ${currentCommand}` };
31686
+ }
31687
+ return { busy: false, reason: null };
31688
+ }
31040
31689
  function calculateDelay(message) {
31041
31690
  const len = message.length;
31042
31691
  const extra = Math.floor(len / 100 * 40);
31043
31692
  return Math.min(DELAY_MIN + extra, DELAY_MAX);
31044
31693
  }
31045
- async function sendToTmux(target, message, delayMs, dryRun = false) {
31694
+ async function sendToTmux(target, message, delayMs, options = false) {
31695
+ const opts = typeof options === "boolean" ? { dryRun: options } : options;
31696
+ const dryRun = opts.dryRun ?? false;
31046
31697
  if (dryRun) {
31047
31698
  console.log(`[dry-run] sendToTmux target=${target} delay=${delayMs}ms`);
31048
31699
  console.log(`[dry-run] message: ${message.slice(0, 200)}`);
31049
31700
  return;
31050
31701
  }
31702
+ if (!opts.confirmBusy) {
31703
+ const pane = await inspectTmuxPane(target);
31704
+ const status = tmuxPaneBusyStatus(pane);
31705
+ if (status.busy) {
31706
+ throw new Error(`tmux target "${target}" appears busy (${status.reason}). Re-run with --confirm-busy to send anyway.`);
31707
+ }
31708
+ }
31051
31709
  const sendProc = Bun.spawn(["tmux", "send-keys", "-t", target, message, ""], {
31052
31710
  stdout: "pipe",
31053
31711
  stderr: "pipe"
@@ -31068,7 +31726,21 @@ async function sendToTmux(target, message, delayMs, dryRun = false) {
31068
31726
  throw new Error(`tmux send-keys Enter failed for target "${target}": ${stderr.trim()}`);
31069
31727
  }
31070
31728
  }
31071
- var DELAY_MIN = 3000, DELAY_MAX = 5000;
31729
+ var DELAY_MIN = 3000, DELAY_MAX = 5000, IDLE_TMUX_COMMANDS;
31730
+ var init_tmux = __esm(() => {
31731
+ IDLE_TMUX_COMMANDS = new Set([
31732
+ "bash",
31733
+ "dash",
31734
+ "elvish",
31735
+ "fish",
31736
+ "ksh",
31737
+ "nu",
31738
+ "pwsh",
31739
+ "sh",
31740
+ "tmux",
31741
+ "zsh"
31742
+ ]);
31743
+ });
31072
31744
 
31073
31745
  // src/lib/dispatch.ts
31074
31746
  var exports_dispatch = {};
@@ -31086,7 +31758,10 @@ async function executeDispatch(dispatch, opts = {}, db) {
31086
31758
  }
31087
31759
  const delayMs = dispatch.delay_ms ?? calculateDelay(message);
31088
31760
  try {
31089
- await sendToTmux(dispatch.target_window, message, delayMs, opts.dryRun ?? false);
31761
+ await sendToTmux(dispatch.target_window, message, delayMs, {
31762
+ dryRun: opts.dryRun ?? false,
31763
+ confirmBusy: opts.confirmBusy ?? false
31764
+ });
31090
31765
  createDispatchLog({
31091
31766
  dispatch_id: dispatch.id,
31092
31767
  target_window: dispatch.target_window,
@@ -31145,6 +31820,7 @@ var init_dispatch = __esm(() => {
31145
31820
  init_tasks();
31146
31821
  init_database();
31147
31822
  init_dispatch_formatter();
31823
+ init_tmux();
31148
31824
  });
31149
31825
 
31150
31826
  // src/db/task-lists.ts
@@ -31239,8 +31915,9 @@ function registerDispatchTools(server, { shouldRegisterTool, resolveId, formatEr
31239
31915
  target: exports_external.string().describe("tmux target \u2014 window name, session:window, or session:window.pane"),
31240
31916
  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."),
31241
31917
  scheduled_at: exports_external.string().optional().describe("ISO datetime to schedule the dispatch for. Fires immediately if omitted."),
31918
+ confirm_busy: exports_external.boolean().optional().describe("Send even if the target tmux pane appears busy. Default: false."),
31242
31919
  dry_run: exports_external.boolean().optional().describe("Preview the formatted message without sending. Default: false.")
31243
- }, async ({ task_ids, target, delay_ms, scheduled_at, dry_run }) => {
31920
+ }, async ({ task_ids, target, delay_ms, scheduled_at, confirm_busy, dry_run }) => {
31244
31921
  try {
31245
31922
  const db = getDatabase();
31246
31923
  const resolvedIds = task_ids.map((id) => resolveId(id));
@@ -31256,7 +31933,7 @@ ${message}` }]
31256
31933
  }
31257
31934
  const dispatch = createDispatch({ task_ids: resolvedIds, target_window: target, message, delay_ms: effectiveDelay, scheduled_at }, db);
31258
31935
  if (!scheduled_at)
31259
- await executeDispatch(dispatch, {}, db);
31936
+ await executeDispatch(dispatch, { confirmBusy: confirm_busy ?? false }, db);
31260
31937
  return {
31261
31938
  content: [{
31262
31939
  type: "text",
@@ -31282,8 +31959,9 @@ ${message}`
31282
31959
  filter_status: exports_external.array(exports_external.enum(["pending", "in_progress", "completed", "failed", "cancelled"])).optional().describe("Only include tasks with these statuses. Default: pending."),
31283
31960
  delay_ms: exports_external.number().optional().describe("Delay in ms between sending and Enter. Auto-calculated if omitted."),
31284
31961
  scheduled_at: exports_external.string().optional().describe("ISO datetime to schedule. Fires immediately if omitted."),
31962
+ confirm_busy: exports_external.boolean().optional().describe("Send even if the target tmux pane appears busy. Default: false."),
31285
31963
  dry_run: exports_external.boolean().optional().describe("Preview without sending. Default: false.")
31286
- }, async ({ task_list_id, target, filter_status, delay_ms, scheduled_at, dry_run }) => {
31964
+ }, async ({ task_list_id, target, filter_status, delay_ms, scheduled_at, confirm_busy, dry_run }) => {
31287
31965
  try {
31288
31966
  const db = getDatabase();
31289
31967
  const resolvedListId = resolveId(task_list_id, "task_lists");
@@ -31303,7 +31981,7 @@ ${message}` }]
31303
31981
  }
31304
31982
  const dispatch = createDispatch({ title: `Task list: ${taskList.name}`, task_list_id: resolvedListId, target_window: target, message, delay_ms: effectiveDelay, scheduled_at }, db);
31305
31983
  if (!scheduled_at)
31306
- await executeDispatch(dispatch, {}, db);
31984
+ await executeDispatch(dispatch, { confirmBusy: confirm_busy ?? false }, db);
31307
31985
  return {
31308
31986
  content: [{
31309
31987
  type: "text",
@@ -31330,15 +32008,16 @@ ${message}`
31330
32008
  task_list_id: exports_external.string().optional().describe("Task list ID to dispatch (use this or task_ids)"),
31331
32009
  stagger_ms: exports_external.number().optional().describe("Delay between each window dispatch. Default: 500ms."),
31332
32010
  delay_ms: exports_external.number().optional().describe("Delay between message send and Enter. Auto-calculated if omitted."),
32011
+ confirm_busy: exports_external.boolean().optional().describe("Send even if target tmux panes appear busy. Default: false."),
31333
32012
  dry_run: exports_external.boolean().optional().describe("Preview without sending. Default: false.")
31334
- }, async ({ targets, task_ids, task_list_id, stagger_ms, delay_ms, dry_run }) => {
32013
+ }, async ({ targets, task_ids, task_list_id, stagger_ms, delay_ms, confirm_busy, dry_run }) => {
31335
32014
  try {
31336
32015
  if (!task_ids && !task_list_id)
31337
32016
  throw new Error("Either task_ids or task_list_id is required");
31338
32017
  const db = getDatabase();
31339
32018
  const resolvedTaskIds = task_ids ? task_ids.map((id) => resolveId(id)) : undefined;
31340
32019
  const resolvedListId = task_list_id ? resolveId(task_list_id, "task_lists") : undefined;
31341
- const dispatches = await dispatchToMultiple({ targets, task_ids: resolvedTaskIds, task_list_id: resolvedListId, delay_ms, stagger_ms }, { dryRun: dry_run }, db);
32020
+ const dispatches = await dispatchToMultiple({ targets, task_ids: resolvedTaskIds, task_list_id: resolvedListId, delay_ms, stagger_ms }, { dryRun: dry_run, confirmBusy: confirm_busy ?? false }, db);
31342
32021
  const lines = dispatches.map((d) => `${d.target_window}: ${d.id} [${d.status}]`);
31343
32022
  return { content: [{ type: "text", text: `Dispatched to ${dispatches.length} target(s):
31344
32023
  ${lines.join(`
@@ -31383,11 +32062,12 @@ ${lines.join(`
31383
32062
  if (shouldRegisterTool("run_due_dispatches")) {
31384
32063
  server.tool("run_due_dispatches", "Manually trigger all pending dispatches that are due (scheduled_at <= now). Returns the count fired.", {
31385
32064
  dry_run: exports_external.boolean().optional().describe("Preview without sending. Default: false."),
32065
+ confirm_busy: exports_external.boolean().optional().describe("Send even if target tmux panes appear busy. Default: false."),
31386
32066
  all: exports_external.boolean().optional().describe("Ignore scheduled_at and fire all pending dispatches immediately.")
31387
- }, async ({ dry_run }) => {
32067
+ }, async ({ dry_run, confirm_busy }) => {
31388
32068
  try {
31389
32069
  const { runDueDispatches: runDueDispatches2 } = await Promise.resolve().then(() => (init_dispatch(), exports_dispatch));
31390
- const count = await runDueDispatches2({ dryRun: dry_run });
32070
+ const count = await runDueDispatches2({ dryRun: dry_run, confirmBusy: confirm_busy ?? false });
31391
32071
  return { content: [{ type: "text", text: `Fired ${count} dispatch(es).` }] };
31392
32072
  } catch (e) {
31393
32073
  return { content: [{ type: "text", text: formatError2(e) }], isError: true };
@@ -31400,6 +32080,7 @@ var init_dispatch2 = __esm(() => {
31400
32080
  init_dispatches();
31401
32081
  init_dispatch();
31402
32082
  init_dispatch_formatter();
32083
+ init_tmux();
31403
32084
  init_tasks();
31404
32085
  init_task_lists();
31405
32086
  init_database();
@@ -32207,7 +32888,7 @@ var init_task_crud2 = __esm(() => {
32207
32888
  });
32208
32889
 
32209
32890
  // src/lib/project-bootstrap.ts
32210
- import { existsSync as existsSync8, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
32891
+ import { existsSync as existsSync9, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
32211
32892
  import { basename as basename3, dirname as dirname7, resolve as resolve8 } from "path";
32212
32893
  function safeStat(path) {
32213
32894
  try {
@@ -32226,7 +32907,7 @@ function canonicalPath(input) {
32226
32907
  function findUp(start, marker) {
32227
32908
  let current = canonicalPath(start);
32228
32909
  while (true) {
32229
- if (existsSync8(resolve8(current, marker)))
32910
+ if (existsSync9(resolve8(current, marker)))
32230
32911
  return current;
32231
32912
  const parent = dirname7(current);
32232
32913
  if (parent === current)
@@ -32238,7 +32919,7 @@ function readPackageJson(path) {
32238
32919
  if (!path)
32239
32920
  return null;
32240
32921
  const file = resolve8(path, "package.json");
32241
- if (!existsSync8(file))
32922
+ if (!existsSync9(file))
32242
32923
  return null;
32243
32924
  try {
32244
32925
  const parsed = JSON.parse(readFileSync4(file, "utf-8"));
@@ -32260,7 +32941,7 @@ function workspaceMarker(root, rootPackage) {
32260
32941
  if (rootPackage?.workspaces)
32261
32942
  markers.push("package.json#workspaces");
32262
32943
  for (const marker of ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json", "rush.json", "bun.lock", "bun.lockb"]) {
32263
- if (existsSync8(resolve8(root, marker)))
32944
+ if (existsSync9(resolve8(root, marker)))
32264
32945
  markers.push(marker);
32265
32946
  }
32266
32947
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -32561,7 +33242,7 @@ var init_tags = __esm(() => {
32561
33242
  });
32562
33243
 
32563
33244
  // src/lib/retention-cleanup.ts
32564
- import { existsSync as existsSync9, unlinkSync } from "fs";
33245
+ import { existsSync as existsSync10, unlinkSync } from "fs";
32565
33246
  function normalizeScopes(scopes) {
32566
33247
  if (!scopes || scopes.length === 0)
32567
33248
  return [...ALL_SCOPES];
@@ -32764,7 +33445,7 @@ function applyRetentionCleanup(input, db) {
32764
33445
  for (const artifact of report.candidates.artifact_files) {
32765
33446
  try {
32766
33447
  const path = artifactStorePath(artifact.relative_path);
32767
- if (!existsSync9(path)) {
33448
+ if (!existsSync10(path)) {
32768
33449
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
32769
33450
  continue;
32770
33451
  }
@@ -32791,8 +33472,8 @@ var init_retention_cleanup = __esm(() => {
32791
33472
  });
32792
33473
 
32793
33474
  // src/lib/mention-resolver.ts
32794
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
32795
- import { basename as basename4, isAbsolute, join as join8, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
33475
+ import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
33476
+ import { basename as basename4, isAbsolute, join as join9, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
32796
33477
  function blankResolution(parsed) {
32797
33478
  return {
32798
33479
  input: parsed.input,
@@ -32890,7 +33571,7 @@ function resolveFile(parsed, workspace) {
32890
33571
  return resolution;
32891
33572
  }
32892
33573
  resolution.path = relPath;
32893
- if (!existsSync10(absolutePath)) {
33574
+ if (!existsSync11(absolutePath)) {
32894
33575
  resolution.warnings.push("file does not exist in the local workspace");
32895
33576
  return resolution;
32896
33577
  }
@@ -32923,7 +33604,7 @@ function walkSourceFiles(root, current = root, files = []) {
32923
33604
  if (SKIP_DIRS.has(entry.name))
32924
33605
  continue;
32925
33606
  }
32926
- const absolutePath = join8(current, entry.name);
33607
+ const absolutePath = join9(current, entry.name);
32927
33608
  if (entry.isDirectory()) {
32928
33609
  if (!SKIP_DIRS.has(entry.name))
32929
33610
  walkSourceFiles(root, absolutePath, files);
@@ -34190,8 +34871,8 @@ var init_local_notifications = __esm(() => {
34190
34871
  });
34191
34872
 
34192
34873
  // src/lib/local-encryption.ts
34193
- import { createCipheriv, createDecipheriv, createHash as createHash4, randomBytes as randomBytes2, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
34194
- function now2() {
34874
+ import { createCipheriv, createDecipheriv, createHash as createHash4, randomBytes as randomBytes2, scryptSync, timingSafeEqual as timingSafeEqual3 } from "crypto";
34875
+ function now3() {
34195
34876
  return new Date().toISOString();
34196
34877
  }
34197
34878
  function sha2562(value) {
@@ -34228,7 +34909,7 @@ function upsertEncryptionProfile(input) {
34228
34909
  const name = normalizeProfileName(input.name);
34229
34910
  const config2 = loadConfig();
34230
34911
  const existing = config2.encryption_profiles?.[name];
34231
- const timestamp = now2();
34912
+ const timestamp = now3();
34232
34913
  const profile = {
34233
34914
  name,
34234
34915
  algorithm: "aes-256-gcm",
@@ -34285,7 +34966,7 @@ function encryptString(plaintext, options = {}) {
34285
34966
  return {
34286
34967
  schemaVersion: TODOS_ENCRYPTION_SCHEMA_VERSION,
34287
34968
  kind: TODOS_ENCRYPTED_VALUE_KIND,
34288
- encryptedAt: options.encryptedAt ?? now2(),
34969
+ encryptedAt: options.encryptedAt ?? now3(),
34289
34970
  profile: profile.name,
34290
34971
  key_env: profile.key_env,
34291
34972
  algorithm: "aes-256-gcm",
@@ -34320,7 +35001,7 @@ function decryptString(envelope, env = process.env) {
34320
35001
  ]).toString("utf8");
34321
35002
  const expected = Buffer.from(envelope.plaintext_sha256, "hex");
34322
35003
  const actual = Buffer.from(sha2562(plaintext), "hex");
34323
- if (expected.length !== actual.length || !timingSafeEqual2(expected, actual)) {
35004
+ if (expected.length !== actual.length || !timingSafeEqual3(expected, actual)) {
34324
35005
  throw new EncryptedPayloadError("decrypted payload checksum mismatch");
34325
35006
  }
34326
35007
  return plaintext;
@@ -35843,7 +36524,7 @@ function createRoadmap(input) {
35843
36524
  if (!name)
35844
36525
  throw new Error("Roadmap name is required");
35845
36526
  const store = readStore();
35846
- const now3 = timestamp();
36527
+ const now4 = timestamp();
35847
36528
  const roadmap = {
35848
36529
  id: newId("roadmap"),
35849
36530
  name,
@@ -35854,8 +36535,8 @@ function createRoadmap(input) {
35854
36535
  agent_id: cleanString(input.agent_id),
35855
36536
  release: cleanString(input.release),
35856
36537
  milestone_ids: [],
35857
- created_at: now3,
35858
- updated_at: now3
36538
+ created_at: now4,
36539
+ updated_at: now4
35859
36540
  };
35860
36541
  store.roadmaps[roadmap.id] = roadmap;
35861
36542
  writeStore(store);
@@ -35907,7 +36588,7 @@ function createMilestone(input) {
35907
36588
  const title = input.title.trim();
35908
36589
  if (!title)
35909
36590
  throw new Error("Milestone title is required");
35910
- const now3 = timestamp();
36591
+ const now4 = timestamp();
35911
36592
  const milestone = {
35912
36593
  id: newId("milestone"),
35913
36594
  roadmap_id: roadmapId,
@@ -35922,11 +36603,11 @@ function createMilestone(input) {
35922
36603
  run_ids: cleanList(input.run_ids),
35923
36604
  release: cleanString(input.release ?? roadmap.release ?? undefined),
35924
36605
  tags: cleanList(input.tags),
35925
- created_at: now3,
35926
- updated_at: now3
36606
+ created_at: now4,
36607
+ updated_at: now4
35927
36608
  };
35928
36609
  store.milestones[milestone.id] = milestone;
35929
- store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList([...roadmap.milestone_ids, milestone.id]), updated_at: now3 };
36610
+ store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList([...roadmap.milestone_ids, milestone.id]), updated_at: now4 };
35930
36611
  writeStore(store);
35931
36612
  return milestone;
35932
36613
  }
@@ -35981,7 +36662,7 @@ function upsertReleaseGroup(input) {
35981
36662
  throw new Error("Release group name is required");
35982
36663
  const key = releaseKey(roadmapId, name);
35983
36664
  const existing = store.releases[key];
35984
- const now3 = timestamp();
36665
+ const now4 = timestamp();
35985
36666
  const release = {
35986
36667
  name,
35987
36668
  version: input.version === undefined ? existing?.version ?? null : cleanString(input.version),
@@ -35992,8 +36673,8 @@ function upsertReleaseGroup(input) {
35992
36673
  plan_ids: input.plan_ids === undefined ? existing?.plan_ids ?? [] : cleanList(input.plan_ids),
35993
36674
  run_ids: input.run_ids === undefined ? existing?.run_ids ?? [] : cleanList(input.run_ids),
35994
36675
  notes: input.notes === undefined ? existing?.notes ?? null : cleanString(input.notes),
35995
- created_at: existing?.created_at ?? now3,
35996
- updated_at: now3
36676
+ created_at: existing?.created_at ?? now4,
36677
+ updated_at: now4
35997
36678
  };
35998
36679
  store.releases[key] = release;
35999
36680
  writeStore(store);
@@ -36192,7 +36873,7 @@ function upsertCapacityProfile(input) {
36192
36873
  const store = readStore2();
36193
36874
  const key = profileKey(agentId, projectId);
36194
36875
  const existing = store.profiles[key];
36195
- const now3 = timestamp2();
36876
+ const now4 = timestamp2();
36196
36877
  const profile = {
36197
36878
  id: existing?.id ?? key,
36198
36879
  agent_id: agentId,
@@ -36200,8 +36881,8 @@ function upsertCapacityProfile(input) {
36200
36881
  minutes_per_day: assertMinutes(input.minutes_per_day),
36201
36882
  working_days: normalizeWorkingDays(input.working_days),
36202
36883
  effective_from: cleanString2(input.effective_from),
36203
- created_at: existing?.created_at ?? now3,
36204
- updated_at: now3
36884
+ created_at: existing?.created_at ?? now4,
36885
+ updated_at: now4
36205
36886
  };
36206
36887
  store.profiles[key] = profile;
36207
36888
  writeStore2(store);
@@ -36610,7 +37291,7 @@ var init_audit_ledger = __esm(() => {
36610
37291
 
36611
37292
  // src/lib/release-compatibility.ts
36612
37293
  import { readFileSync as readFileSync6 } from "fs";
36613
- import { join as join9, resolve as resolve11 } from "path";
37294
+ import { join as join10, resolve as resolve11 } from "path";
36614
37295
  import { Database as Database2 } from "bun:sqlite";
36615
37296
  function pass(id, message, details) {
36616
37297
  return { id, status: "passed", message, details };
@@ -36622,7 +37303,7 @@ function warn(id, message, details) {
36622
37303
  return { id, status: "warning", message, details };
36623
37304
  }
36624
37305
  function readPackageJson2(root) {
36625
- return JSON.parse(readFileSync6(join9(root, "package.json"), "utf8"));
37306
+ return JSON.parse(readFileSync6(join10(root, "package.json"), "utf8"));
36626
37307
  }
36627
37308
  function sortedKeys(value) {
36628
37309
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -36891,6 +37572,9 @@ function hasFts(db) {
36891
37572
  function escapeFtsQuery(q) {
36892
37573
  return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
36893
37574
  }
37575
+ function shouldUseFts(q) {
37576
+ return /^[\p{L}\p{N}_\-\s]+$/u.test(q);
37577
+ }
36894
37578
  function searchTasks(options, projectId, taskListId, db) {
36895
37579
  const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
36896
37580
  const d = db || getDatabase();
@@ -36899,7 +37583,8 @@ function searchTasks(options, projectId, taskListId, db) {
36899
37583
  let sql;
36900
37584
  const raw = opts.query?.trim() ?? "";
36901
37585
  const q = raw === "*" ? "" : raw;
36902
- if (hasFts(d) && q) {
37586
+ const useFts = hasFts(d) && q && shouldUseFts(q);
37587
+ if (useFts) {
36903
37588
  const ftsQuery = escapeFtsQuery(q);
36904
37589
  sql = `SELECT t.* FROM tasks t
36905
37590
  INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
@@ -36907,8 +37592,16 @@ function searchTasks(options, projectId, taskListId, db) {
36907
37592
  params.push(ftsQuery);
36908
37593
  } else if (q) {
36909
37594
  const pattern = `%${q}%`;
36910
- 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 ?))`;
36911
- params.push(pattern, pattern, pattern);
37595
+ sql = `SELECT * FROM tasks t WHERE (
37596
+ t.id LIKE ?
37597
+ OR t.short_id LIKE ?
37598
+ OR t.title LIKE ?
37599
+ OR t.description LIKE ?
37600
+ OR t.working_dir LIKE ?
37601
+ OR t.metadata LIKE ?
37602
+ OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?)
37603
+ )`;
37604
+ params.push(pattern, pattern, pattern, pattern, pattern, pattern, pattern);
36912
37605
  } else {
36913
37606
  sql = `SELECT * FROM tasks t WHERE 1=1`;
36914
37607
  }
@@ -36964,7 +37657,7 @@ function searchTasks(options, projectId, taskListId, db) {
36964
37657
  } else if (opts.is_blocked === false) {
36965
37658
  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')";
36966
37659
  }
36967
- if (hasFts(d) && q) {
37660
+ if (useFts) {
36968
37661
  sql += ` ORDER BY bm25(tasks_fts),
36969
37662
  CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
36970
37663
  t.created_at DESC`;
@@ -40059,7 +40752,7 @@ function registerTaskWorkflowTools(server, ctx) {
40059
40752
  const globalRole = n.agent.role ? ` [${n.agent.role}]` : "";
40060
40753
  const lead = n.is_lead ? " \u2605" : "";
40061
40754
  const lastSeen = new Date(n.agent.last_seen_at).getTime();
40062
- const active = now3 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
40755
+ const active = now4 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
40063
40756
  const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${lead}`;
40064
40757
  const children = n.reports.length > 0 ? `
40065
40758
  ` + render(n.reports, indent + 1) : "";
@@ -40072,7 +40765,7 @@ function registerTaskWorkflowTools(server, ctx) {
40072
40765
  if (format === "json") {
40073
40766
  return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
40074
40767
  }
40075
- const now3 = Date.now();
40768
+ const now4 = Date.now();
40076
40769
  const ACTIVE_MS = 30 * 60 * 1000;
40077
40770
  const text = tree.length > 0 ? render(tree) : "No agents in org chart.";
40078
40771
  return { content: [{ type: "text", text }] };
@@ -40268,14 +40961,14 @@ function registerTaskAutoTools(server, ctx) {
40268
40961
  return { content: [{ type: "text", text: "No agent_id provided and no agent focus is active." }], isError: true };
40269
40962
  }
40270
40963
  const assigned = listTasks3({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
40271
- const now3 = Date.now();
40272
- const dueSoonCutoff = now3 + 24 * 60 * 60 * 1000;
40964
+ const now4 = Date.now();
40965
+ const dueSoonCutoff = now4 + 24 * 60 * 60 * 1000;
40273
40966
  const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
40274
40967
  const workload = {
40275
40968
  in_progress: assigned.filter((t) => t.status === "in_progress").length,
40276
40969
  pending: assigned.filter((t) => t.status === "pending").length,
40277
- completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now3 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
40278
- 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,
40970
+ completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now4 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
40971
+ 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,
40279
40972
  blocked: blocked.length
40280
40973
  };
40281
40974
  const lines = [
@@ -40502,7 +41195,7 @@ function limits(input) {
40502
41195
  stale_after_hours: clamp(input.stale_after_hours, DEFAULT_LIMITS.stale_after_hours, 24 * 365)
40503
41196
  };
40504
41197
  }
40505
- function truncate(value, max) {
41198
+ function truncate2(value, max) {
40506
41199
  if (!value)
40507
41200
  return value ?? null;
40508
41201
  const redacted = redactEvidenceText(value);
@@ -40523,9 +41216,9 @@ function acceptanceCriteria(task, maxText) {
40523
41216
  const metadata = task.metadata || {};
40524
41217
  const raw = metadata["acceptance_criteria"] ?? metadata["acceptanceCriteria"] ?? metadata["criteria"];
40525
41218
  if (Array.isArray(raw))
40526
- return raw.map((item) => truncate(String(item), maxText)).filter((item) => Boolean(item));
41219
+ return raw.map((item) => truncate2(String(item), maxText)).filter((item) => Boolean(item));
40527
41220
  if (typeof raw === "string") {
40528
- return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate(line, maxText)).filter((item) => Boolean(item));
41221
+ return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate2(line, maxText)).filter((item) => Boolean(item));
40529
41222
  }
40530
41223
  return [];
40531
41224
  }
@@ -40548,7 +41241,7 @@ function addFile(files, path, source, base) {
40548
41241
  path,
40549
41242
  status: base?.status || "active",
40550
41243
  agent_id: base?.agent_id ?? null,
40551
- note: truncate(base?.note, 240),
41244
+ note: truncate2(base?.note, 240),
40552
41245
  updated_at: base?.updated_at || "",
40553
41246
  sources: [source]
40554
41247
  });
@@ -40606,7 +41299,7 @@ function estimateTokens(value) {
40606
41299
  return Math.max(1, Math.ceil((text || "").length / 4));
40607
41300
  }
40608
41301
  function summarizeStrings(values, maxChars) {
40609
- return truncate(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
41302
+ return truncate2(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
40610
41303
  }
40611
41304
  function summarizeSection(pack, section, maxChars) {
40612
41305
  if (section === "project")
@@ -40797,8 +41490,8 @@ function createAgentContextPack(input, db) {
40797
41490
  ...taskFiles.map((file) => file.updated_at)
40798
41491
  ], task.updated_at);
40799
41492
  const warnings = [];
40800
- const now3 = input.now ? new Date(input.now) : new Date;
40801
- if (Date.parse(task.updated_at) < now3.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
41493
+ const now4 = input.now ? new Date(input.now) : new Date;
41494
+ if (Date.parse(task.updated_at) < now4.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
40802
41495
  warnings.push(`task state is older than ${limit.stale_after_hours} hours`);
40803
41496
  }
40804
41497
  if (comments.length > recentComments.length)
@@ -40813,7 +41506,7 @@ function createAgentContextPack(input, db) {
40813
41506
  id: task.id,
40814
41507
  short_id: task.short_id,
40815
41508
  title: redactEvidenceText(task.title),
40816
- description: truncate(task.description, limit.max_text_chars),
41509
+ description: truncate2(task.description, limit.max_text_chars),
40817
41510
  status: task.status,
40818
41511
  priority: task.priority,
40819
41512
  assigned_to: task.assigned_to,
@@ -40837,7 +41530,7 @@ function createAgentContextPack(input, db) {
40837
41530
  plan: plan ? {
40838
41531
  id: plan.id,
40839
41532
  name: plan.name,
40840
- description: truncate(plan.description, limit.max_text_chars),
41533
+ description: truncate2(plan.description, limit.max_text_chars),
40841
41534
  status: plan.status,
40842
41535
  agent_id: plan.agent_id,
40843
41536
  tasks: planTasks.slice(0, limit.plan_task_limit).map(taskSummary).filter((item) => Boolean(item)),
@@ -40856,7 +41549,7 @@ function createAgentContextPack(input, db) {
40856
41549
  type: comment.type,
40857
41550
  progress_pct: comment.progress_pct,
40858
41551
  created_at: comment.created_at,
40859
- content: truncate(comment.content, limit.max_text_chars) || ""
41552
+ content: truncate2(comment.content, limit.max_text_chars) || ""
40860
41553
  })),
40861
41554
  omitted: Math.max(0, comments.length - recentComments.length)
40862
41555
  },
@@ -40864,7 +41557,7 @@ function createAgentContextPack(input, db) {
40864
41557
  traceability: {
40865
41558
  commits: traceability.commits.map((commit) => ({
40866
41559
  sha: commit.sha,
40867
- message: truncate(commit.message, 240),
41560
+ message: truncate2(commit.message, 240),
40868
41561
  files_changed: commit.files_changed,
40869
41562
  committed_at: commit.committed_at
40870
41563
  })),
@@ -40872,7 +41565,7 @@ function createAgentContextPack(input, db) {
40872
41565
  verifications: verifications.map((verification) => ({
40873
41566
  command: verification.command,
40874
41567
  status: verification.status,
40875
- output_summary: truncate(verification.output_summary, limit.max_text_chars),
41568
+ output_summary: truncate2(verification.output_summary, limit.max_text_chars),
40876
41569
  artifact_path: verification.artifact_path,
40877
41570
  run_at: verification.run_at
40878
41571
  })),
@@ -40883,14 +41576,14 @@ function createAgentContextPack(input, db) {
40883
41576
  id: ledger.run.id,
40884
41577
  title: ledger.run.title,
40885
41578
  status: ledger.run.status,
40886
- summary: truncate(ledger.run.summary, limit.max_text_chars),
41579
+ summary: truncate2(ledger.run.summary, limit.max_text_chars),
40887
41580
  agent_id: ledger.run.agent_id,
40888
41581
  started_at: ledger.run.started_at,
40889
41582
  completed_at: ledger.run.completed_at,
40890
- events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate(event.message, 500), created_at: event.created_at })),
40891
- 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 })),
40892
- files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate(file.note, 240) })),
40893
- artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate(artifact.description, 240), sha256: artifact.sha256 }))
41583
+ events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate2(event.message, 500), created_at: event.created_at })),
41584
+ 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 })),
41585
+ files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate2(file.note, 240) })),
41586
+ artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate2(artifact.description, 240), sha256: artifact.sha256 }))
40894
41587
  })),
40895
41588
  omitted: Math.max(0, runs.length - selectedRuns.length)
40896
41589
  },
@@ -40995,7 +41688,7 @@ function renderAgentContextPackCompactMarkdown(pack) {
40995
41688
  const lines = [
40996
41689
  `# Context: ${pack.task.title}`,
40997
41690
  `${pack.task.status} | ${pack.task.priority} | ${pack.task.short_id || pack.task.id.slice(0, 8)}`,
40998
- pack.task.description ? truncate(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
41691
+ pack.task.description ? truncate2(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
40999
41692
  "",
41000
41693
  "## Must Know",
41001
41694
  bullet([
@@ -56983,7 +57676,7 @@ var require_to_json_schema = __commonJS((exports) => {
56983
57676
  if (val === undefined) {
56984
57677
  if (this.unrepresentable === "throw") {
56985
57678
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
56986
- }
57679
+ } else {}
56987
57680
  } else if (typeof val === "bigint") {
56988
57681
  if (this.unrepresentable === "throw") {
56989
57682
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -66019,7 +66712,7 @@ var init_agent_run_dispatcher = __esm(() => {
66019
66712
  });
66020
66713
 
66021
66714
  // src/lib/verification-providers.ts
66022
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
66715
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
66023
66716
  function normalizeName5(name) {
66024
66717
  const normalized = name.trim().toLowerCase();
66025
66718
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -66171,7 +66864,7 @@ Timed out after ${provider.timeout_ms}ms`);
66171
66864
  };
66172
66865
  }
66173
66866
  function runCiLogProvider(input) {
66174
- const text = input.log_text ?? (input.log_path && existsSync11(input.log_path) ? readFileSync7(input.log_path, "utf-8") : "");
66867
+ const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync7(input.log_path, "utf-8") : "");
66175
66868
  return {
66176
66869
  status: classifyLog(text),
66177
66870
  attempts: 1,
@@ -66183,7 +66876,7 @@ function runBrowserProvider(input) {
66183
66876
  if (!input.artifact_path) {
66184
66877
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
66185
66878
  }
66186
- if (!existsSync11(input.artifact_path)) {
66879
+ if (!existsSync12(input.artifact_path)) {
66187
66880
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
66188
66881
  }
66189
66882
  return {
@@ -67973,12 +68666,12 @@ function summarizeTask(task) {
67973
68666
  };
67974
68667
  }
67975
68668
  function overdueTasks(tasks, nowIso) {
67976
- const now3 = Date.parse(nowIso);
68669
+ const now4 = Date.parse(nowIso);
67977
68670
  return tasks.filter((task) => {
67978
68671
  if (isTerminal2(task) || !task.due_at)
67979
68672
  return false;
67980
68673
  const due = Date.parse(task.due_at);
67981
- return Number.isFinite(due) && due < now3;
68674
+ return Number.isFinite(due) && due < now4;
67982
68675
  });
67983
68676
  }
67984
68677
  function isReady(task, db) {
@@ -70403,8 +71096,8 @@ __export(exports_local_extensions, {
70403
71096
  discoverLocalExtensions: () => discoverLocalExtensions
70404
71097
  });
70405
71098
  import { createHash as createHash9, createVerify } from "crypto";
70406
- import { existsSync as existsSync12, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
70407
- import { basename as basename5, join as join10, resolve as resolve13 } from "path";
71099
+ import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
71100
+ import { basename as basename5, join as join11, resolve as resolve13 } from "path";
70408
71101
  function isObject3(value) {
70409
71102
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
70410
71103
  }
@@ -70663,10 +71356,10 @@ function verifyExtensionSignature(input) {
70663
71356
  }
70664
71357
  function inspectExtensionSource(source3) {
70665
71358
  const resolved = resolve13(source3);
70666
- if (!existsSync12(resolved))
71359
+ if (!existsSync13(resolved))
70667
71360
  throw new Error(`extension source not found: ${source3}`);
70668
71361
  const stat = statSync6(resolved);
70669
- const manifestPath = stat.isDirectory() ? [join10(resolved, "todos.extension.json"), join10(resolved, "extension.json")].find(existsSync12) : resolved;
71362
+ const manifestPath = stat.isDirectory() ? [join11(resolved, "todos.extension.json"), join11(resolved, "extension.json")].find(existsSync13) : resolved;
70670
71363
  if (!manifestPath)
70671
71364
  throw new Error(`extension directory ${source3} is missing todos.extension.json`);
70672
71365
  const raw = readFileSync9(manifestPath);
@@ -70762,20 +71455,20 @@ function projectExtensionSources(projectPath) {
70762
71455
  return [];
70763
71456
  const root = resolve13(projectPath);
70764
71457
  const candidates = [
70765
- join10(root, "todos.extension.json"),
70766
- join10(root, ".todos", "todos.extension.json")
71458
+ join11(root, "todos.extension.json"),
71459
+ join11(root, ".todos", "todos.extension.json")
70767
71460
  ];
70768
- const extensionDir = join10(root, ".todos", "extensions");
70769
- if (existsSync12(extensionDir)) {
71461
+ const extensionDir = join11(root, ".todos", "extensions");
71462
+ if (existsSync13(extensionDir)) {
70770
71463
  for (const entry of readdirSync3(extensionDir)) {
70771
71464
  if (entry.startsWith("."))
70772
71465
  continue;
70773
- const full = join10(extensionDir, entry);
71466
+ const full = join11(extensionDir, entry);
70774
71467
  if (statSync6(full).isDirectory() || entry.endsWith(".json"))
70775
71468
  candidates.push(full);
70776
71469
  }
70777
71470
  }
70778
- return candidates.filter(existsSync12);
71471
+ return candidates.filter(existsSync13);
70779
71472
  }
70780
71473
  function discoverLocalExtensions(options = {}) {
70781
71474
  const config2 = loadConfig();
@@ -74297,7 +74990,7 @@ ${lines.join(`
74297
74990
  const projectRoles = n.project_roles.length > 0 ? ` <${n.project_roles.join(", ")}>` : "";
74298
74991
  const lead = n.is_project_lead ? " \u2605" : "";
74299
74992
  const lastSeen = new Date(n.agent.last_seen_at).getTime();
74300
- const active = now3 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
74993
+ const active = now4 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
74301
74994
  const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${projectRoles}${lead}`;
74302
74995
  const children = n.reports.length > 0 ? `
74303
74996
  ` + render(n.reports, indent + 1) : "";
@@ -74311,7 +75004,7 @@ ${lines.join(`
74311
75004
  if (format === "json") {
74312
75005
  return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
74313
75006
  }
74314
- const now3 = Date.now();
75007
+ const now4 = Date.now();
74315
75008
  const ACTIVE_MS = 30 * 60 * 1000;
74316
75009
  const text2 = tree.length > 0 ? render(tree) : "No agents in this project's org chart.";
74317
75010
  return { content: [{ type: "text", text: text2 }] };
@@ -74861,10 +75554,10 @@ ${lines.join(`
74861
75554
  });
74862
75555
  }
74863
75556
  if (shouldRegisterTool("get_idle_focus_prompts")) {
74864
- 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 }) => {
75557
+ 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 }) => {
74865
75558
  try {
74866
75559
  const { getIdleFocusSessionPrompts: getIdleFocusSessionPrompts2 } = (init_tasks(), __toCommonJS(exports_tasks));
74867
- return { content: [{ type: "text", text: JSON.stringify(getIdleFocusSessionPrompts2({ agent_id, now: now3 }), null, 2) }] };
75560
+ return { content: [{ type: "text", text: JSON.stringify(getIdleFocusSessionPrompts2({ agent_id, now: now4 }), null, 2) }] };
74868
75561
  } catch (e) {
74869
75562
  return { content: [{ type: "text", text: formatError2(e) }], isError: true };
74870
75563
  }
@@ -75059,9 +75752,9 @@ __export(exports_extract, {
75059
75752
  buildCodebaseIndex: () => buildCodebaseIndex,
75060
75753
  EXTRACT_TAGS: () => EXTRACT_TAGS
75061
75754
  });
75062
- import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
75755
+ import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
75063
75756
  import { createHash as createHash11 } from "crypto";
75064
- import { relative as relative5, resolve as resolve14, join as join11 } from "path";
75757
+ import { relative as relative5, resolve as resolve14, join as join12 } from "path";
75065
75758
  function stableHash(value) {
75066
75759
  return createHash11("sha256").update(value).digest("hex");
75067
75760
  }
@@ -75070,8 +75763,8 @@ function normalizePathForMatch(value) {
75070
75763
  }
75071
75764
  function readGitignorePatterns(basePath) {
75072
75765
  const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
75073
- const gitignorePath = join11(root, ".gitignore");
75074
- if (!existsSync13(gitignorePath))
75766
+ const gitignorePath = join12(root, ".gitignore");
75767
+ if (!existsSync14(gitignorePath))
75075
75768
  return [];
75076
75769
  try {
75077
75770
  return readFileSync10(gitignorePath, "utf-8").split(`
@@ -75213,7 +75906,7 @@ function buildCodebaseIndex(options) {
75213
75906
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
75214
75907
  const indexed = [];
75215
75908
  for (const file of files) {
75216
- const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
75909
+ const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
75217
75910
  try {
75218
75911
  const source3 = readFileSync10(fullPath, "utf-8");
75219
75912
  const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
@@ -75244,7 +75937,7 @@ function extractTodos(options, db) {
75244
75937
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
75245
75938
  const allComments = [];
75246
75939
  for (const file of files) {
75247
- const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
75940
+ const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
75248
75941
  try {
75249
75942
  const source3 = readFileSync10(fullPath, "utf-8");
75250
75943
  const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
@@ -76137,7 +76830,7 @@ __export(exports_builtin_templates, {
76137
76830
  BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
76138
76831
  });
76139
76832
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
76140
- import { join as join12 } from "path";
76833
+ import { join as join13 } from "path";
76141
76834
  function templateMetadata(template) {
76142
76835
  return {
76143
76836
  source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
@@ -76196,7 +76889,7 @@ function writeBuiltinTemplateFiles(directory) {
76196
76889
  mkdirSync7(directory, { recursive: true });
76197
76890
  const files = [];
76198
76891
  for (const entry of exportBuiltinTemplateFiles()) {
76199
- const path = join12(directory, entry.filename);
76892
+ const path = join13(directory, entry.filename);
76200
76893
  writeFileSync4(path, `${JSON.stringify(entry.template, null, 2)}
76201
76894
  `, "utf-8");
76202
76895
  files.push(path);
@@ -76722,16 +77415,16 @@ __export(exports_environment_snapshots, {
76722
77415
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
76723
77416
  });
76724
77417
  import { createHash as createHash12 } from "crypto";
76725
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
77418
+ import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
76726
77419
  import { hostname as hostname3, platform, arch } from "os";
76727
- import { dirname as dirname9, join as join13, resolve as resolve15 } from "path";
77420
+ import { dirname as dirname9, join as join14, resolve as resolve15 } from "path";
76728
77421
  import { tmpdir as tmpdir2 } from "os";
76729
77422
  function sha2566(value) {
76730
77423
  return createHash12("sha256").update(value).digest("hex");
76731
77424
  }
76732
77425
  function fileRecord(root, relativePath) {
76733
- const path = join13(root, relativePath);
76734
- if (!existsSync14(path))
77426
+ const path = join14(root, relativePath);
77427
+ if (!existsSync15(path))
76735
77428
  return null;
76736
77429
  const stat = statSync8(path);
76737
77430
  if (!stat.isFile())
@@ -76743,7 +77436,7 @@ function manifestRecord(root, relativePath) {
76743
77436
  const base = fileRecord(root, relativePath);
76744
77437
  if (!base)
76745
77438
  return null;
76746
- const parsed = readJsonFile(join13(root, relativePath));
77439
+ const parsed = readJsonFile(join14(root, relativePath));
76747
77440
  if (!parsed)
76748
77441
  return { ...base, redacted: {} };
76749
77442
  const redacted = redactValue({
@@ -76838,8 +77531,8 @@ function commandEnv(env, includeValues) {
76838
77531
  function defaultSnapshotDir() {
76839
77532
  const dbPath = getDatabasePath();
76840
77533
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
76841
- return join13(tmpdir2(), "hasna-todos", "environment-snapshots");
76842
- return join13(dirname9(resolve15(dbPath)), "environment-snapshots");
77534
+ return join14(tmpdir2(), "hasna-todos", "environment-snapshots");
77535
+ return join14(dirname9(resolve15(dbPath)), "environment-snapshots");
76843
77536
  }
76844
77537
  function snapshotWithId(snapshot) {
76845
77538
  const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
@@ -76886,7 +77579,7 @@ function captureEnvironmentSnapshot(input = {}) {
76886
77579
  });
76887
77580
  }
76888
77581
  function writeEnvironmentSnapshot(snapshot, outputPath) {
76889
- const path = outputPath ? resolve15(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
77582
+ const path = outputPath ? resolve15(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
76890
77583
  ensureDir2(dirname9(path));
76891
77584
  writeJsonFile(path, snapshot);
76892
77585
  return path;
@@ -77532,27 +78225,27 @@ __export(exports_serve, {
77532
78225
  SECURITY_HEADERS: () => SECURITY_HEADERS,
77533
78226
  MIME_TYPES: () => MIME_TYPES
77534
78227
  });
77535
- import { existsSync as existsSync15 } from "fs";
77536
- import { join as join14, dirname as dirname10, extname } from "path";
78228
+ import { existsSync as existsSync16 } from "fs";
78229
+ import { join as join15, dirname as dirname10, extname } from "path";
77537
78230
  import { fileURLToPath as fileURLToPath2 } from "url";
77538
78231
  function resolveDashboardDir() {
77539
78232
  const candidates = [];
77540
78233
  try {
77541
78234
  const scriptDir = dirname10(fileURLToPath2(import.meta.url));
77542
- candidates.push(join14(scriptDir, "..", "dashboard", "dist"));
77543
- candidates.push(join14(scriptDir, "..", "..", "dashboard", "dist"));
78235
+ candidates.push(join15(scriptDir, "..", "dashboard", "dist"));
78236
+ candidates.push(join15(scriptDir, "..", "..", "dashboard", "dist"));
77544
78237
  } catch {}
77545
78238
  if (process.argv[1]) {
77546
78239
  const mainDir = dirname10(process.argv[1]);
77547
- candidates.push(join14(mainDir, "..", "dashboard", "dist"));
77548
- candidates.push(join14(mainDir, "..", "..", "dashboard", "dist"));
78240
+ candidates.push(join15(mainDir, "..", "dashboard", "dist"));
78241
+ candidates.push(join15(mainDir, "..", "..", "dashboard", "dist"));
77549
78242
  }
77550
- candidates.push(join14(process.cwd(), "dashboard", "dist"));
78243
+ candidates.push(join15(process.cwd(), "dashboard", "dist"));
77551
78244
  for (const candidate of candidates) {
77552
- if (existsSync15(candidate))
78245
+ if (existsSync16(candidate))
77553
78246
  return candidate;
77554
78247
  }
77555
- return join14(process.cwd(), "dashboard", "dist");
78248
+ return join15(process.cwd(), "dashboard", "dist");
77556
78249
  }
77557
78250
  function getProvidedApiKey(req) {
77558
78251
  const headerKey = req.headers.get("x-api-key");
@@ -77579,15 +78272,15 @@ function checkAuth(req, apiKey) {
77579
78272
  return null;
77580
78273
  }
77581
78274
  function checkRateLimit(ip) {
77582
- const now3 = Date.now();
78275
+ const now4 = Date.now();
77583
78276
  const entry = rateLimitMap.get(ip);
77584
- if (!entry || now3 > entry.resetAt) {
77585
- rateLimitMap.set(ip, { count: 1, resetAt: now3 + RATE_LIMIT_WINDOW_MS });
78277
+ if (!entry || now4 > entry.resetAt) {
78278
+ rateLimitMap.set(ip, { count: 1, resetAt: now4 + RATE_LIMIT_WINDOW_MS });
77586
78279
  return { allowed: true };
77587
78280
  }
77588
78281
  entry.count++;
77589
78282
  if (entry.count > RATE_LIMIT_MAX) {
77590
- return { allowed: false, retryAfter: Math.ceil((entry.resetAt - now3) / 1000) };
78283
+ return { allowed: false, retryAfter: Math.ceil((entry.resetAt - now4) / 1000) };
77591
78284
  }
77592
78285
  return { allowed: true };
77593
78286
  }
@@ -77602,7 +78295,7 @@ function json(data, status = 200, headers) {
77602
78295
  });
77603
78296
  }
77604
78297
  function serveStaticFile(filePath) {
77605
- if (!existsSync15(filePath))
78298
+ if (!existsSync16(filePath))
77606
78299
  return null;
77607
78300
  const ext = extname(filePath);
77608
78301
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -77681,7 +78374,7 @@ data: ${data}
77681
78374
  filteredSseClients.delete(client);
77682
78375
  }
77683
78376
  const dashboardDir = resolveDashboardDir();
77684
- const dashboardExists = existsSync15(dashboardDir);
78377
+ const dashboardExists = existsSync16(dashboardDir);
77685
78378
  if (!dashboardExists) {
77686
78379
  console.error(`
77687
78380
  Dashboard not found at: ${dashboardDir}`);