@hasna/todos 0.11.56 → 0.11.58

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,760 @@ 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.9/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, options = {}) {
4035
+ let body = "";
4036
+ for (let index = 0;index < pattern.length; index += 1) {
4037
+ const char = pattern[index];
4038
+ if (char === "*") {
4039
+ if (pattern[index + 1] === "*") {
4040
+ body += ".*";
4041
+ index += 1;
4042
+ } else {
4043
+ body += options.segmentSafe ? "[^/]*" : ".*";
4044
+ }
4045
+ } else {
4046
+ body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
4047
+ }
4048
+ }
4049
+ return new RegExp(`^${body}$`);
4050
+ }
4051
+ function matchString(value, matcher, options = {}) {
4052
+ if (matcher === undefined)
4053
+ return true;
4054
+ if (value === undefined)
4055
+ return false;
4056
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
4057
+ return matchers.some((item) => wildcardToRegExp(item, options).test(value));
4058
+ }
4059
+ function matchRecord(input, matcher) {
4060
+ if (!matcher)
4061
+ return true;
4062
+ return Object.entries(matcher).every(([path, expected]) => {
4063
+ const actual = getPathValue(input, path);
4064
+ if (typeof expected === "string" || Array.isArray(expected)) {
4065
+ return matchString(actual === undefined ? undefined : String(actual), expected, {
4066
+ segmentSafe: path.endsWith("_path") || path.endsWith(".path")
4067
+ });
4068
+ }
4069
+ return actual === expected;
4070
+ });
4071
+ }
4072
+ function eventMatchesFilter(event, filter) {
4073
+ 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);
4074
+ }
4075
+ function channelMatchesEvent(channel, event) {
4076
+ if (!channel.enabled)
4077
+ return false;
4078
+ if (!channel.filters || channel.filters.length === 0)
4079
+ return true;
4080
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
4081
+ }
4082
+ function getEventsDataDir(override) {
4083
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir(), ".hasna", "events");
4084
+ }
4085
+
4086
+ class JsonEventsStore {
4087
+ dataDir;
4088
+ channelsPath;
4089
+ eventsPath;
4090
+ deliveriesPath;
4091
+ constructor(dataDir = getEventsDataDir()) {
4092
+ this.dataDir = dataDir;
4093
+ this.channelsPath = join5(dataDir, "channels.json");
4094
+ this.eventsPath = join5(dataDir, "events.json");
4095
+ this.deliveriesPath = join5(dataDir, "deliveries.json");
4096
+ }
4097
+ async init() {
4098
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
4099
+ await chmod(this.dataDir, 448).catch(() => {
4100
+ return;
4101
+ });
4102
+ await this.ensureArrayFile(this.channelsPath);
4103
+ await this.ensureArrayFile(this.eventsPath);
4104
+ await this.ensureArrayFile(this.deliveriesPath);
4105
+ }
4106
+ async addChannel(channel) {
4107
+ await this.init();
4108
+ const channels = await this.readJson(this.channelsPath, []);
4109
+ const index = channels.findIndex((item) => item.id === channel.id);
4110
+ if (index >= 0) {
4111
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
4112
+ } else {
4113
+ channels.push(channel);
4114
+ }
4115
+ await this.writeJson(this.channelsPath, channels);
4116
+ return index >= 0 ? channels[index] : channel;
4117
+ }
4118
+ async listChannels() {
4119
+ await this.init();
4120
+ return this.readJson(this.channelsPath, []);
4121
+ }
4122
+ async getChannel(id) {
4123
+ const channels = await this.listChannels();
4124
+ return channels.find((channel) => channel.id === id);
4125
+ }
4126
+ async removeChannel(id) {
4127
+ await this.init();
4128
+ const channels = await this.readJson(this.channelsPath, []);
4129
+ const next = channels.filter((channel) => channel.id !== id);
4130
+ await this.writeJson(this.channelsPath, next);
4131
+ return next.length !== channels.length;
4132
+ }
4133
+ async appendEvent(event) {
4134
+ await this.init();
4135
+ const events = await this.readJson(this.eventsPath, []);
4136
+ events.push(event);
4137
+ await this.writeJson(this.eventsPath, events);
4138
+ return event;
4139
+ }
4140
+ async listEvents() {
4141
+ await this.init();
4142
+ return this.readJson(this.eventsPath, []);
4143
+ }
4144
+ async findEventByIdentity(identity) {
4145
+ const events = await this.listEvents();
4146
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
4147
+ }
4148
+ async appendDelivery(result) {
4149
+ await this.init();
4150
+ const deliveries = await this.readJson(this.deliveriesPath, []);
4151
+ deliveries.push(result);
4152
+ await this.writeJson(this.deliveriesPath, deliveries);
4153
+ return result;
4154
+ }
4155
+ async listDeliveries() {
4156
+ await this.init();
4157
+ return this.readJson(this.deliveriesPath, []);
4158
+ }
4159
+ async exportData() {
4160
+ return {
4161
+ channels: await this.listChannels(),
4162
+ events: await this.listEvents(),
4163
+ deliveries: await this.listDeliveries()
4164
+ };
4165
+ }
4166
+ async ensureArrayFile(path) {
4167
+ if (!existsSync6(path)) {
4168
+ await writeFile(path, `[]
4169
+ `, { encoding: "utf-8", mode: 384 });
4170
+ }
4171
+ await chmod(path, 384).catch(() => {
4172
+ return;
4173
+ });
4174
+ }
4175
+ async readJson(path, fallback) {
4176
+ try {
4177
+ const raw = await readFile(path, "utf-8");
4178
+ if (!raw.trim())
4179
+ return fallback;
4180
+ return JSON.parse(raw);
4181
+ } catch (error) {
4182
+ if (error.code === "ENOENT")
4183
+ return fallback;
4184
+ throw error;
4185
+ }
4186
+ }
4187
+ async writeJson(path, value) {
4188
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
4189
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
4190
+ `, { encoding: "utf-8", mode: 384 });
4191
+ await rename(tempPath, path);
4192
+ await chmod(path, 384).catch(() => {
4193
+ return;
4194
+ });
4195
+ }
4196
+ }
4197
+ function buildSignatureBase(timestamp, body) {
4198
+ return `${timestamp}.${body}`;
4199
+ }
4200
+ function signPayload(secret, timestamp, body) {
4201
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
4202
+ return `sha256=${digest}`;
4203
+ }
4204
+ function now2() {
4205
+ return new Date().toISOString();
4206
+ }
4207
+ function truncate(value, max = 4096) {
4208
+ return value.length > max ? `${value.slice(0, max)}...` : value;
4209
+ }
4210
+ function buildWebhookRequest(event, channel) {
4211
+ if (!channel.webhook)
4212
+ throw new Error(`Channel ${channel.id} has no webhook config`);
4213
+ const body = JSON.stringify(event);
4214
+ const timestamp = event.time;
4215
+ const headers = {
4216
+ "Content-Type": "application/json",
4217
+ "User-Agent": "@hasna/events",
4218
+ "X-Hasna-Event-Id": event.id,
4219
+ "X-Hasna-Event-Type": event.type,
4220
+ "X-Hasna-Timestamp": timestamp,
4221
+ ...channel.webhook.headers
4222
+ };
4223
+ if (channel.webhook.secret) {
4224
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
4225
+ }
4226
+ return { body, headers };
4227
+ }
4228
+ async function dispatchWebhook(event, channel, options = {}) {
4229
+ if (!channel.webhook)
4230
+ throw new Error(`Channel ${channel.id} has no webhook config`);
4231
+ const startedAt = now2();
4232
+ const { body, headers } = buildWebhookRequest(event, channel);
4233
+ const controller = new AbortController;
4234
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
4235
+ try {
4236
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
4237
+ method: "POST",
4238
+ headers,
4239
+ body,
4240
+ signal: controller.signal
4241
+ });
4242
+ const responseBody = truncate(await response.text());
4243
+ return {
4244
+ attempt: 1,
4245
+ status: response.ok ? "success" : "failed",
4246
+ startedAt,
4247
+ completedAt: now2(),
4248
+ responseStatus: response.status,
4249
+ responseBody,
4250
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
4251
+ };
4252
+ } catch (error) {
4253
+ return {
4254
+ attempt: 1,
4255
+ status: "failed",
4256
+ startedAt,
4257
+ completedAt: now2(),
4258
+ error: error instanceof Error ? error.message : String(error)
4259
+ };
4260
+ } finally {
4261
+ clearTimeout(timeout);
4262
+ }
4263
+ }
4264
+ async function dispatchCommand(event, channel) {
4265
+ if (!channel.command)
4266
+ throw new Error(`Channel ${channel.id} has no command config`);
4267
+ const startedAt = now2();
4268
+ const eventJson = JSON.stringify(event);
4269
+ const env = {
4270
+ ...process.env,
4271
+ ...channel.command.env,
4272
+ HASNA_CHANNEL_ID: channel.id,
4273
+ HASNA_EVENT_ID: event.id,
4274
+ HASNA_EVENT_TYPE: event.type,
4275
+ HASNA_EVENT_SOURCE: event.source,
4276
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
4277
+ HASNA_EVENT_SEVERITY: event.severity,
4278
+ HASNA_EVENT_TIME: event.time,
4279
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
4280
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
4281
+ HASNA_EVENT_JSON: eventJson
4282
+ };
4283
+ return new Promise((resolve6) => {
4284
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
4285
+ cwd: channel.command.cwd,
4286
+ env,
4287
+ stdio: ["pipe", "pipe", "pipe"]
4288
+ });
4289
+ let stdout = "";
4290
+ let stderr = "";
4291
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
4292
+ child.stdin.end(eventJson);
4293
+ child.stdout.on("data", (chunk) => {
4294
+ stdout += chunk.toString();
4295
+ });
4296
+ child.stderr.on("data", (chunk) => {
4297
+ stderr += chunk.toString();
4298
+ });
4299
+ child.on("error", (error) => {
4300
+ clearTimeout(timeout);
4301
+ resolve6({
4302
+ attempt: 1,
4303
+ status: "failed",
4304
+ startedAt,
4305
+ completedAt: now2(),
4306
+ stdout: truncate(stdout),
4307
+ stderr: truncate(stderr),
4308
+ error: error.message
4309
+ });
4310
+ });
4311
+ child.on("close", (code, signal) => {
4312
+ clearTimeout(timeout);
4313
+ const success = code === 0;
4314
+ resolve6({
4315
+ attempt: 1,
4316
+ status: success ? "success" : "failed",
4317
+ startedAt,
4318
+ completedAt: now2(),
4319
+ stdout: truncate(stdout),
4320
+ stderr: truncate(stderr),
4321
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
4322
+ });
4323
+ });
4324
+ });
4325
+ }
4326
+ async function dispatchChannel(event, channel, options = {}) {
4327
+ if (channel.transport === "webhook")
4328
+ return dispatchWebhook(event, channel, options);
4329
+ if (channel.transport === "command")
4330
+ return dispatchCommand(event, channel);
4331
+ return {
4332
+ attempt: 1,
4333
+ status: "skipped",
4334
+ startedAt: now2(),
4335
+ completedAt: now2(),
4336
+ error: `Unsupported transport: ${channel.transport}`
4337
+ };
4338
+ }
4339
+ function createDeliveryResult(event, channel, attempts) {
4340
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
4341
+ return {
4342
+ id: randomUUID2(),
4343
+ eventId: event.id,
4344
+ channelId: channel.id,
4345
+ transport: channel.transport,
4346
+ status,
4347
+ attempts,
4348
+ createdAt: attempts[0]?.startedAt ?? now2(),
4349
+ completedAt: attempts.at(-1)?.completedAt ?? now2()
4350
+ };
4351
+ }
4352
+ function createEvent(input) {
4353
+ return {
4354
+ id: input.id ?? randomUUID22(),
4355
+ source: input.source,
4356
+ type: input.type,
4357
+ time: normalizeTime(input.time),
4358
+ subject: input.subject,
4359
+ severity: input.severity ?? "info",
4360
+ data: input.data ?? {},
4361
+ message: input.message,
4362
+ dedupeKey: input.dedupeKey,
4363
+ schemaVersion: input.schemaVersion ?? "1.0",
4364
+ metadata: input.metadata ?? {}
4365
+ };
4366
+ }
4367
+
4368
+ class EventsClient {
4369
+ store;
4370
+ redactors;
4371
+ transportOptions;
4372
+ constructor(options = {}) {
4373
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
4374
+ this.redactors = options.redactors ?? [];
4375
+ this.transportOptions = { fetchImpl: options.fetchImpl };
4376
+ }
4377
+ async addChannel(input) {
4378
+ const timestamp = new Date().toISOString();
4379
+ return this.store.addChannel({
4380
+ ...input,
4381
+ createdAt: input.createdAt ?? timestamp,
4382
+ updatedAt: input.updatedAt ?? timestamp
4383
+ });
4384
+ }
4385
+ async listChannels() {
4386
+ return this.store.listChannels();
4387
+ }
4388
+ async removeChannel(id) {
4389
+ return this.store.removeChannel(id);
4390
+ }
4391
+ async emit(input, options = {}) {
4392
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
4393
+ if (options.dedupe !== false) {
4394
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
4395
+ if (existing) {
4396
+ return { event: existing, deliveries: [], deduped: true };
4397
+ }
4398
+ }
4399
+ await this.store.appendEvent(event);
4400
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
4401
+ return { event, deliveries, deduped: false };
4402
+ }
4403
+ async listEvents() {
4404
+ return this.store.listEvents();
4405
+ }
4406
+ async listDeliveries() {
4407
+ return this.store.listDeliveries();
4408
+ }
4409
+ async deliver(event) {
4410
+ const channels = await this.store.listChannels();
4411
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
4412
+ const deliveries = [];
4413
+ for (const channel of selected) {
4414
+ const eventForChannel = await this.applyRedaction(event, channel);
4415
+ const result = await this.deliverWithRetry(eventForChannel, channel);
4416
+ await this.store.appendDelivery(result);
4417
+ deliveries.push(result);
4418
+ }
4419
+ return deliveries;
4420
+ }
4421
+ async matchChannel(id, input = {}) {
4422
+ const channel = await this.store.getChannel(id);
4423
+ if (!channel)
4424
+ throw new Error(`Channel not found: ${id}`);
4425
+ const event = createEvent({
4426
+ source: input.source ?? "hasna.events",
4427
+ type: input.type ?? "events.test",
4428
+ subject: input.subject ?? id,
4429
+ severity: input.severity ?? "info",
4430
+ data: input.data ?? { test: true },
4431
+ message: input.message ?? "Hasna events test delivery",
4432
+ dedupeKey: input.dedupeKey,
4433
+ schemaVersion: input.schemaVersion,
4434
+ metadata: input.metadata,
4435
+ time: input.time,
4436
+ id: input.id
4437
+ });
4438
+ const matched = channelMatchesEvent(channel, event);
4439
+ return {
4440
+ channelId: channel.id,
4441
+ matched,
4442
+ event,
4443
+ filters: channel.filters,
4444
+ reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
4445
+ };
4446
+ }
4447
+ async testChannel(id, input = {}, options = {}) {
4448
+ const channel = await this.store.getChannel(id);
4449
+ if (!channel)
4450
+ throw new Error(`Channel not found: ${id}`);
4451
+ const match = await this.matchChannel(id, input);
4452
+ const event = match.event;
4453
+ if (options.honorFilters && !match.matched) {
4454
+ const timestamp = new Date().toISOString();
4455
+ const result2 = createDeliveryResult(event, channel, [{
4456
+ attempt: 1,
4457
+ status: "skipped",
4458
+ startedAt: timestamp,
4459
+ completedAt: timestamp,
4460
+ error: match.reason
4461
+ }]);
4462
+ result2.metadata = { reason: "filter_mismatch" };
4463
+ await this.store.appendDelivery(result2);
4464
+ return result2;
4465
+ }
4466
+ const eventForChannel = await this.applyRedaction(event, channel);
4467
+ const result = await this.deliverWithRetry(eventForChannel, channel);
4468
+ await this.store.appendDelivery(result);
4469
+ return result;
4470
+ }
4471
+ async replay(options = {}) {
4472
+ const events = (await this.store.listEvents()).filter((event) => {
4473
+ if (options.eventId && event.id !== options.eventId)
4474
+ return false;
4475
+ if (options.source && event.source !== options.source)
4476
+ return false;
4477
+ if (options.type && event.type !== options.type)
4478
+ return false;
4479
+ return true;
4480
+ });
4481
+ if (options.dryRun)
4482
+ return { events, deliveries: [] };
4483
+ const deliveries = [];
4484
+ for (const event of events) {
4485
+ deliveries.push(...await this.deliver(event));
4486
+ }
4487
+ return { events, deliveries };
4488
+ }
4489
+ async applyRedaction(event, channel) {
4490
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
4491
+ for (const redactor of this.redactors) {
4492
+ next = await redactor(next, channel);
4493
+ }
4494
+ return next;
4495
+ }
4496
+ async deliverWithRetry(event, channel) {
4497
+ const policy = normalizeRetryPolicy(channel.retry);
4498
+ const attempts = [];
4499
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
4500
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
4501
+ attempt.attempt = index + 1;
4502
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
4503
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
4504
+ }
4505
+ attempts.push(attempt);
4506
+ if (attempt.status !== "failed")
4507
+ break;
4508
+ if (attempt.nextBackoffMs)
4509
+ await Bun.sleep(attempt.nextBackoffMs);
4510
+ }
4511
+ return createDeliveryResult(event, channel, attempts);
4512
+ }
4513
+ }
4514
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
4515
+ if (paths.length === 0)
4516
+ return event;
4517
+ const copy = structuredClone(event);
4518
+ for (const path of paths) {
4519
+ setPath(copy, path, replacement);
4520
+ }
4521
+ return copy;
4522
+ }
4523
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
4524
+ return redactValue2(event, replacement);
4525
+ }
4526
+ function shouldRedactKey(key) {
4527
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
4528
+ }
4529
+ function redactValue2(value, replacement) {
4530
+ if (Array.isArray(value))
4531
+ return value.map((item) => redactValue2(item, replacement));
4532
+ if (!value || typeof value !== "object")
4533
+ return value;
4534
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
4535
+ key,
4536
+ shouldRedactKey(key) ? replacement : redactValue2(item, replacement)
4537
+ ]));
4538
+ }
4539
+ function setPath(input, path, replacement) {
4540
+ const parts = path.split(".");
4541
+ let cursor = input;
4542
+ for (const part of parts.slice(0, -1)) {
4543
+ const next = cursor[part];
4544
+ if (!next || typeof next !== "object")
4545
+ return;
4546
+ cursor = next;
4547
+ }
4548
+ const last = parts.at(-1);
4549
+ if (last && last in cursor)
4550
+ cursor[last] = replacement;
4551
+ }
4552
+ function normalizeTime(value) {
4553
+ if (!value)
4554
+ return new Date().toISOString();
4555
+ return value instanceof Date ? value.toISOString() : value;
4556
+ }
4557
+ function normalizeRetryPolicy(policy) {
4558
+ return {
4559
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
4560
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
4561
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
4562
+ };
4563
+ }
4564
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR", HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME", DEFAULT_SIGNATURE_TOLERANCE_MS;
4565
+ var init_dist = __esm(() => {
4566
+ DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
4567
+ });
4568
+
4569
+ // src/db/task-lists.ts
4570
+ function rowToTaskList(row) {
4571
+ return {
4572
+ ...row,
4573
+ metadata: JSON.parse(row.metadata || "{}")
4574
+ };
4575
+ }
4576
+ function createTaskList(input, db) {
4577
+ const d = db || getDatabase();
4578
+ const id = uuid();
4579
+ const timestamp = now();
4580
+ const slug = input.slug || slugify(input.name);
4581
+ if (!input.project_id) {
4582
+ const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
4583
+ if (existing) {
4584
+ throw new Error(`Standalone task list with slug "${slug}" already exists`);
4585
+ }
4586
+ }
4587
+ d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
4588
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
4589
+ return getTaskList(id, d);
4590
+ }
4591
+ function getTaskList(id, db) {
4592
+ const d = db || getDatabase();
4593
+ const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
4594
+ return row ? rowToTaskList(row) : null;
4595
+ }
4596
+ function getTaskListBySlug(slug, projectId, db) {
4597
+ const d = db || getDatabase();
4598
+ let row;
4599
+ if (projectId) {
4600
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
4601
+ } else {
4602
+ row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
4603
+ }
4604
+ return row ? rowToTaskList(row) : null;
4605
+ }
4606
+ function listTaskLists(projectId, db) {
4607
+ const d = db || getDatabase();
4608
+ if (projectId) {
4609
+ return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
4610
+ }
4611
+ return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
4612
+ }
4613
+ function updateTaskList(id, input, db) {
4614
+ const d = db || getDatabase();
4615
+ const existing = getTaskList(id, d);
4616
+ if (!existing)
4617
+ throw new TaskListNotFoundError(id);
4618
+ const sets = ["updated_at = ?"];
4619
+ const params = [now()];
4620
+ if (input.name !== undefined) {
4621
+ sets.push("name = ?");
4622
+ params.push(input.name);
4623
+ }
4624
+ if (input.description !== undefined) {
4625
+ sets.push("description = ?");
4626
+ params.push(input.description);
4627
+ }
4628
+ if (input.metadata !== undefined) {
4629
+ sets.push("metadata = ?");
4630
+ params.push(JSON.stringify(input.metadata));
4631
+ }
4632
+ params.push(id);
4633
+ d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
4634
+ return getTaskList(id, d);
4635
+ }
4636
+ function deleteTaskList(id, db) {
4637
+ const d = db || getDatabase();
4638
+ return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
4639
+ }
4640
+ function ensureTaskList(name, slug, projectId, db) {
4641
+ const d = db || getDatabase();
4642
+ const existing = getTaskListBySlug(slug, projectId, d);
4643
+ if (existing)
4644
+ return existing;
4645
+ return createTaskList({ name, slug, project_id: projectId }, d);
4646
+ }
4647
+ var init_task_lists = __esm(() => {
4648
+ init_types();
4649
+ init_database();
4650
+ init_projects();
4651
+ });
4652
+
4653
+ // src/lib/shared-events.ts
4654
+ function taskEventData(task, extra = {}) {
4655
+ return {
4656
+ id: task.id,
4657
+ task_id: task.id,
4658
+ short_id: task.short_id,
4659
+ title: task.title,
4660
+ description: task.description,
4661
+ status: task.status,
4662
+ priority: task.priority,
4663
+ project_id: task.project_id,
4664
+ parent_id: task.parent_id,
4665
+ plan_id: task.plan_id,
4666
+ task_list_id: task.task_list_id,
4667
+ agent_id: task.agent_id,
4668
+ assigned_to: task.assigned_to,
4669
+ session_id: task.session_id,
4670
+ working_dir: task.working_dir,
4671
+ tags: task.tags,
4672
+ metadata: task.metadata,
4673
+ version: task.version,
4674
+ created_at: task.created_at,
4675
+ updated_at: task.updated_at,
4676
+ started_at: task.started_at,
4677
+ completed_at: task.completed_at,
4678
+ due_at: task.due_at,
4679
+ ...extra
4680
+ };
4681
+ }
4682
+ function taskEventMetadata(task) {
4683
+ const metadata = {
4684
+ package: "@hasna/todos",
4685
+ todos_event_schema_version: 1,
4686
+ task_id: task.id,
4687
+ task_short_id: task.short_id,
4688
+ project_id: task.project_id,
4689
+ task_list_id: task.task_list_id,
4690
+ working_dir: task.working_dir
4691
+ };
4692
+ try {
4693
+ const project = task.project_id ? getProject(task.project_id) : null;
4694
+ const projectPath = project ? readMachineLocalPath(project) ?? project.path : task.working_dir;
4695
+ if (project) {
4696
+ metadata.project_id = project.id;
4697
+ metadata.project_name = project.name;
4698
+ metadata.project_path = projectPath;
4699
+ metadata.project_canonical_path = project.path;
4700
+ metadata.project_default_task_list_slug = project.task_list_id;
4701
+ metadata.root_project_id = inferRootProjectId(project);
4702
+ } else if (projectPath) {
4703
+ metadata.project_path = projectPath;
4704
+ metadata.project_canonical_path = projectPath;
4705
+ }
4706
+ if (projectPath) {
4707
+ metadata.project_kind = classifyProjectKind(projectPath);
4708
+ metadata.project_is_worktree = isWorktreePath(projectPath);
4709
+ if (typeof task.metadata.route_enabled === "boolean") {
4710
+ metadata.route_enabled = task.metadata.route_enabled;
4711
+ }
4712
+ metadata.working_dir = task.working_dir ?? projectPath;
4713
+ }
4714
+ const taskList = task.task_list_id ? getTaskList(task.task_list_id) ?? (project ? getTaskListBySlug(task.task_list_id, project.id) : null) : project?.task_list_id ? getTaskListBySlug(project.task_list_id, project.id) : null;
4715
+ if (taskList) {
4716
+ metadata.task_list_id = taskList.id;
4717
+ metadata.task_list_slug = taskList.slug;
4718
+ metadata.task_list_name = taskList.name;
4719
+ metadata.task_list_project_id = taskList.project_id;
4720
+ metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
4721
+ }
4722
+ } catch {}
4723
+ return metadata;
4724
+ }
4725
+ function classifyProjectKind(path) {
4726
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
4727
+ }
4728
+ function isWorktreePath(path) {
4729
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
4730
+ }
4731
+ function inferRootProjectId(project) {
4732
+ return isWorktreePath(project.path) ? null : project.id;
4733
+ }
4734
+ function readMachineLocalPath(project) {
4735
+ const machineId = process.env["TODOS_MACHINE_ID"];
4736
+ if (!machineId)
4737
+ return null;
4738
+ try {
4739
+ const row = getDatabase().query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
4740
+ return row?.path ?? null;
4741
+ } catch {
4742
+ return null;
4743
+ }
4744
+ }
4745
+ async function emitSharedTaskEvent(input) {
4746
+ const data = taskEventData(input.task, input.data);
4747
+ await new EventsClient().emit({
4748
+ source: SOURCE,
4749
+ type: input.type,
4750
+ subject: input.task.id,
4751
+ severity: input.severity ?? "info",
4752
+ message: input.message ?? `${input.type}: ${input.task.title}`,
4753
+ data,
4754
+ dedupeKey: input.dedupeKey ?? `${input.type}:${input.task.id}:${input.task.version}`,
4755
+ metadata: taskEventMetadata(input.task)
4756
+ }, { deliver: true, dedupe: true });
4757
+ }
4758
+ function emitSharedTaskEventQuiet(input) {
4759
+ emitSharedTaskEvent(input).catch(() => {
4760
+ return;
4761
+ });
4762
+ }
4763
+ var SOURCE = "todos";
4764
+ var init_shared_events = __esm(() => {
4765
+ init_dist();
4766
+ init_database();
4767
+ init_projects();
4768
+ init_task_lists();
4769
+ });
4770
+
4016
4771
  // src/lib/secret-redaction.ts
4017
4772
  function isAllowlisted(text, match, allowlist) {
4018
4773
  const context = text.slice(Math.max(0, text.indexOf(match) - 20), text.indexOf(match) + match.length + 20);
@@ -4480,7 +5235,7 @@ async function deliverWebhook(wh, event, body, attempt, db) {
4480
5235
  activeDeliveries--;
4481
5236
  }
4482
5237
  }
4483
- async function dispatchWebhook(event, payload, db) {
5238
+ async function dispatchWebhook2(event, payload, db) {
4484
5239
  const d = db || getDatabase();
4485
5240
  const webhooks = listWebhooks(d).filter((w) => w.active && (w.events.length === 0 || w.events.includes(event)));
4486
5241
  const payloadObj = typeof payload === "object" && payload !== null ? payload : {};
@@ -4597,7 +5352,10 @@ function createTask(input, db) {
4597
5352
  insertTaskTags(id, tags, d);
4598
5353
  }
4599
5354
  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(() => {});
5355
+ const payload = taskEventData(task);
5356
+ dispatchWebhook2("task.created", payload, d).catch(() => {});
5357
+ emitLocalEventHooksQuiet({ type: "task.created", payload });
5358
+ emitSharedTaskEventQuiet({ type: "task.created", task });
4601
5359
  return task;
4602
5360
  }
4603
5361
  function getTask(id, db) {
@@ -4941,18 +5699,7 @@ function updateTask(id, input, db) {
4941
5699
  logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
4942
5700
  if (input.approved_by !== undefined)
4943
5701
  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 {
5702
+ const updatedTask = {
4956
5703
  ...task,
4957
5704
  ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)),
4958
5705
  tags: input.tags ?? task.tags,
@@ -4970,6 +5717,22 @@ function updateTask(id, input, db) {
4970
5717
  approved_by: input.approved_by ?? task.approved_by,
4971
5718
  approved_at: input.approved_by ? timestamp : task.approved_at
4972
5719
  };
5720
+ if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
5721
+ const payload = taskEventData(updatedTask, { assigned_to: input.assigned_to, old_assigned_to: task.assigned_to });
5722
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
5723
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
5724
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: updatedTask, data: { old_assigned_to: task.assigned_to } });
5725
+ }
5726
+ if (input.status !== undefined && input.status !== task.status) {
5727
+ const payload = taskEventData(updatedTask, { old_status: task.status, new_status: input.status });
5728
+ dispatchWebhook2("task.status_changed", payload, d).catch(() => {});
5729
+ emitLocalEventHooksQuiet({ type: "task.status_changed", payload });
5730
+ emitSharedTaskEventQuiet({ type: "task.status_changed", task: updatedTask, data: { old_status: task.status, new_status: input.status } });
5731
+ }
5732
+ if (input.approved_by !== undefined) {
5733
+ emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
5734
+ }
5735
+ return updatedTask;
4973
5736
  }
4974
5737
  function deleteTask(id, db) {
4975
5738
  const d = db || getDatabase();
@@ -4981,6 +5744,7 @@ var init_task_crud = __esm(() => {
4981
5744
  init_database();
4982
5745
  init_completion_guard();
4983
5746
  init_event_hooks();
5747
+ init_shared_events();
4984
5748
  init_audit();
4985
5749
  init_webhooks();
4986
5750
  init_checklists();
@@ -5736,9 +6500,12 @@ function startTask(id, agentId, db) {
5736
6500
  throw new Error(`Task ${id} could not be started because it changed during claim`);
5737
6501
  }
5738
6502
  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 };
6503
+ 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 };
6504
+ const payload = taskEventData(startedTask, { agent_id: agentId });
6505
+ dispatchWebhook2("task.started", payload, d).catch(() => {});
6506
+ emitLocalEventHooksQuiet({ type: "task.started", payload });
6507
+ emitSharedTaskEventQuiet({ type: "task.started", task: startedTask, data: { agent_id: agentId } });
6508
+ return startedTask;
5742
6509
  }
5743
6510
  function completeTask(id, agentId, db, options) {
5744
6511
  const d = db || getDatabase();
@@ -5774,8 +6541,21 @@ function completeTask(id, agentId, db, options) {
5774
6541
  });
5775
6542
  tx();
5776
6543
  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 } });
6544
+ const completedTaskForEvent = {
6545
+ ...task,
6546
+ status: "completed",
6547
+ locked_by: null,
6548
+ locked_at: null,
6549
+ completed_at: timestamp,
6550
+ confidence,
6551
+ version: task.version + 1,
6552
+ updated_at: timestamp,
6553
+ metadata: hasMeta ? { ...task.metadata, ...completionMeta } : task.metadata
6554
+ };
6555
+ const completionPayload = taskEventData(completedTaskForEvent, { agent_id: agentId, completed_at: timestamp });
6556
+ dispatchWebhook2("task.completed", completionPayload, d).catch(() => {});
6557
+ emitLocalEventHooksQuiet({ type: "task.completed", payload: completionPayload });
6558
+ emitSharedTaskEventQuiet({ type: "task.completed", task: completedTaskForEvent, data: { agent_id: agentId, completed_at: timestamp } });
5779
6559
  let spawnedTask = null;
5780
6560
  if (task.recurrence_rule && !options?.skip_recurrence) {
5781
6561
  spawnedTask = spawnNextRecurrence(task, d, timestamp);
@@ -5816,8 +6596,12 @@ function completeTask(id, agentId, db, options) {
5816
6596
  if (unblockedDeps.length > 0) {
5817
6597
  meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
5818
6598
  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 } });
6599
+ const depTask = getTask(dep.id, d);
6600
+ const payload = depTask ? taskEventData(depTask, { unblocked_by: id }) : { id: dep.id, unblocked_by: id, title: dep.title };
6601
+ dispatchWebhook2("task.unblocked", payload, d).catch(() => {});
6602
+ emitLocalEventHooksQuiet({ type: "task.unblocked", payload });
6603
+ if (depTask)
6604
+ emitSharedTaskEventQuiet({ type: "task.unblocked", task: depTask, data: { unblocked_by: id } });
5821
6605
  }
5822
6606
  }
5823
6607
  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 +6787,6 @@ function failTask(id, agentId, reason, options, db) {
6003
6787
  const timestamp = now();
6004
6788
  d.run(`UPDATE tasks SET status = 'failed', locked_by = NULL, locked_at = NULL, metadata = ?, version = version + 1, updated_at = ?
6005
6789
  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
6790
  const failedTask = {
6010
6791
  ...task,
6011
6792
  status: "failed",
@@ -6015,6 +6796,11 @@ function failTask(id, agentId, reason, options, db) {
6015
6796
  version: task.version + 1,
6016
6797
  updated_at: timestamp
6017
6798
  };
6799
+ logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
6800
+ const failurePayload = taskEventData(failedTask, { reason, error_code: options?.error_code, agent_id: agentId });
6801
+ dispatchWebhook2("task.failed", failurePayload, d).catch(() => {});
6802
+ emitLocalEventHooksQuiet({ type: "task.failed", payload: failurePayload });
6803
+ emitSharedTaskEventQuiet({ type: "task.failed", task: failedTask, data: { reason, error_code: options?.error_code, agent_id: agentId }, severity: "warning" });
6018
6804
  let retryTask;
6019
6805
  if (options?.retry) {
6020
6806
  const retryCount = (task.retry_count || 0) + 1;
@@ -6089,9 +6875,12 @@ function stealTask(agentId, opts, db) {
6089
6875
  return null;
6090
6876
  logTaskChange(target.id, "steal", "assigned_to", target.assigned_to, agentId, agentId, d);
6091
6877
  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 };
6878
+ const stolenTask = { ...target, assigned_to: agentId, locked_by: agentId, locked_at: timestamp, updated_at: timestamp, version: target.version + 1 };
6879
+ const payload = taskEventData(stolenTask, { agent_id: agentId, stolen_from: target.assigned_to });
6880
+ dispatchWebhook2("task.assigned", payload, d).catch(() => {});
6881
+ emitLocalEventHooksQuiet({ type: "task.assigned", payload });
6882
+ emitSharedTaskEventQuiet({ type: "task.assigned", task: stolenTask, data: { agent_id: agentId, stolen_from: target.assigned_to } });
6883
+ return stolenTask;
6095
6884
  }
6096
6885
  function claimOrSteal(agentId, filters, db) {
6097
6886
  const d = db || getDatabase();
@@ -6139,6 +6928,7 @@ var init_task_lifecycle = __esm(() => {
6139
6928
  init_database();
6140
6929
  init_completion_guard();
6141
6930
  init_event_hooks();
6931
+ init_shared_events();
6142
6932
  init_audit();
6143
6933
  init_recurrence();
6144
6934
  init_webhooks();
@@ -6826,7 +7616,7 @@ function getTaskWatchers(taskId, db) {
6826
7616
  }
6827
7617
  function notifyWatchers(taskId, event, data, db) {
6828
7618
  const watchers = getTaskWatchers(taskId, db);
6829
- dispatchWebhook(`task.watcher.${event}`, { task_id: taskId, watchers: watchers.map((w) => w.agent_id), ...data }, db).catch(() => {});
7619
+ dispatchWebhook2(`task.watcher.${event}`, { task_id: taskId, watchers: watchers.map((w) => w.agent_id), ...data }, db).catch(() => {});
6830
7620
  }
6831
7621
  function logCost(taskId, tokens, usd, db) {
6832
7622
  const d = db || getDatabase();
@@ -7320,8 +8110,8 @@ var init_boards = __esm(() => {
7320
8110
 
7321
8111
  // src/lib/artifact-store.ts
7322
8112
  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";
8113
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
8114
+ import { basename, dirname as dirname5, join as join6, resolve as resolve6 } from "path";
7325
8115
  import { tmpdir } from "os";
7326
8116
  function isInMemoryDb2(path) {
7327
8117
  return path === ":memory:" || path.startsWith("file::memory:");
@@ -7333,15 +8123,15 @@ function artifactStoreRoot() {
7333
8123
  return resolve6(process.env["TODOS_ARTIFACTS_DIR"]);
7334
8124
  const dbPath = getDatabasePath();
7335
8125
  if (isInMemoryDb2(dbPath))
7336
- return join5(tmpdir(), "hasna-todos-artifacts");
7337
- return join5(dirname5(resolve6(dbPath)), "artifacts");
8126
+ return join6(tmpdir(), "hasna-todos-artifacts");
8127
+ return join6(dirname5(resolve6(dbPath)), "artifacts");
7338
8128
  }
7339
8129
  function artifactStorePath(relativePath) {
7340
8130
  const normalized = relativePath.replace(/\\/g, "/");
7341
8131
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
7342
8132
  throw new Error("Invalid artifact store path");
7343
8133
  }
7344
- return join5(artifactStoreRoot(), normalized);
8134
+ return join6(artifactStoreRoot(), normalized);
7345
8135
  }
7346
8136
  function sha256(buffer) {
7347
8137
  return createHash3("sha256").update(buffer).digest("hex");
@@ -7382,7 +8172,7 @@ function mediaTypeFor(path, textLike) {
7382
8172
  }
7383
8173
  function storeArtifactContent(input) {
7384
8174
  const sourcePath = resolve6(input.path);
7385
- if (!existsSync6(sourcePath))
8175
+ if (!existsSync7(sourcePath))
7386
8176
  return null;
7387
8177
  const sourceStat = statSync2(sourcePath);
7388
8178
  if (!sourceStat.isFile())
@@ -7399,9 +8189,9 @@ function storeArtifactContent(input) {
7399
8189
  redactionStatus = "redacted";
7400
8190
  }
7401
8191
  const storedSha = sha256(storedBuffer);
7402
- const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
8192
+ const relativePath = join6("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
7403
8193
  const destination = artifactStorePath(relativePath);
7404
- if (!existsSync6(destination)) {
8194
+ if (!existsSync7(destination)) {
7405
8195
  mkdirSync4(dirname5(destination), { recursive: true });
7406
8196
  writeFileSync2(destination, storedBuffer);
7407
8197
  }
@@ -7461,7 +8251,7 @@ function verifyStoredArtifact(input) {
7461
8251
  };
7462
8252
  }
7463
8253
  const storedPath = artifactStorePath(store.relative_path);
7464
- if (!existsSync6(storedPath)) {
8254
+ if (!existsSync7(storedPath)) {
7465
8255
  return {
7466
8256
  id: input.id,
7467
8257
  path: input.path,
@@ -9488,8 +10278,8 @@ var exports_doctor = {};
9488
10278
  __export(exports_doctor, {
9489
10279
  runTodosDoctor: () => runTodosDoctor
9490
10280
  });
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";
10281
+ import { chmodSync, copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
10282
+ import { basename as basename2, dirname as dirname6, join as join7 } from "path";
9493
10283
  function tableExists(db, table) {
9494
10284
  return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
9495
10285
  }
@@ -9583,7 +10373,7 @@ function findMissingProjectRoots(db) {
9583
10373
  continue;
9584
10374
  if (!row.path.startsWith("/"))
9585
10375
  continue;
9586
- if (!existsSync7(row.path))
10376
+ if (!existsSync8(row.path))
9587
10377
  missing++;
9588
10378
  }
9589
10379
  return missing;
@@ -9643,16 +10433,16 @@ function databasePermissionsAreUnsafe(dbPath) {
9643
10433
  function createBackup(dbPath) {
9644
10434
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
9645
10435
  return;
9646
- if (!existsSync7(dbPath))
10436
+ if (!existsSync8(dbPath))
9647
10437
  return;
9648
10438
  const stamp = now().replace(/[:.]/g, "-");
9649
- const backupDir = join6(dirname6(dbPath), `${basename2(dbPath)}.backup-${stamp}`);
10439
+ const backupDir = join7(dirname6(dbPath), `${basename2(dbPath)}.backup-${stamp}`);
9650
10440
  const files = [];
9651
10441
  mkdirSync5(backupDir, { recursive: true });
9652
10442
  for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
9653
- if (!existsSync7(source))
10443
+ if (!existsSync8(source))
9654
10444
  continue;
9655
- const target = join6(backupDir, basename2(source));
10445
+ const target = join7(backupDir, basename2(source));
9656
10446
  copyFileSync(source, target);
9657
10447
  files.push(target);
9658
10448
  }
@@ -9878,7 +10668,7 @@ var init_doctor = __esm(() => {
9878
10668
  });
9879
10669
 
9880
10670
  // src/server/routes.ts
9881
- import { join as join7, resolve as resolve7, sep } from "path";
10671
+ import { join as join8, resolve as resolve7, sep } from "path";
9882
10672
  function parseFieldsParam(url) {
9883
10673
  const fieldsParam = url.searchParams.get("fields");
9884
10674
  return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
@@ -10582,7 +11372,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
10582
11372
  if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
10583
11373
  return null;
10584
11374
  if (path !== "/") {
10585
- const filePath = join7(ctx.dashboardDir, path);
11375
+ const filePath = join8(ctx.dashboardDir, path);
10586
11376
  const resolvedFile = resolve7(filePath);
10587
11377
  const resolvedBase = resolve7(ctx.dashboardDir);
10588
11378
  if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
@@ -10592,7 +11382,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
10592
11382
  if (res2)
10593
11383
  return res2;
10594
11384
  }
10595
- const indexPath = join7(ctx.dashboardDir, "index.html");
11385
+ const indexPath = join8(ctx.dashboardDir, "index.html");
10596
11386
  const res = serveStaticFile2(indexPath);
10597
11387
  if (res)
10598
11388
  return res;
@@ -13984,7 +14774,7 @@ class JSONSchemaGenerator {
13984
14774
  if (val === undefined) {
13985
14775
  if (this.unrepresentable === "throw") {
13986
14776
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
13987
- }
14777
+ } else {}
13988
14778
  } else if (typeof val === "bigint") {
13989
14779
  if (this.unrepresentable === "throw") {
13990
14780
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -31037,17 +31827,73 @@ var init_dispatch_formatter = __esm(() => {
31037
31827
  });
31038
31828
 
31039
31829
  // src/lib/tmux.ts
31830
+ async function inspectTmuxPane(target) {
31831
+ const proc = Bun.spawn([
31832
+ "tmux",
31833
+ "display-message",
31834
+ "-p",
31835
+ "-t",
31836
+ target,
31837
+ "#{pane_id}\t#{pane_current_command}\t#{pane_dead}\t#{pane_input_off}\t#{pane_in_mode}"
31838
+ ], {
31839
+ stdout: "pipe",
31840
+ stderr: "pipe"
31841
+ });
31842
+ const exitCode = await proc.exited;
31843
+ if (exitCode !== 0) {
31844
+ const stderr = await new Response(proc.stderr).text();
31845
+ throw new Error(`tmux target "${target}" not found: ${stderr.trim() || "unknown error"}`);
31846
+ }
31847
+ const stdout = (await new Response(proc.stdout).text()).trim();
31848
+ const [paneId, currentCommand = "", paneDead = "0", inputOff = "0", inMode = "0"] = stdout.split("\t");
31849
+ if (!paneId) {
31850
+ throw new Error(`tmux target "${target}" did not resolve to a pane`);
31851
+ }
31852
+ return {
31853
+ target,
31854
+ paneId,
31855
+ currentCommand,
31856
+ paneDead: paneDead === "1",
31857
+ inputOff: inputOff === "1",
31858
+ inMode: inMode === "1"
31859
+ };
31860
+ }
31861
+ function tmuxPaneBusyStatus(pane) {
31862
+ if (pane.paneDead) {
31863
+ return { busy: true, reason: "pane is dead" };
31864
+ }
31865
+ if (pane.inputOff) {
31866
+ return { busy: true, reason: "pane input is disabled" };
31867
+ }
31868
+ if (pane.inMode) {
31869
+ return { busy: true, reason: "pane is in copy or alternate mode" };
31870
+ }
31871
+ const currentCommand = pane.currentCommand.trim();
31872
+ if (currentCommand && !IDLE_TMUX_COMMANDS.has(currentCommand)) {
31873
+ return { busy: true, reason: `pane is running ${currentCommand}` };
31874
+ }
31875
+ return { busy: false, reason: null };
31876
+ }
31040
31877
  function calculateDelay(message) {
31041
31878
  const len = message.length;
31042
31879
  const extra = Math.floor(len / 100 * 40);
31043
31880
  return Math.min(DELAY_MIN + extra, DELAY_MAX);
31044
31881
  }
31045
- async function sendToTmux(target, message, delayMs, dryRun = false) {
31882
+ async function sendToTmux(target, message, delayMs, options = false) {
31883
+ const opts = typeof options === "boolean" ? { dryRun: options } : options;
31884
+ const dryRun = opts.dryRun ?? false;
31046
31885
  if (dryRun) {
31047
31886
  console.log(`[dry-run] sendToTmux target=${target} delay=${delayMs}ms`);
31048
31887
  console.log(`[dry-run] message: ${message.slice(0, 200)}`);
31049
31888
  return;
31050
31889
  }
31890
+ if (!opts.confirmBusy) {
31891
+ const pane = await inspectTmuxPane(target);
31892
+ const status = tmuxPaneBusyStatus(pane);
31893
+ if (status.busy) {
31894
+ throw new Error(`tmux target "${target}" appears busy (${status.reason}). Re-run with --confirm-busy to send anyway.`);
31895
+ }
31896
+ }
31051
31897
  const sendProc = Bun.spawn(["tmux", "send-keys", "-t", target, message, ""], {
31052
31898
  stdout: "pipe",
31053
31899
  stderr: "pipe"
@@ -31068,7 +31914,21 @@ async function sendToTmux(target, message, delayMs, dryRun = false) {
31068
31914
  throw new Error(`tmux send-keys Enter failed for target "${target}": ${stderr.trim()}`);
31069
31915
  }
31070
31916
  }
31071
- var DELAY_MIN = 3000, DELAY_MAX = 5000;
31917
+ var DELAY_MIN = 3000, DELAY_MAX = 5000, IDLE_TMUX_COMMANDS;
31918
+ var init_tmux = __esm(() => {
31919
+ IDLE_TMUX_COMMANDS = new Set([
31920
+ "bash",
31921
+ "dash",
31922
+ "elvish",
31923
+ "fish",
31924
+ "ksh",
31925
+ "nu",
31926
+ "pwsh",
31927
+ "sh",
31928
+ "tmux",
31929
+ "zsh"
31930
+ ]);
31931
+ });
31072
31932
 
31073
31933
  // src/lib/dispatch.ts
31074
31934
  var exports_dispatch = {};
@@ -31086,7 +31946,10 @@ async function executeDispatch(dispatch, opts = {}, db) {
31086
31946
  }
31087
31947
  const delayMs = dispatch.delay_ms ?? calculateDelay(message);
31088
31948
  try {
31089
- await sendToTmux(dispatch.target_window, message, delayMs, opts.dryRun ?? false);
31949
+ await sendToTmux(dispatch.target_window, message, delayMs, {
31950
+ dryRun: opts.dryRun ?? false,
31951
+ confirmBusy: opts.confirmBusy ?? false
31952
+ });
31090
31953
  createDispatchLog({
31091
31954
  dispatch_id: dispatch.id,
31092
31955
  target_window: dispatch.target_window,
@@ -31145,90 +32008,7 @@ var init_dispatch = __esm(() => {
31145
32008
  init_tasks();
31146
32009
  init_database();
31147
32010
  init_dispatch_formatter();
31148
- });
31149
-
31150
- // src/db/task-lists.ts
31151
- function rowToTaskList(row) {
31152
- return {
31153
- ...row,
31154
- metadata: JSON.parse(row.metadata || "{}")
31155
- };
31156
- }
31157
- function createTaskList(input, db) {
31158
- const d = db || getDatabase();
31159
- const id = uuid();
31160
- const timestamp = now();
31161
- const slug = input.slug || slugify(input.name);
31162
- if (!input.project_id) {
31163
- const existing = d.query("SELECT id FROM task_lists WHERE project_id IS NULL AND slug = ?").get(slug);
31164
- if (existing) {
31165
- throw new Error(`Standalone task list with slug "${slug}" already exists`);
31166
- }
31167
- }
31168
- d.run(`INSERT INTO task_lists (id, project_id, slug, name, description, metadata, created_at, updated_at)
31169
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, input.project_id || null, slug, input.name, input.description || null, JSON.stringify(input.metadata || {}), timestamp, timestamp]);
31170
- return getTaskList(id, d);
31171
- }
31172
- function getTaskList(id, db) {
31173
- const d = db || getDatabase();
31174
- const row = d.query("SELECT * FROM task_lists WHERE id = ?").get(id);
31175
- return row ? rowToTaskList(row) : null;
31176
- }
31177
- function getTaskListBySlug(slug, projectId, db) {
31178
- const d = db || getDatabase();
31179
- let row;
31180
- if (projectId) {
31181
- row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id = ?").get(slug, projectId);
31182
- } else {
31183
- row = d.query("SELECT * FROM task_lists WHERE slug = ? AND project_id IS NULL").get(slug);
31184
- }
31185
- return row ? rowToTaskList(row) : null;
31186
- }
31187
- function listTaskLists(projectId, db) {
31188
- const d = db || getDatabase();
31189
- if (projectId) {
31190
- return d.query("SELECT * FROM task_lists WHERE project_id = ? ORDER BY name").all(projectId).map(rowToTaskList);
31191
- }
31192
- return d.query("SELECT * FROM task_lists ORDER BY name").all().map(rowToTaskList);
31193
- }
31194
- function updateTaskList(id, input, db) {
31195
- const d = db || getDatabase();
31196
- const existing = getTaskList(id, d);
31197
- if (!existing)
31198
- throw new TaskListNotFoundError(id);
31199
- const sets = ["updated_at = ?"];
31200
- const params = [now()];
31201
- if (input.name !== undefined) {
31202
- sets.push("name = ?");
31203
- params.push(input.name);
31204
- }
31205
- if (input.description !== undefined) {
31206
- sets.push("description = ?");
31207
- params.push(input.description);
31208
- }
31209
- if (input.metadata !== undefined) {
31210
- sets.push("metadata = ?");
31211
- params.push(JSON.stringify(input.metadata));
31212
- }
31213
- params.push(id);
31214
- d.run(`UPDATE task_lists SET ${sets.join(", ")} WHERE id = ?`, params);
31215
- return getTaskList(id, d);
31216
- }
31217
- function deleteTaskList(id, db) {
31218
- const d = db || getDatabase();
31219
- return d.run("DELETE FROM task_lists WHERE id = ?", [id]).changes > 0;
31220
- }
31221
- function ensureTaskList(name, slug, projectId, db) {
31222
- const d = db || getDatabase();
31223
- const existing = getTaskListBySlug(slug, projectId, d);
31224
- if (existing)
31225
- return existing;
31226
- return createTaskList({ name, slug, project_id: projectId }, d);
31227
- }
31228
- var init_task_lists = __esm(() => {
31229
- init_types();
31230
- init_database();
31231
- init_projects();
32011
+ init_tmux();
31232
32012
  });
31233
32013
 
31234
32014
  // src/mcp/tools/dispatch.ts
@@ -31239,8 +32019,9 @@ function registerDispatchTools(server, { shouldRegisterTool, resolveId, formatEr
31239
32019
  target: exports_external.string().describe("tmux target \u2014 window name, session:window, or session:window.pane"),
31240
32020
  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
32021
  scheduled_at: exports_external.string().optional().describe("ISO datetime to schedule the dispatch for. Fires immediately if omitted."),
32022
+ confirm_busy: exports_external.boolean().optional().describe("Send even if the target tmux pane appears busy. Default: false."),
31242
32023
  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 }) => {
32024
+ }, async ({ task_ids, target, delay_ms, scheduled_at, confirm_busy, dry_run }) => {
31244
32025
  try {
31245
32026
  const db = getDatabase();
31246
32027
  const resolvedIds = task_ids.map((id) => resolveId(id));
@@ -31256,7 +32037,7 @@ ${message}` }]
31256
32037
  }
31257
32038
  const dispatch = createDispatch({ task_ids: resolvedIds, target_window: target, message, delay_ms: effectiveDelay, scheduled_at }, db);
31258
32039
  if (!scheduled_at)
31259
- await executeDispatch(dispatch, {}, db);
32040
+ await executeDispatch(dispatch, { confirmBusy: confirm_busy ?? false }, db);
31260
32041
  return {
31261
32042
  content: [{
31262
32043
  type: "text",
@@ -31282,8 +32063,9 @@ ${message}`
31282
32063
  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
32064
  delay_ms: exports_external.number().optional().describe("Delay in ms between sending and Enter. Auto-calculated if omitted."),
31284
32065
  scheduled_at: exports_external.string().optional().describe("ISO datetime to schedule. Fires immediately if omitted."),
32066
+ confirm_busy: exports_external.boolean().optional().describe("Send even if the target tmux pane appears busy. Default: false."),
31285
32067
  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 }) => {
32068
+ }, async ({ task_list_id, target, filter_status, delay_ms, scheduled_at, confirm_busy, dry_run }) => {
31287
32069
  try {
31288
32070
  const db = getDatabase();
31289
32071
  const resolvedListId = resolveId(task_list_id, "task_lists");
@@ -31303,7 +32085,7 @@ ${message}` }]
31303
32085
  }
31304
32086
  const dispatch = createDispatch({ title: `Task list: ${taskList.name}`, task_list_id: resolvedListId, target_window: target, message, delay_ms: effectiveDelay, scheduled_at }, db);
31305
32087
  if (!scheduled_at)
31306
- await executeDispatch(dispatch, {}, db);
32088
+ await executeDispatch(dispatch, { confirmBusy: confirm_busy ?? false }, db);
31307
32089
  return {
31308
32090
  content: [{
31309
32091
  type: "text",
@@ -31330,15 +32112,16 @@ ${message}`
31330
32112
  task_list_id: exports_external.string().optional().describe("Task list ID to dispatch (use this or task_ids)"),
31331
32113
  stagger_ms: exports_external.number().optional().describe("Delay between each window dispatch. Default: 500ms."),
31332
32114
  delay_ms: exports_external.number().optional().describe("Delay between message send and Enter. Auto-calculated if omitted."),
32115
+ confirm_busy: exports_external.boolean().optional().describe("Send even if target tmux panes appear busy. Default: false."),
31333
32116
  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 }) => {
32117
+ }, async ({ targets, task_ids, task_list_id, stagger_ms, delay_ms, confirm_busy, dry_run }) => {
31335
32118
  try {
31336
32119
  if (!task_ids && !task_list_id)
31337
32120
  throw new Error("Either task_ids or task_list_id is required");
31338
32121
  const db = getDatabase();
31339
32122
  const resolvedTaskIds = task_ids ? task_ids.map((id) => resolveId(id)) : undefined;
31340
32123
  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);
32124
+ 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
32125
  const lines = dispatches.map((d) => `${d.target_window}: ${d.id} [${d.status}]`);
31343
32126
  return { content: [{ type: "text", text: `Dispatched to ${dispatches.length} target(s):
31344
32127
  ${lines.join(`
@@ -31383,11 +32166,12 @@ ${lines.join(`
31383
32166
  if (shouldRegisterTool("run_due_dispatches")) {
31384
32167
  server.tool("run_due_dispatches", "Manually trigger all pending dispatches that are due (scheduled_at <= now). Returns the count fired.", {
31385
32168
  dry_run: exports_external.boolean().optional().describe("Preview without sending. Default: false."),
32169
+ confirm_busy: exports_external.boolean().optional().describe("Send even if target tmux panes appear busy. Default: false."),
31386
32170
  all: exports_external.boolean().optional().describe("Ignore scheduled_at and fire all pending dispatches immediately.")
31387
- }, async ({ dry_run }) => {
32171
+ }, async ({ dry_run, confirm_busy }) => {
31388
32172
  try {
31389
32173
  const { runDueDispatches: runDueDispatches2 } = await Promise.resolve().then(() => (init_dispatch(), exports_dispatch));
31390
- const count = await runDueDispatches2({ dryRun: dry_run });
32174
+ const count = await runDueDispatches2({ dryRun: dry_run, confirmBusy: confirm_busy ?? false });
31391
32175
  return { content: [{ type: "text", text: `Fired ${count} dispatch(es).` }] };
31392
32176
  } catch (e) {
31393
32177
  return { content: [{ type: "text", text: formatError2(e) }], isError: true };
@@ -31400,6 +32184,7 @@ var init_dispatch2 = __esm(() => {
31400
32184
  init_dispatches();
31401
32185
  init_dispatch();
31402
32186
  init_dispatch_formatter();
32187
+ init_tmux();
31403
32188
  init_tasks();
31404
32189
  init_task_lists();
31405
32190
  init_database();
@@ -32207,7 +32992,7 @@ var init_task_crud2 = __esm(() => {
32207
32992
  });
32208
32993
 
32209
32994
  // src/lib/project-bootstrap.ts
32210
- import { existsSync as existsSync8, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
32995
+ import { existsSync as existsSync9, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
32211
32996
  import { basename as basename3, dirname as dirname7, resolve as resolve8 } from "path";
32212
32997
  function safeStat(path) {
32213
32998
  try {
@@ -32226,7 +33011,7 @@ function canonicalPath(input) {
32226
33011
  function findUp(start, marker) {
32227
33012
  let current = canonicalPath(start);
32228
33013
  while (true) {
32229
- if (existsSync8(resolve8(current, marker)))
33014
+ if (existsSync9(resolve8(current, marker)))
32230
33015
  return current;
32231
33016
  const parent = dirname7(current);
32232
33017
  if (parent === current)
@@ -32238,7 +33023,7 @@ function readPackageJson(path) {
32238
33023
  if (!path)
32239
33024
  return null;
32240
33025
  const file = resolve8(path, "package.json");
32241
- if (!existsSync8(file))
33026
+ if (!existsSync9(file))
32242
33027
  return null;
32243
33028
  try {
32244
33029
  const parsed = JSON.parse(readFileSync4(file, "utf-8"));
@@ -32260,7 +33045,7 @@ function workspaceMarker(root, rootPackage) {
32260
33045
  if (rootPackage?.workspaces)
32261
33046
  markers.push("package.json#workspaces");
32262
33047
  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)))
33048
+ if (existsSync9(resolve8(root, marker)))
32264
33049
  markers.push(marker);
32265
33050
  }
32266
33051
  const kind = markers.find((marker) => marker !== "bun.lock" && marker !== "bun.lockb") ?? null;
@@ -32561,7 +33346,7 @@ var init_tags = __esm(() => {
32561
33346
  });
32562
33347
 
32563
33348
  // src/lib/retention-cleanup.ts
32564
- import { existsSync as existsSync9, unlinkSync } from "fs";
33349
+ import { existsSync as existsSync10, unlinkSync } from "fs";
32565
33350
  function normalizeScopes(scopes) {
32566
33351
  if (!scopes || scopes.length === 0)
32567
33352
  return [...ALL_SCOPES];
@@ -32764,7 +33549,7 @@ function applyRetentionCleanup(input, db) {
32764
33549
  for (const artifact of report.candidates.artifact_files) {
32765
33550
  try {
32766
33551
  const path = artifactStorePath(artifact.relative_path);
32767
- if (!existsSync9(path)) {
33552
+ if (!existsSync10(path)) {
32768
33553
  report.warnings.push(`stored artifact already missing: ${artifact.relative_path}`);
32769
33554
  continue;
32770
33555
  }
@@ -32791,8 +33576,8 @@ var init_retention_cleanup = __esm(() => {
32791
33576
  });
32792
33577
 
32793
33578
  // 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";
33579
+ import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
33580
+ import { basename as basename4, isAbsolute, join as join9, relative as relative3, resolve as resolve9, sep as sep2 } from "path";
32796
33581
  function blankResolution(parsed) {
32797
33582
  return {
32798
33583
  input: parsed.input,
@@ -32890,7 +33675,7 @@ function resolveFile(parsed, workspace) {
32890
33675
  return resolution;
32891
33676
  }
32892
33677
  resolution.path = relPath;
32893
- if (!existsSync10(absolutePath)) {
33678
+ if (!existsSync11(absolutePath)) {
32894
33679
  resolution.warnings.push("file does not exist in the local workspace");
32895
33680
  return resolution;
32896
33681
  }
@@ -32923,7 +33708,7 @@ function walkSourceFiles(root, current = root, files = []) {
32923
33708
  if (SKIP_DIRS.has(entry.name))
32924
33709
  continue;
32925
33710
  }
32926
- const absolutePath = join8(current, entry.name);
33711
+ const absolutePath = join9(current, entry.name);
32927
33712
  if (entry.isDirectory()) {
32928
33713
  if (!SKIP_DIRS.has(entry.name))
32929
33714
  walkSourceFiles(root, absolutePath, files);
@@ -34190,8 +34975,8 @@ var init_local_notifications = __esm(() => {
34190
34975
  });
34191
34976
 
34192
34977
  // src/lib/local-encryption.ts
34193
- import { createCipheriv, createDecipheriv, createHash as createHash4, randomBytes as randomBytes2, scryptSync, timingSafeEqual as timingSafeEqual2 } from "crypto";
34194
- function now2() {
34978
+ import { createCipheriv, createDecipheriv, createHash as createHash4, randomBytes as randomBytes2, scryptSync, timingSafeEqual as timingSafeEqual3 } from "crypto";
34979
+ function now3() {
34195
34980
  return new Date().toISOString();
34196
34981
  }
34197
34982
  function sha2562(value) {
@@ -34228,7 +35013,7 @@ function upsertEncryptionProfile(input) {
34228
35013
  const name = normalizeProfileName(input.name);
34229
35014
  const config2 = loadConfig();
34230
35015
  const existing = config2.encryption_profiles?.[name];
34231
- const timestamp = now2();
35016
+ const timestamp = now3();
34232
35017
  const profile = {
34233
35018
  name,
34234
35019
  algorithm: "aes-256-gcm",
@@ -34285,7 +35070,7 @@ function encryptString(plaintext, options = {}) {
34285
35070
  return {
34286
35071
  schemaVersion: TODOS_ENCRYPTION_SCHEMA_VERSION,
34287
35072
  kind: TODOS_ENCRYPTED_VALUE_KIND,
34288
- encryptedAt: options.encryptedAt ?? now2(),
35073
+ encryptedAt: options.encryptedAt ?? now3(),
34289
35074
  profile: profile.name,
34290
35075
  key_env: profile.key_env,
34291
35076
  algorithm: "aes-256-gcm",
@@ -34320,7 +35105,7 @@ function decryptString(envelope, env = process.env) {
34320
35105
  ]).toString("utf8");
34321
35106
  const expected = Buffer.from(envelope.plaintext_sha256, "hex");
34322
35107
  const actual = Buffer.from(sha2562(plaintext), "hex");
34323
- if (expected.length !== actual.length || !timingSafeEqual2(expected, actual)) {
35108
+ if (expected.length !== actual.length || !timingSafeEqual3(expected, actual)) {
34324
35109
  throw new EncryptedPayloadError("decrypted payload checksum mismatch");
34325
35110
  }
34326
35111
  return plaintext;
@@ -35843,7 +36628,7 @@ function createRoadmap(input) {
35843
36628
  if (!name)
35844
36629
  throw new Error("Roadmap name is required");
35845
36630
  const store = readStore();
35846
- const now3 = timestamp();
36631
+ const now4 = timestamp();
35847
36632
  const roadmap = {
35848
36633
  id: newId("roadmap"),
35849
36634
  name,
@@ -35854,8 +36639,8 @@ function createRoadmap(input) {
35854
36639
  agent_id: cleanString(input.agent_id),
35855
36640
  release: cleanString(input.release),
35856
36641
  milestone_ids: [],
35857
- created_at: now3,
35858
- updated_at: now3
36642
+ created_at: now4,
36643
+ updated_at: now4
35859
36644
  };
35860
36645
  store.roadmaps[roadmap.id] = roadmap;
35861
36646
  writeStore(store);
@@ -35907,7 +36692,7 @@ function createMilestone(input) {
35907
36692
  const title = input.title.trim();
35908
36693
  if (!title)
35909
36694
  throw new Error("Milestone title is required");
35910
- const now3 = timestamp();
36695
+ const now4 = timestamp();
35911
36696
  const milestone = {
35912
36697
  id: newId("milestone"),
35913
36698
  roadmap_id: roadmapId,
@@ -35922,11 +36707,11 @@ function createMilestone(input) {
35922
36707
  run_ids: cleanList(input.run_ids),
35923
36708
  release: cleanString(input.release ?? roadmap.release ?? undefined),
35924
36709
  tags: cleanList(input.tags),
35925
- created_at: now3,
35926
- updated_at: now3
36710
+ created_at: now4,
36711
+ updated_at: now4
35927
36712
  };
35928
36713
  store.milestones[milestone.id] = milestone;
35929
- store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList([...roadmap.milestone_ids, milestone.id]), updated_at: now3 };
36714
+ store.roadmaps[roadmapId] = { ...roadmap, milestone_ids: cleanList([...roadmap.milestone_ids, milestone.id]), updated_at: now4 };
35930
36715
  writeStore(store);
35931
36716
  return milestone;
35932
36717
  }
@@ -35981,7 +36766,7 @@ function upsertReleaseGroup(input) {
35981
36766
  throw new Error("Release group name is required");
35982
36767
  const key = releaseKey(roadmapId, name);
35983
36768
  const existing = store.releases[key];
35984
- const now3 = timestamp();
36769
+ const now4 = timestamp();
35985
36770
  const release = {
35986
36771
  name,
35987
36772
  version: input.version === undefined ? existing?.version ?? null : cleanString(input.version),
@@ -35992,8 +36777,8 @@ function upsertReleaseGroup(input) {
35992
36777
  plan_ids: input.plan_ids === undefined ? existing?.plan_ids ?? [] : cleanList(input.plan_ids),
35993
36778
  run_ids: input.run_ids === undefined ? existing?.run_ids ?? [] : cleanList(input.run_ids),
35994
36779
  notes: input.notes === undefined ? existing?.notes ?? null : cleanString(input.notes),
35995
- created_at: existing?.created_at ?? now3,
35996
- updated_at: now3
36780
+ created_at: existing?.created_at ?? now4,
36781
+ updated_at: now4
35997
36782
  };
35998
36783
  store.releases[key] = release;
35999
36784
  writeStore(store);
@@ -36192,7 +36977,7 @@ function upsertCapacityProfile(input) {
36192
36977
  const store = readStore2();
36193
36978
  const key = profileKey(agentId, projectId);
36194
36979
  const existing = store.profiles[key];
36195
- const now3 = timestamp2();
36980
+ const now4 = timestamp2();
36196
36981
  const profile = {
36197
36982
  id: existing?.id ?? key,
36198
36983
  agent_id: agentId,
@@ -36200,8 +36985,8 @@ function upsertCapacityProfile(input) {
36200
36985
  minutes_per_day: assertMinutes(input.minutes_per_day),
36201
36986
  working_days: normalizeWorkingDays(input.working_days),
36202
36987
  effective_from: cleanString2(input.effective_from),
36203
- created_at: existing?.created_at ?? now3,
36204
- updated_at: now3
36988
+ created_at: existing?.created_at ?? now4,
36989
+ updated_at: now4
36205
36990
  };
36206
36991
  store.profiles[key] = profile;
36207
36992
  writeStore2(store);
@@ -36610,7 +37395,7 @@ var init_audit_ledger = __esm(() => {
36610
37395
 
36611
37396
  // src/lib/release-compatibility.ts
36612
37397
  import { readFileSync as readFileSync6 } from "fs";
36613
- import { join as join9, resolve as resolve11 } from "path";
37398
+ import { join as join10, resolve as resolve11 } from "path";
36614
37399
  import { Database as Database2 } from "bun:sqlite";
36615
37400
  function pass(id, message, details) {
36616
37401
  return { id, status: "passed", message, details };
@@ -36622,7 +37407,7 @@ function warn(id, message, details) {
36622
37407
  return { id, status: "warning", message, details };
36623
37408
  }
36624
37409
  function readPackageJson2(root) {
36625
- return JSON.parse(readFileSync6(join9(root, "package.json"), "utf8"));
37410
+ return JSON.parse(readFileSync6(join10(root, "package.json"), "utf8"));
36626
37411
  }
36627
37412
  function sortedKeys(value) {
36628
37413
  return Object.keys(value ?? {}).sort((left, right) => left.localeCompare(right));
@@ -36891,6 +37676,9 @@ function hasFts(db) {
36891
37676
  function escapeFtsQuery(q) {
36892
37677
  return q.replace(/["*^()]/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => `"${token}"*`).join(" ");
36893
37678
  }
37679
+ function shouldUseFts(q) {
37680
+ return /^[\p{L}\p{N}_\-\s]+$/u.test(q);
37681
+ }
36894
37682
  function searchTasks(options, projectId, taskListId, db) {
36895
37683
  const opts = typeof options === "string" ? { query: options || undefined, project_id: projectId, task_list_id: taskListId } : options;
36896
37684
  const d = db || getDatabase();
@@ -36899,7 +37687,8 @@ function searchTasks(options, projectId, taskListId, db) {
36899
37687
  let sql;
36900
37688
  const raw = opts.query?.trim() ?? "";
36901
37689
  const q = raw === "*" ? "" : raw;
36902
- if (hasFts(d) && q) {
37690
+ const useFts = hasFts(d) && q && shouldUseFts(q);
37691
+ if (useFts) {
36903
37692
  const ftsQuery = escapeFtsQuery(q);
36904
37693
  sql = `SELECT t.* FROM tasks t
36905
37694
  INNER JOIN tasks_fts fts ON fts.rowid = t.rowid
@@ -36907,8 +37696,16 @@ function searchTasks(options, projectId, taskListId, db) {
36907
37696
  params.push(ftsQuery);
36908
37697
  } else if (q) {
36909
37698
  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);
37699
+ sql = `SELECT * FROM tasks t WHERE (
37700
+ t.id LIKE ?
37701
+ OR t.short_id LIKE ?
37702
+ OR t.title LIKE ?
37703
+ OR t.description LIKE ?
37704
+ OR t.working_dir LIKE ?
37705
+ OR t.metadata LIKE ?
37706
+ OR EXISTS (SELECT 1 FROM task_tags WHERE task_tags.task_id = t.id AND tag LIKE ?)
37707
+ )`;
37708
+ params.push(pattern, pattern, pattern, pattern, pattern, pattern, pattern);
36912
37709
  } else {
36913
37710
  sql = `SELECT * FROM tasks t WHERE 1=1`;
36914
37711
  }
@@ -36964,7 +37761,7 @@ function searchTasks(options, projectId, taskListId, db) {
36964
37761
  } else if (opts.is_blocked === false) {
36965
37762
  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
37763
  }
36967
- if (hasFts(d) && q) {
37764
+ if (useFts) {
36968
37765
  sql += ` ORDER BY bm25(tasks_fts),
36969
37766
  CASE t.priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END,
36970
37767
  t.created_at DESC`;
@@ -40059,7 +40856,7 @@ function registerTaskWorkflowTools(server, ctx) {
40059
40856
  const globalRole = n.agent.role ? ` [${n.agent.role}]` : "";
40060
40857
  const lead = n.is_lead ? " \u2605" : "";
40061
40858
  const lastSeen = new Date(n.agent.last_seen_at).getTime();
40062
- const active = now3 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
40859
+ const active = now4 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
40063
40860
  const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${lead}`;
40064
40861
  const children = n.reports.length > 0 ? `
40065
40862
  ` + render(n.reports, indent + 1) : "";
@@ -40072,7 +40869,7 @@ function registerTaskWorkflowTools(server, ctx) {
40072
40869
  if (format === "json") {
40073
40870
  return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
40074
40871
  }
40075
- const now3 = Date.now();
40872
+ const now4 = Date.now();
40076
40873
  const ACTIVE_MS = 30 * 60 * 1000;
40077
40874
  const text = tree.length > 0 ? render(tree) : "No agents in org chart.";
40078
40875
  return { content: [{ type: "text", text }] };
@@ -40268,14 +41065,14 @@ function registerTaskAutoTools(server, ctx) {
40268
41065
  return { content: [{ type: "text", text: "No agent_id provided and no agent focus is active." }], isError: true };
40269
41066
  }
40270
41067
  const assigned = listTasks3({ assigned_to: effectiveAgentId, limit: 500 }, undefined);
40271
- const now3 = Date.now();
40272
- const dueSoonCutoff = now3 + 24 * 60 * 60 * 1000;
41068
+ const now4 = Date.now();
41069
+ const dueSoonCutoff = now4 + 24 * 60 * 60 * 1000;
40273
41070
  const blocked = getBlockedTasks2().filter((t) => t.assigned_to === effectiveAgentId);
40274
41071
  const workload = {
40275
41072
  in_progress: assigned.filter((t) => t.status === "in_progress").length,
40276
41073
  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,
41074
+ completed_recent: assigned.filter((t) => t.status === "completed" && t.completed_at && now4 - new Date(t.completed_at).getTime() <= 7 * 24 * 60 * 60 * 1000).length,
41075
+ 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
41076
  blocked: blocked.length
40280
41077
  };
40281
41078
  const lines = [
@@ -40502,7 +41299,7 @@ function limits(input) {
40502
41299
  stale_after_hours: clamp(input.stale_after_hours, DEFAULT_LIMITS.stale_after_hours, 24 * 365)
40503
41300
  };
40504
41301
  }
40505
- function truncate(value, max) {
41302
+ function truncate2(value, max) {
40506
41303
  if (!value)
40507
41304
  return value ?? null;
40508
41305
  const redacted = redactEvidenceText(value);
@@ -40523,9 +41320,9 @@ function acceptanceCriteria(task, maxText) {
40523
41320
  const metadata = task.metadata || {};
40524
41321
  const raw = metadata["acceptance_criteria"] ?? metadata["acceptanceCriteria"] ?? metadata["criteria"];
40525
41322
  if (Array.isArray(raw))
40526
- return raw.map((item) => truncate(String(item), maxText)).filter((item) => Boolean(item));
41323
+ return raw.map((item) => truncate2(String(item), maxText)).filter((item) => Boolean(item));
40527
41324
  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));
41325
+ return raw.split(/\r?\n/).map((line) => line.replace(/^[-*]\s*/, "").trim()).filter(Boolean).map((line) => truncate2(line, maxText)).filter((item) => Boolean(item));
40529
41326
  }
40530
41327
  return [];
40531
41328
  }
@@ -40548,7 +41345,7 @@ function addFile(files, path, source, base) {
40548
41345
  path,
40549
41346
  status: base?.status || "active",
40550
41347
  agent_id: base?.agent_id ?? null,
40551
- note: truncate(base?.note, 240),
41348
+ note: truncate2(base?.note, 240),
40552
41349
  updated_at: base?.updated_at || "",
40553
41350
  sources: [source]
40554
41351
  });
@@ -40606,7 +41403,7 @@ function estimateTokens(value) {
40606
41403
  return Math.max(1, Math.ceil((text || "").length / 4));
40607
41404
  }
40608
41405
  function summarizeStrings(values, maxChars) {
40609
- return truncate(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
41406
+ return truncate2(values.filter(Boolean).join("; "), maxChars) || "No local details were available before this section was omitted.";
40610
41407
  }
40611
41408
  function summarizeSection(pack, section, maxChars) {
40612
41409
  if (section === "project")
@@ -40797,8 +41594,8 @@ function createAgentContextPack(input, db) {
40797
41594
  ...taskFiles.map((file) => file.updated_at)
40798
41595
  ], task.updated_at);
40799
41596
  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) {
41597
+ const now4 = input.now ? new Date(input.now) : new Date;
41598
+ if (Date.parse(task.updated_at) < now4.getTime() - limit.stale_after_hours * 60 * 60 * 1000) {
40802
41599
  warnings.push(`task state is older than ${limit.stale_after_hours} hours`);
40803
41600
  }
40804
41601
  if (comments.length > recentComments.length)
@@ -40813,7 +41610,7 @@ function createAgentContextPack(input, db) {
40813
41610
  id: task.id,
40814
41611
  short_id: task.short_id,
40815
41612
  title: redactEvidenceText(task.title),
40816
- description: truncate(task.description, limit.max_text_chars),
41613
+ description: truncate2(task.description, limit.max_text_chars),
40817
41614
  status: task.status,
40818
41615
  priority: task.priority,
40819
41616
  assigned_to: task.assigned_to,
@@ -40837,7 +41634,7 @@ function createAgentContextPack(input, db) {
40837
41634
  plan: plan ? {
40838
41635
  id: plan.id,
40839
41636
  name: plan.name,
40840
- description: truncate(plan.description, limit.max_text_chars),
41637
+ description: truncate2(plan.description, limit.max_text_chars),
40841
41638
  status: plan.status,
40842
41639
  agent_id: plan.agent_id,
40843
41640
  tasks: planTasks.slice(0, limit.plan_task_limit).map(taskSummary).filter((item) => Boolean(item)),
@@ -40856,7 +41653,7 @@ function createAgentContextPack(input, db) {
40856
41653
  type: comment.type,
40857
41654
  progress_pct: comment.progress_pct,
40858
41655
  created_at: comment.created_at,
40859
- content: truncate(comment.content, limit.max_text_chars) || ""
41656
+ content: truncate2(comment.content, limit.max_text_chars) || ""
40860
41657
  })),
40861
41658
  omitted: Math.max(0, comments.length - recentComments.length)
40862
41659
  },
@@ -40864,7 +41661,7 @@ function createAgentContextPack(input, db) {
40864
41661
  traceability: {
40865
41662
  commits: traceability.commits.map((commit) => ({
40866
41663
  sha: commit.sha,
40867
- message: truncate(commit.message, 240),
41664
+ message: truncate2(commit.message, 240),
40868
41665
  files_changed: commit.files_changed,
40869
41666
  committed_at: commit.committed_at
40870
41667
  })),
@@ -40872,7 +41669,7 @@ function createAgentContextPack(input, db) {
40872
41669
  verifications: verifications.map((verification) => ({
40873
41670
  command: verification.command,
40874
41671
  status: verification.status,
40875
- output_summary: truncate(verification.output_summary, limit.max_text_chars),
41672
+ output_summary: truncate2(verification.output_summary, limit.max_text_chars),
40876
41673
  artifact_path: verification.artifact_path,
40877
41674
  run_at: verification.run_at
40878
41675
  })),
@@ -40883,14 +41680,14 @@ function createAgentContextPack(input, db) {
40883
41680
  id: ledger.run.id,
40884
41681
  title: ledger.run.title,
40885
41682
  status: ledger.run.status,
40886
- summary: truncate(ledger.run.summary, limit.max_text_chars),
41683
+ summary: truncate2(ledger.run.summary, limit.max_text_chars),
40887
41684
  agent_id: ledger.run.agent_id,
40888
41685
  started_at: ledger.run.started_at,
40889
41686
  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 }))
41687
+ events: ledger.events.map((event) => ({ event_type: event.event_type, message: truncate2(event.message, 500), created_at: event.created_at })),
41688
+ 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 })),
41689
+ files: ledger.files.map((file) => ({ path: file.path, status: file.status, note: truncate2(file.note, 240) })),
41690
+ artifacts: ledger.artifacts.map((artifact) => ({ path: artifact.path, artifact_type: artifact.artifact_type, description: truncate2(artifact.description, 240), sha256: artifact.sha256 }))
40894
41691
  })),
40895
41692
  omitted: Math.max(0, runs.length - selectedRuns.length)
40896
41693
  },
@@ -40995,7 +41792,7 @@ function renderAgentContextPackCompactMarkdown(pack) {
40995
41792
  const lines = [
40996
41793
  `# Context: ${pack.task.title}`,
40997
41794
  `${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,
41795
+ pack.task.description ? truncate2(pack.task.description, Math.min(pack.limits.summary_char_limit, 700)) : null,
40999
41796
  "",
41000
41797
  "## Must Know",
41001
41798
  bullet([
@@ -56983,7 +57780,7 @@ var require_to_json_schema = __commonJS((exports) => {
56983
57780
  if (val === undefined) {
56984
57781
  if (this.unrepresentable === "throw") {
56985
57782
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
56986
- }
57783
+ } else {}
56987
57784
  } else if (typeof val === "bigint") {
56988
57785
  if (this.unrepresentable === "throw") {
56989
57786
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -66019,7 +66816,7 @@ var init_agent_run_dispatcher = __esm(() => {
66019
66816
  });
66020
66817
 
66021
66818
  // src/lib/verification-providers.ts
66022
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
66819
+ import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
66023
66820
  function normalizeName5(name) {
66024
66821
  const normalized = name.trim().toLowerCase();
66025
66822
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(normalized)) {
@@ -66171,7 +66968,7 @@ Timed out after ${provider.timeout_ms}ms`);
66171
66968
  };
66172
66969
  }
66173
66970
  function runCiLogProvider(input) {
66174
- const text = input.log_text ?? (input.log_path && existsSync11(input.log_path) ? readFileSync7(input.log_path, "utf-8") : "");
66971
+ const text = input.log_text ?? (input.log_path && existsSync12(input.log_path) ? readFileSync7(input.log_path, "utf-8") : "");
66175
66972
  return {
66176
66973
  status: classifyLog(text),
66177
66974
  attempts: 1,
@@ -66183,7 +66980,7 @@ function runBrowserProvider(input) {
66183
66980
  if (!input.artifact_path) {
66184
66981
  return { status: "unknown", attempts: 1, exit_code: null, output_summary: "browser provider needs a screenshot or artifact path" };
66185
66982
  }
66186
- if (!existsSync11(input.artifact_path)) {
66983
+ if (!existsSync12(input.artifact_path)) {
66187
66984
  return { status: "failed", attempts: 1, exit_code: null, output_summary: `artifact not found: ${input.artifact_path}` };
66188
66985
  }
66189
66986
  return {
@@ -67973,12 +68770,12 @@ function summarizeTask(task) {
67973
68770
  };
67974
68771
  }
67975
68772
  function overdueTasks(tasks, nowIso) {
67976
- const now3 = Date.parse(nowIso);
68773
+ const now4 = Date.parse(nowIso);
67977
68774
  return tasks.filter((task) => {
67978
68775
  if (isTerminal2(task) || !task.due_at)
67979
68776
  return false;
67980
68777
  const due = Date.parse(task.due_at);
67981
- return Number.isFinite(due) && due < now3;
68778
+ return Number.isFinite(due) && due < now4;
67982
68779
  });
67983
68780
  }
67984
68781
  function isReady(task, db) {
@@ -70403,8 +71200,8 @@ __export(exports_local_extensions, {
70403
71200
  discoverLocalExtensions: () => discoverLocalExtensions
70404
71201
  });
70405
71202
  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";
71203
+ import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
71204
+ import { basename as basename5, join as join11, resolve as resolve13 } from "path";
70408
71205
  function isObject3(value) {
70409
71206
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
70410
71207
  }
@@ -70663,10 +71460,10 @@ function verifyExtensionSignature(input) {
70663
71460
  }
70664
71461
  function inspectExtensionSource(source3) {
70665
71462
  const resolved = resolve13(source3);
70666
- if (!existsSync12(resolved))
71463
+ if (!existsSync13(resolved))
70667
71464
  throw new Error(`extension source not found: ${source3}`);
70668
71465
  const stat = statSync6(resolved);
70669
- const manifestPath = stat.isDirectory() ? [join10(resolved, "todos.extension.json"), join10(resolved, "extension.json")].find(existsSync12) : resolved;
71466
+ const manifestPath = stat.isDirectory() ? [join11(resolved, "todos.extension.json"), join11(resolved, "extension.json")].find(existsSync13) : resolved;
70670
71467
  if (!manifestPath)
70671
71468
  throw new Error(`extension directory ${source3} is missing todos.extension.json`);
70672
71469
  const raw = readFileSync9(manifestPath);
@@ -70762,20 +71559,20 @@ function projectExtensionSources(projectPath) {
70762
71559
  return [];
70763
71560
  const root = resolve13(projectPath);
70764
71561
  const candidates = [
70765
- join10(root, "todos.extension.json"),
70766
- join10(root, ".todos", "todos.extension.json")
71562
+ join11(root, "todos.extension.json"),
71563
+ join11(root, ".todos", "todos.extension.json")
70767
71564
  ];
70768
- const extensionDir = join10(root, ".todos", "extensions");
70769
- if (existsSync12(extensionDir)) {
71565
+ const extensionDir = join11(root, ".todos", "extensions");
71566
+ if (existsSync13(extensionDir)) {
70770
71567
  for (const entry of readdirSync3(extensionDir)) {
70771
71568
  if (entry.startsWith("."))
70772
71569
  continue;
70773
- const full = join10(extensionDir, entry);
71570
+ const full = join11(extensionDir, entry);
70774
71571
  if (statSync6(full).isDirectory() || entry.endsWith(".json"))
70775
71572
  candidates.push(full);
70776
71573
  }
70777
71574
  }
70778
- return candidates.filter(existsSync12);
71575
+ return candidates.filter(existsSync13);
70779
71576
  }
70780
71577
  function discoverLocalExtensions(options = {}) {
70781
71578
  const config2 = loadConfig();
@@ -74297,7 +75094,7 @@ ${lines.join(`
74297
75094
  const projectRoles = n.project_roles.length > 0 ? ` <${n.project_roles.join(", ")}>` : "";
74298
75095
  const lead = n.is_project_lead ? " \u2605" : "";
74299
75096
  const lastSeen = new Date(n.agent.last_seen_at).getTime();
74300
- const active = now3 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
75097
+ const active = now4 - lastSeen < ACTIVE_MS ? " \u25CF" : " \u25CB";
74301
75098
  const line = `${prefix}${active} ${n.agent.name}${title}${globalRole}${projectRoles}${lead}`;
74302
75099
  const children = n.reports.length > 0 ? `
74303
75100
  ` + render(n.reports, indent + 1) : "";
@@ -74311,7 +75108,7 @@ ${lines.join(`
74311
75108
  if (format === "json") {
74312
75109
  return { content: [{ type: "text", text: JSON.stringify(tree, null, 2) }] };
74313
75110
  }
74314
- const now3 = Date.now();
75111
+ const now4 = Date.now();
74315
75112
  const ACTIVE_MS = 30 * 60 * 1000;
74316
75113
  const text2 = tree.length > 0 ? render(tree) : "No agents in this project's org chart.";
74317
75114
  return { content: [{ type: "text", text: text2 }] };
@@ -74861,10 +75658,10 @@ ${lines.join(`
74861
75658
  });
74862
75659
  }
74863
75660
  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 }) => {
75661
+ 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
75662
  try {
74866
75663
  const { getIdleFocusSessionPrompts: getIdleFocusSessionPrompts2 } = (init_tasks(), __toCommonJS(exports_tasks));
74867
- return { content: [{ type: "text", text: JSON.stringify(getIdleFocusSessionPrompts2({ agent_id, now: now3 }), null, 2) }] };
75664
+ return { content: [{ type: "text", text: JSON.stringify(getIdleFocusSessionPrompts2({ agent_id, now: now4 }), null, 2) }] };
74868
75665
  } catch (e) {
74869
75666
  return { content: [{ type: "text", text: formatError2(e) }], isError: true };
74870
75667
  }
@@ -75059,9 +75856,9 @@ __export(exports_extract, {
75059
75856
  buildCodebaseIndex: () => buildCodebaseIndex,
75060
75857
  EXTRACT_TAGS: () => EXTRACT_TAGS
75061
75858
  });
75062
- import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
75859
+ import { existsSync as existsSync14, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
75063
75860
  import { createHash as createHash11 } from "crypto";
75064
- import { relative as relative5, resolve as resolve14, join as join11 } from "path";
75861
+ import { relative as relative5, resolve as resolve14, join as join12 } from "path";
75065
75862
  function stableHash(value) {
75066
75863
  return createHash11("sha256").update(value).digest("hex");
75067
75864
  }
@@ -75070,8 +75867,8 @@ function normalizePathForMatch(value) {
75070
75867
  }
75071
75868
  function readGitignorePatterns(basePath) {
75072
75869
  const root = statSync7(basePath).isFile() ? resolve14(basePath, "..") : basePath;
75073
- const gitignorePath = join11(root, ".gitignore");
75074
- if (!existsSync13(gitignorePath))
75870
+ const gitignorePath = join12(root, ".gitignore");
75871
+ if (!existsSync14(gitignorePath))
75075
75872
  return [];
75076
75873
  try {
75077
75874
  return readFileSync10(gitignorePath, "utf-8").split(`
@@ -75213,7 +76010,7 @@ function buildCodebaseIndex(options) {
75213
76010
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
75214
76011
  const indexed = [];
75215
76012
  for (const file of files) {
75216
- const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
76013
+ const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
75217
76014
  try {
75218
76015
  const source3 = readFileSync10(fullPath, "utf-8");
75219
76016
  const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
@@ -75244,7 +76041,7 @@ function extractTodos(options, db) {
75244
76041
  const files = collectFiles(basePath, extensions, excludes, respectGitignore);
75245
76042
  const allComments = [];
75246
76043
  for (const file of files) {
75247
- const fullPath = statSync7(basePath).isFile() ? basePath : join11(basePath, file);
76044
+ const fullPath = statSync7(basePath).isFile() ? basePath : join12(basePath, file);
75248
76045
  try {
75249
76046
  const source3 = readFileSync10(fullPath, "utf-8");
75250
76047
  const relPath = statSync7(basePath).isFile() ? relative5(resolve14(basePath, ".."), fullPath) : file;
@@ -76137,7 +76934,7 @@ __export(exports_builtin_templates, {
76137
76934
  BUILTIN_TEMPLATES: () => BUILTIN_TEMPLATES
76138
76935
  });
76139
76936
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync4 } from "fs";
76140
- import { join as join12 } from "path";
76937
+ import { join as join13 } from "path";
76141
76938
  function templateMetadata(template) {
76142
76939
  return {
76143
76940
  source: BUILTIN_TEMPLATE_LIBRARY_SOURCE,
@@ -76196,7 +76993,7 @@ function writeBuiltinTemplateFiles(directory) {
76196
76993
  mkdirSync7(directory, { recursive: true });
76197
76994
  const files = [];
76198
76995
  for (const entry of exportBuiltinTemplateFiles()) {
76199
- const path = join12(directory, entry.filename);
76996
+ const path = join13(directory, entry.filename);
76200
76997
  writeFileSync4(path, `${JSON.stringify(entry.template, null, 2)}
76201
76998
  `, "utf-8");
76202
76999
  files.push(path);
@@ -76722,16 +77519,16 @@ __export(exports_environment_snapshots, {
76722
77519
  captureEnvironmentSnapshot: () => captureEnvironmentSnapshot
76723
77520
  });
76724
77521
  import { createHash as createHash12 } from "crypto";
76725
- import { existsSync as existsSync14, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
77522
+ import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
76726
77523
  import { hostname as hostname3, platform, arch } from "os";
76727
- import { dirname as dirname9, join as join13, resolve as resolve15 } from "path";
77524
+ import { dirname as dirname9, join as join14, resolve as resolve15 } from "path";
76728
77525
  import { tmpdir as tmpdir2 } from "os";
76729
77526
  function sha2566(value) {
76730
77527
  return createHash12("sha256").update(value).digest("hex");
76731
77528
  }
76732
77529
  function fileRecord(root, relativePath) {
76733
- const path = join13(root, relativePath);
76734
- if (!existsSync14(path))
77530
+ const path = join14(root, relativePath);
77531
+ if (!existsSync15(path))
76735
77532
  return null;
76736
77533
  const stat = statSync8(path);
76737
77534
  if (!stat.isFile())
@@ -76743,7 +77540,7 @@ function manifestRecord(root, relativePath) {
76743
77540
  const base = fileRecord(root, relativePath);
76744
77541
  if (!base)
76745
77542
  return null;
76746
- const parsed = readJsonFile(join13(root, relativePath));
77543
+ const parsed = readJsonFile(join14(root, relativePath));
76747
77544
  if (!parsed)
76748
77545
  return { ...base, redacted: {} };
76749
77546
  const redacted = redactValue({
@@ -76838,8 +77635,8 @@ function commandEnv(env, includeValues) {
76838
77635
  function defaultSnapshotDir() {
76839
77636
  const dbPath = getDatabasePath();
76840
77637
  if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
76841
- return join13(tmpdir2(), "hasna-todos", "environment-snapshots");
76842
- return join13(dirname9(resolve15(dbPath)), "environment-snapshots");
77638
+ return join14(tmpdir2(), "hasna-todos", "environment-snapshots");
77639
+ return join14(dirname9(resolve15(dbPath)), "environment-snapshots");
76843
77640
  }
76844
77641
  function snapshotWithId(snapshot) {
76845
77642
  const digest = sha2566(JSON.stringify(snapshot)).slice(0, 24);
@@ -76886,7 +77683,7 @@ function captureEnvironmentSnapshot(input = {}) {
76886
77683
  });
76887
77684
  }
76888
77685
  function writeEnvironmentSnapshot(snapshot, outputPath) {
76889
- const path = outputPath ? resolve15(outputPath) : join13(defaultSnapshotDir(), `${snapshot.id}.json`);
77686
+ const path = outputPath ? resolve15(outputPath) : join14(defaultSnapshotDir(), `${snapshot.id}.json`);
76890
77687
  ensureDir2(dirname9(path));
76891
77688
  writeJsonFile(path, snapshot);
76892
77689
  return path;
@@ -77532,27 +78329,27 @@ __export(exports_serve, {
77532
78329
  SECURITY_HEADERS: () => SECURITY_HEADERS,
77533
78330
  MIME_TYPES: () => MIME_TYPES
77534
78331
  });
77535
- import { existsSync as existsSync15 } from "fs";
77536
- import { join as join14, dirname as dirname10, extname } from "path";
78332
+ import { existsSync as existsSync16 } from "fs";
78333
+ import { join as join15, dirname as dirname10, extname } from "path";
77537
78334
  import { fileURLToPath as fileURLToPath2 } from "url";
77538
78335
  function resolveDashboardDir() {
77539
78336
  const candidates = [];
77540
78337
  try {
77541
78338
  const scriptDir = dirname10(fileURLToPath2(import.meta.url));
77542
- candidates.push(join14(scriptDir, "..", "dashboard", "dist"));
77543
- candidates.push(join14(scriptDir, "..", "..", "dashboard", "dist"));
78339
+ candidates.push(join15(scriptDir, "..", "dashboard", "dist"));
78340
+ candidates.push(join15(scriptDir, "..", "..", "dashboard", "dist"));
77544
78341
  } catch {}
77545
78342
  if (process.argv[1]) {
77546
78343
  const mainDir = dirname10(process.argv[1]);
77547
- candidates.push(join14(mainDir, "..", "dashboard", "dist"));
77548
- candidates.push(join14(mainDir, "..", "..", "dashboard", "dist"));
78344
+ candidates.push(join15(mainDir, "..", "dashboard", "dist"));
78345
+ candidates.push(join15(mainDir, "..", "..", "dashboard", "dist"));
77549
78346
  }
77550
- candidates.push(join14(process.cwd(), "dashboard", "dist"));
78347
+ candidates.push(join15(process.cwd(), "dashboard", "dist"));
77551
78348
  for (const candidate of candidates) {
77552
- if (existsSync15(candidate))
78349
+ if (existsSync16(candidate))
77553
78350
  return candidate;
77554
78351
  }
77555
- return join14(process.cwd(), "dashboard", "dist");
78352
+ return join15(process.cwd(), "dashboard", "dist");
77556
78353
  }
77557
78354
  function getProvidedApiKey(req) {
77558
78355
  const headerKey = req.headers.get("x-api-key");
@@ -77579,15 +78376,15 @@ function checkAuth(req, apiKey) {
77579
78376
  return null;
77580
78377
  }
77581
78378
  function checkRateLimit(ip) {
77582
- const now3 = Date.now();
78379
+ const now4 = Date.now();
77583
78380
  const entry = rateLimitMap.get(ip);
77584
- if (!entry || now3 > entry.resetAt) {
77585
- rateLimitMap.set(ip, { count: 1, resetAt: now3 + RATE_LIMIT_WINDOW_MS });
78381
+ if (!entry || now4 > entry.resetAt) {
78382
+ rateLimitMap.set(ip, { count: 1, resetAt: now4 + RATE_LIMIT_WINDOW_MS });
77586
78383
  return { allowed: true };
77587
78384
  }
77588
78385
  entry.count++;
77589
78386
  if (entry.count > RATE_LIMIT_MAX) {
77590
- return { allowed: false, retryAfter: Math.ceil((entry.resetAt - now3) / 1000) };
78387
+ return { allowed: false, retryAfter: Math.ceil((entry.resetAt - now4) / 1000) };
77591
78388
  }
77592
78389
  return { allowed: true };
77593
78390
  }
@@ -77602,7 +78399,7 @@ function json(data, status = 200, headers) {
77602
78399
  });
77603
78400
  }
77604
78401
  function serveStaticFile(filePath) {
77605
- if (!existsSync15(filePath))
78402
+ if (!existsSync16(filePath))
77606
78403
  return null;
77607
78404
  const ext = extname(filePath);
77608
78405
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -77681,7 +78478,7 @@ data: ${data}
77681
78478
  filteredSseClients.delete(client);
77682
78479
  }
77683
78480
  const dashboardDir = resolveDashboardDir();
77684
- const dashboardExists = existsSync15(dashboardDir);
78481
+ const dashboardExists = existsSync16(dashboardDir);
77685
78482
  if (!dashboardExists) {
77686
78483
  console.error(`
77687
78484
  Dashboard not found at: ${dashboardDir}`);