@hasna/todos 0.11.64 → 0.11.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -6914,38 +6914,7 @@ var init_task_lists = __esm(() => {
6914
6914
  init_projects();
6915
6915
  });
6916
6916
 
6917
- // src/lib/shared-events.ts
6918
- function taskEventData(task, extra = {}) {
6919
- return {
6920
- id: task.id,
6921
- task_id: task.id,
6922
- short_id: task.short_id,
6923
- title: task.title,
6924
- description: task.description,
6925
- status: task.status,
6926
- priority: task.priority,
6927
- project_id: task.project_id,
6928
- parent_id: task.parent_id,
6929
- plan_id: task.plan_id,
6930
- task_list_id: task.task_list_id,
6931
- agent_id: task.agent_id,
6932
- assigned_to: task.assigned_to,
6933
- session_id: task.session_id,
6934
- working_dir: task.working_dir,
6935
- tags: task.tags,
6936
- metadata: task.metadata,
6937
- version: task.version,
6938
- created_at: task.created_at,
6939
- updated_at: task.updated_at,
6940
- started_at: task.started_at,
6941
- completed_at: task.completed_at,
6942
- due_at: task.due_at,
6943
- requires_approval: task.requires_approval,
6944
- approved_by: task.approved_by,
6945
- approved_at: task.approved_at,
6946
- ...extra
6947
- };
6948
- }
6917
+ // src/lib/task-route-contract.ts
6949
6918
  function booleanField(value) {
6950
6919
  if (typeof value === "boolean")
6951
6920
  return value;
@@ -6967,21 +6936,31 @@ function booleanField(value) {
6967
6936
  function objectField(value) {
6968
6937
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
6969
6938
  }
6970
- function firstBoolean(records, keys) {
6939
+ function collectBooleans(records, keys) {
6940
+ const values = [];
6971
6941
  for (const record of records) {
6942
+ if (!record)
6943
+ continue;
6972
6944
  for (const key of keys) {
6973
6945
  const value = booleanField(record[key]);
6974
6946
  if (value !== undefined)
6975
- return value;
6947
+ values.push(value);
6976
6948
  }
6977
6949
  }
6978
- return;
6950
+ return values;
6979
6951
  }
6980
- function routingAutomationMetadata(task) {
6981
- const automation = objectField(task.metadata.automation);
6982
- const records = [task.metadata];
6983
- if (automation)
6984
- records.push(automation);
6952
+ function mergedBoolean(records, keys, trueWins) {
6953
+ const values = collectBooleans(records, keys);
6954
+ if (values.length === 0)
6955
+ return;
6956
+ if (trueWins)
6957
+ return values.some(Boolean);
6958
+ return values.includes(false) ? false : true;
6959
+ }
6960
+ function routingAutomationMetadata(task, taskList) {
6961
+ const taskAutomation = objectField(task.metadata.automation);
6962
+ const taskListAutomation = taskList ? objectField(taskList.metadata.automation) : undefined;
6963
+ const records = [task.metadata, taskAutomation, taskList?.metadata, taskListAutomation];
6985
6964
  const result = {};
6986
6965
  const aliases = [
6987
6966
  ["allowed", ["allowed", "automation_allowed", "automationAllowed"]],
@@ -6992,7 +6971,7 @@ function routingAutomationMetadata(task) {
6992
6971
  ["approval_required", ["approval_required", "approvalRequired"]]
6993
6972
  ];
6994
6973
  for (const [canonical, keys] of aliases) {
6995
- const value = firstBoolean(records, keys);
6974
+ const value = mergedBoolean(records, keys, canonical !== "allowed");
6996
6975
  if (value !== undefined)
6997
6976
  result[canonical] = value;
6998
6977
  }
@@ -7000,23 +6979,91 @@ function routingAutomationMetadata(task) {
7000
6979
  result.requires_approval = true;
7001
6980
  return Object.keys(result).length > 0 ? result : undefined;
7002
6981
  }
6982
+ function routeEnabledForTask(task, taskList) {
6983
+ const explicit = booleanField(task.metadata.route_enabled);
6984
+ if (explicit !== undefined)
6985
+ return explicit;
6986
+ if (task.tags.includes("auto:route") || task.tags.includes("route:enabled"))
6987
+ return true;
6988
+ const taskListDefault = taskList ? booleanField(taskList.metadata.route_enabled) : undefined;
6989
+ if (taskListDefault !== undefined)
6990
+ return taskListDefault;
6991
+ return;
6992
+ }
6993
+ function stringField(value) {
6994
+ return typeof value === "string" && value.trim() ? value : undefined;
6995
+ }
6996
+ function workflowPointersFromMetadata(metadata) {
6997
+ const nested = objectField(metadata.workflow_invocation) ?? objectField(metadata.workflow) ?? {};
6998
+ return {
6999
+ current_workflow_invocation_id: stringField(metadata.current_workflow_invocation_id) ?? stringField(nested.current_workflow_invocation_id) ?? stringField(nested.invocation_id),
7000
+ current_run_id: stringField(metadata.current_run_id) ?? stringField(nested.current_run_id) ?? stringField(nested.run_id),
7001
+ latest_manifest_path: stringField(metadata.latest_manifest_path) ?? stringField(nested.latest_manifest_path) ?? stringField(nested.manifest_path),
7002
+ latest_evaluation_path: stringField(metadata.latest_evaluation_path) ?? stringField(nested.latest_evaluation_path) ?? stringField(nested.evaluation_path),
7003
+ workflow_state: stringField(metadata.workflow_state) ?? stringField(nested.workflow_state) ?? stringField(nested.state)
7004
+ };
7005
+ }
7006
+ function compactWorkflowPointers(pointers) {
7007
+ return Object.fromEntries(Object.entries(pointers).filter(([, value]) => typeof value === "string" && value.length > 0));
7008
+ }
7009
+ function classifyProjectKind(path) {
7010
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
7011
+ }
7012
+ function isWorktreePath(path) {
7013
+ return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
7014
+ }
7015
+ function inferRootProjectId(project) {
7016
+ return isWorktreePath(project.path) ? null : project.id;
7017
+ }
7018
+ var TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION = "todos.task_route_state.v1", TASK_WORKFLOW_POINTER_SCHEMA_VERSION = "todos.task_workflow_pointer.v1";
7019
+
7020
+ // src/lib/shared-events.ts
7021
+ function taskEventData(task, extra = {}) {
7022
+ return {
7023
+ id: task.id,
7024
+ task_id: task.id,
7025
+ short_id: task.short_id,
7026
+ title: task.title,
7027
+ description: task.description,
7028
+ status: task.status,
7029
+ priority: task.priority,
7030
+ project_id: task.project_id,
7031
+ parent_id: task.parent_id,
7032
+ plan_id: task.plan_id,
7033
+ task_list_id: task.task_list_id,
7034
+ agent_id: task.agent_id,
7035
+ assigned_to: task.assigned_to,
7036
+ session_id: task.session_id,
7037
+ working_dir: task.working_dir,
7038
+ tags: task.tags,
7039
+ metadata: task.metadata,
7040
+ version: task.version,
7041
+ created_at: task.created_at,
7042
+ updated_at: task.updated_at,
7043
+ started_at: task.started_at,
7044
+ completed_at: task.completed_at,
7045
+ due_at: task.due_at,
7046
+ requires_approval: task.requires_approval,
7047
+ approved_by: task.approved_by,
7048
+ approved_at: task.approved_at,
7049
+ ...extra
7050
+ };
7051
+ }
7003
7052
  function taskEventMetadata(task) {
7004
7053
  const metadata = {
7005
7054
  package: "@hasna/todos",
7006
7055
  todos_event_schema_version: 1,
7056
+ route_state_schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
7007
7057
  task_id: task.id,
7008
7058
  task_short_id: task.short_id,
7009
7059
  project_id: task.project_id,
7010
7060
  task_list_id: task.task_list_id,
7011
7061
  working_dir: task.working_dir
7012
7062
  };
7013
- const routeEnabled = booleanField(task.metadata.route_enabled);
7014
- if (routeEnabled !== undefined) {
7015
- metadata.route_enabled = routeEnabled;
7016
- }
7017
- const automation = routingAutomationMetadata(task);
7018
- if (automation) {
7019
- metadata.automation = automation;
7063
+ const pointers = workflowPointersFromMetadata(task.metadata);
7064
+ for (const [key, value] of Object.entries(pointers)) {
7065
+ if (value)
7066
+ metadata[key] = value;
7020
7067
  }
7021
7068
  try {
7022
7069
  const project = task.project_id ? getProject(task.project_id) : null;
@@ -7045,18 +7092,20 @@ function taskEventMetadata(task) {
7045
7092
  metadata.task_list_project_id = taskList.project_id;
7046
7093
  metadata.task_list_is_project_default = Boolean(project?.task_list_id && taskList.slug === project.task_list_id);
7047
7094
  }
7095
+ const routeEnabled = routeEnabledForTask(task, taskList);
7096
+ if (routeEnabled !== undefined) {
7097
+ metadata.route_enabled = routeEnabled;
7098
+ }
7099
+ const automation = routingAutomationMetadata(task, taskList);
7100
+ if (automation) {
7101
+ metadata.automation = automation;
7102
+ metadata.route_blocked_by_no_auto = automation.no_auto === true;
7103
+ metadata.route_blocked_by_manual = automation.manual === true || automation.manual_required === true;
7104
+ metadata.route_blocked_by_approval = (automation.requires_approval === true || automation.approval_required === true) && !task.approved_by;
7105
+ }
7048
7106
  } catch {}
7049
7107
  return metadata;
7050
7108
  }
7051
- function classifyProjectKind(path) {
7052
- return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
7053
- }
7054
- function isWorktreePath(path) {
7055
- return path.includes("/.codewith/worktrees/") || path.includes("/.worktrees/");
7056
- }
7057
- function inferRootProjectId(project) {
7058
- return isWorktreePath(project.path) ? null : project.id;
7059
- }
7060
7109
  function readMachineLocalPath(project) {
7061
7110
  const machineId = process.env["TODOS_MACHINE_ID"];
7062
7111
  if (!machineId)
@@ -12298,6 +12347,184 @@ var init_helpers = __esm(() => {
12298
12347
  };
12299
12348
  });
12300
12349
 
12350
+ // src/lib/task-routing.ts
12351
+ var exports_task_routing = {};
12352
+ __export(exports_task_routing, {
12353
+ setTaskWorkflowPointers: () => setTaskWorkflowPointers,
12354
+ getTaskRouteState: () => getTaskRouteState
12355
+ });
12356
+ function classifyProjectKind2(path) {
12357
+ if (!path)
12358
+ return null;
12359
+ return path.includes("/hasna/opensource/") ? "open-source" : "unknown";
12360
+ }
12361
+ function machineLocalPath(project, db) {
12362
+ const machineId = process.env["TODOS_MACHINE_ID"];
12363
+ if (!machineId)
12364
+ return null;
12365
+ try {
12366
+ const row = db.query("SELECT path FROM project_machine_paths WHERE project_id = ? AND machine_id = ?").get(project.id, machineId);
12367
+ return row?.path ?? null;
12368
+ } catch {
12369
+ return null;
12370
+ }
12371
+ }
12372
+ function resolveProject(task, db) {
12373
+ const project = task.project_id ? getProject(task.project_id, db) : null;
12374
+ const projectPath = project ? machineLocalPath(project, db) ?? project.path : task.working_dir;
12375
+ return { project, projectPath: projectPath ?? null };
12376
+ }
12377
+ function resolveTaskList(task, project, db) {
12378
+ if (task.task_list_id) {
12379
+ return getTaskList(task.task_list_id, db) ?? (project ? getTaskListBySlug(task.task_list_id, project.id, db) : null);
12380
+ }
12381
+ if (project?.task_list_id) {
12382
+ return getTaskListBySlug(project.task_list_id, project.id, db);
12383
+ }
12384
+ return null;
12385
+ }
12386
+ function isTerminal(status) {
12387
+ return status === "completed" || status === "cancelled" || status === "failed";
12388
+ }
12389
+ function routeConcurrencyKey(task, project, taskList, projectPath) {
12390
+ if (project?.id)
12391
+ return `project:${project.id}`;
12392
+ if (taskList?.id)
12393
+ return `task-list:${taskList.id}`;
12394
+ if (projectPath)
12395
+ return `path:${projectPath}`;
12396
+ return `task:${task.id}`;
12397
+ }
12398
+ function getTaskRouteState(taskOrId, db) {
12399
+ const d = db || getDatabase();
12400
+ const task = typeof taskOrId === "string" ? getTask(taskOrId, d) : taskOrId;
12401
+ if (!task)
12402
+ throw new Error(`Task not found: ${taskOrId}`);
12403
+ const { project, projectPath } = resolveProject(task, d);
12404
+ const taskList = resolveTaskList(task, project, d);
12405
+ const automation = routingAutomationMetadata(task, taskList) ?? {};
12406
+ const routeEnabled = routeEnabledForTask(task, taskList) === true;
12407
+ const tagOptIn = task.tags.includes("auto:route") || task.tags.includes("route:enabled");
12408
+ const locked = Boolean(task.locked_by && !isLockExpired(task.locked_at));
12409
+ const blockers = getBlockingDeps(task.id, d);
12410
+ const blocked = blockers.length > 0;
12411
+ const terminal = isTerminal(task.status);
12412
+ const requiresApproval = automation.requires_approval === true || task.requires_approval === true;
12413
+ const approvalRequired = automation.approval_required === true;
12414
+ const approved = Boolean(task.approved_by);
12415
+ const gates = {
12416
+ route_enabled: routeEnabled,
12417
+ tag_opt_in: tagOptIn,
12418
+ no_auto: automation.no_auto === true,
12419
+ manual: automation.manual === true,
12420
+ manual_required: automation.manual_required === true,
12421
+ requires_approval: requiresApproval,
12422
+ approval_required: approvalRequired,
12423
+ approved,
12424
+ locked,
12425
+ blocked,
12426
+ terminal
12427
+ };
12428
+ const reasons = [];
12429
+ if (task.status !== "pending")
12430
+ reasons.push("task_not_pending");
12431
+ if (terminal)
12432
+ reasons.push("task_terminal");
12433
+ if (!routeEnabled)
12434
+ reasons.push("route_not_enabled");
12435
+ if (locked)
12436
+ reasons.push("task_locked");
12437
+ if (blocked)
12438
+ reasons.push("task_blocked");
12439
+ if (gates.no_auto)
12440
+ reasons.push("no_auto");
12441
+ if (gates.manual)
12442
+ reasons.push("manual");
12443
+ if (gates.manual_required)
12444
+ reasons.push("manual_required");
12445
+ if (requiresApproval && !approved)
12446
+ reasons.push("requires_approval");
12447
+ if (approvalRequired && !approved)
12448
+ reasons.push("approval_required");
12449
+ if (automation.allowed === false)
12450
+ reasons.push("automation_disallowed");
12451
+ return {
12452
+ schema_version: TODOS_TASK_ROUTE_STATE_SCHEMA_VERSION,
12453
+ task_id: task.id,
12454
+ task_short_id: task.short_id,
12455
+ status: task.status,
12456
+ eligible: reasons.length === 0,
12457
+ reasons,
12458
+ blockers: blockers.map((blocker) => ({
12459
+ id: blocker.id,
12460
+ short_id: blocker.short_id,
12461
+ title: blocker.title,
12462
+ status: blocker.status
12463
+ })),
12464
+ gates,
12465
+ automation: Object.keys(automation).length > 0 ? automation : null,
12466
+ route: {
12467
+ project_id: project?.id ?? task.project_id,
12468
+ project_path: projectPath,
12469
+ working_dir: task.working_dir ?? projectPath,
12470
+ project_kind: classifyProjectKind2(projectPath),
12471
+ task_list_id: taskList?.id ?? task.task_list_id,
12472
+ task_list_slug: taskList?.slug ?? null,
12473
+ task_list_name: taskList?.name ?? null,
12474
+ concurrency_key: routeConcurrencyKey(task, project, taskList, projectPath)
12475
+ },
12476
+ pointers: workflowPointersFromMetadata(task.metadata)
12477
+ };
12478
+ }
12479
+ function setTaskWorkflowPointers(taskId, input, db) {
12480
+ const d = db || getDatabase();
12481
+ const task = getTask(taskId, d);
12482
+ if (!task)
12483
+ throw new Error(`Task not found: ${taskId}`);
12484
+ const previous = workflowPointersFromMetadata(task.metadata);
12485
+ const next = compactWorkflowPointers({
12486
+ current_workflow_invocation_id: pointerPatch(previous.current_workflow_invocation_id, input, "current_workflow_invocation_id"),
12487
+ current_run_id: pointerPatch(previous.current_run_id, input, "current_run_id"),
12488
+ latest_manifest_path: pointerPatch(previous.latest_manifest_path, input, "latest_manifest_path"),
12489
+ latest_evaluation_path: pointerPatch(previous.latest_evaluation_path, input, "latest_evaluation_path"),
12490
+ workflow_state: pointerPatch(previous.workflow_state, input, "workflow_state")
12491
+ });
12492
+ const timestamp = now();
12493
+ const {
12494
+ current_workflow_invocation_id,
12495
+ current_run_id,
12496
+ latest_manifest_path,
12497
+ latest_evaluation_path,
12498
+ workflow_state,
12499
+ workflow_invocation,
12500
+ ...baseMetadata
12501
+ } = task.metadata;
12502
+ const metadata = {
12503
+ ...baseMetadata,
12504
+ ...next,
12505
+ workflow_invocation: {
12506
+ schema_version: TASK_WORKFLOW_POINTER_SCHEMA_VERSION,
12507
+ ...next,
12508
+ updated_at: timestamp,
12509
+ updated_by: input.actor ?? null
12510
+ }
12511
+ };
12512
+ return updateTask(task.id, { version: task.version, metadata }, d);
12513
+ }
12514
+ function pointerPatch(previous, input, key) {
12515
+ if (!Object.prototype.hasOwnProperty.call(input, key))
12516
+ return previous;
12517
+ const value = input[key];
12518
+ return typeof value === "string" && value.trim() ? value : undefined;
12519
+ }
12520
+ var init_task_routing = __esm(() => {
12521
+ init_database();
12522
+ init_projects();
12523
+ init_task_crud();
12524
+ init_task_lifecycle();
12525
+ init_task_lists();
12526
+ });
12527
+
12301
12528
  // src/cli/commands/task-commands.ts
12302
12529
  var exports_task_commands = {};
12303
12530
  __export(exports_task_commands, {
@@ -12367,6 +12594,11 @@ function parseJsonValue(value) {
12367
12594
  return value;
12368
12595
  }
12369
12596
  }
12597
+ function pointerOption(value, clear) {
12598
+ if (value !== undefined)
12599
+ return value;
12600
+ return clear ? null : undefined;
12601
+ }
12370
12602
  function parseTags(value) {
12371
12603
  return value ? value.split(",").map((tag) => tag.trim()).filter(Boolean) : undefined;
12372
12604
  }
@@ -12485,6 +12717,63 @@ function registerTaskCommands(program2) {
12485
12717
  console.log(formatTaskLine(result.task));
12486
12718
  }
12487
12719
  });
12720
+ task.command("route-state <id>").description("Show deterministic routing eligibility and workflow pointers for a task").action(async (id) => {
12721
+ const globalOpts = program2.opts();
12722
+ const resolvedId = resolveTaskId(id);
12723
+ const { getTaskRouteState: getTaskRouteState2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
12724
+ let state;
12725
+ try {
12726
+ state = getTaskRouteState2(resolvedId);
12727
+ } catch (e) {
12728
+ handleError(e);
12729
+ }
12730
+ if (globalOpts.json) {
12731
+ output(state, true);
12732
+ return;
12733
+ }
12734
+ console.log(chalk2.bold("Task route state"));
12735
+ console.log(` ${chalk2.dim("Task:")} ${state.task_short_id || state.task_id.slice(0, 8)}`);
12736
+ console.log(` ${chalk2.dim("Eligible:")} ${state.eligible ? chalk2.green("yes") : chalk2.yellow("no")}`);
12737
+ console.log(` ${chalk2.dim("Reasons:")} ${state.reasons.length > 0 ? state.reasons.join(", ") : "none"}`);
12738
+ console.log(` ${chalk2.dim("Route:")} ${state.route.concurrency_key}`);
12739
+ if (state.pointers.current_workflow_invocation_id) {
12740
+ console.log(` ${chalk2.dim("Invocation:")} ${state.pointers.current_workflow_invocation_id}`);
12741
+ }
12742
+ if (state.pointers.current_run_id) {
12743
+ console.log(` ${chalk2.dim("Run:")} ${state.pointers.current_run_id}`);
12744
+ }
12745
+ if (state.pointers.latest_manifest_path) {
12746
+ console.log(` ${chalk2.dim("Manifest:")} ${state.pointers.latest_manifest_path}`);
12747
+ }
12748
+ });
12749
+ task.command("workflow-pointers <id>").description("Update OpenLoops workflow invocation/run artifact pointers on a task").option("--invocation <id>", "Current workflow invocation ID").option("--run <id>", "Current workflow run ID").option("--manifest <path>", "Latest run manifest path").option("--evaluation <path>", "Latest evaluator artifact path").option("--state <state>", "Human-visible workflow state").option("--actor <agent>", "Agent or workflow updating the pointers").option("--clear", "Clear all workflow pointers before applying explicit pointer values").option("--clear-invocation", "Clear current workflow invocation ID").option("--clear-run", "Clear current workflow run ID").option("--clear-manifest", "Clear latest run manifest path").option("--clear-evaluation", "Clear latest evaluator artifact path").option("--clear-state", "Clear human-visible workflow state").action(async (id, opts) => {
12750
+ const globalOpts = program2.opts();
12751
+ const resolvedId = resolveTaskId(id);
12752
+ const { getTaskRouteState: getTaskRouteState2, setTaskWorkflowPointers: setTaskWorkflowPointers2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
12753
+ let taskResult;
12754
+ try {
12755
+ taskResult = setTaskWorkflowPointers2(resolvedId, {
12756
+ current_workflow_invocation_id: pointerOption(opts.invocation, Boolean(opts.clear || opts.clearInvocation)),
12757
+ current_run_id: pointerOption(opts.run, Boolean(opts.clear || opts.clearRun)),
12758
+ latest_manifest_path: pointerOption(opts.manifest, Boolean(opts.clear || opts.clearManifest)),
12759
+ latest_evaluation_path: pointerOption(opts.evaluation, Boolean(opts.clear || opts.clearEvaluation)),
12760
+ workflow_state: pointerOption(opts.state, Boolean(opts.clear || opts.clearState)),
12761
+ actor: opts.actor || globalOpts.agent || "cli"
12762
+ });
12763
+ } catch (e) {
12764
+ handleError(e);
12765
+ }
12766
+ const state = getTaskRouteState2(taskResult.id);
12767
+ if (globalOpts.json) {
12768
+ output({ task: taskResult, route_state: state }, true);
12769
+ return;
12770
+ }
12771
+ console.log(chalk2.green("Workflow pointers updated:"));
12772
+ console.log(formatTaskLine(taskResult));
12773
+ if (state.pointers.latest_manifest_path) {
12774
+ console.log(` ${chalk2.dim("Manifest:")} ${state.pointers.latest_manifest_path}`);
12775
+ }
12776
+ });
12488
12777
  program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").option("--task-list <id>", "Filter by task list ID (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").option("--due-today", "Only tasks due today or earlier").option("--overdue", "Only overdue tasks (past due_at)").option("--recurring", "Only recurring tasks").option("--limit <n>", "Max tasks to return").action((opts) => {
12489
12778
  const globalOpts = program2.opts();
12490
12779
  opts.tags = opts.tags || opts.tag;
@@ -15097,7 +15386,19 @@ function bootstrapProject(options = {}, db) {
15097
15386
  }
15098
15387
  setMachineLocalPath(project.id, discovery.projectPath, d);
15099
15388
  const beforeTaskList = d.query("SELECT id FROM task_lists WHERE project_id = ? AND slug = ?").get(project.id, taskListSlug);
15100
- const taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
15389
+ let taskList = ensureTaskList(`${project.name} Tasks`, taskListSlug, project.id, d);
15390
+ if (options.routeEnabled && taskList.metadata.route_enabled !== true) {
15391
+ taskList = updateTaskList(taskList.id, {
15392
+ metadata: {
15393
+ ...taskList.metadata,
15394
+ route_enabled: true,
15395
+ automation: {
15396
+ ...taskList.metadata.automation && typeof taskList.metadata.automation === "object" && !Array.isArray(taskList.metadata.automation) ? taskList.metadata.automation : {},
15397
+ no_auto: false
15398
+ }
15399
+ }
15400
+ }, d);
15401
+ }
15101
15402
  const createdSources = [];
15102
15403
  for (const source of [
15103
15404
  addSourceOnce(project.id, "local", "Project root", discovery.projectPath, { role: "project-root" }, d),
@@ -16703,7 +17004,7 @@ function buildSearchFilters(query, opts, projectId) {
16703
17004
  return Object.fromEntries(Object.entries(filters).filter(([, value]) => value !== undefined));
16704
17005
  }
16705
17006
  function registerProjectCommands(program2) {
16706
- program2.command("project-bootstrap [path]").description("Discover a local workspace and initialize project task state").option("--name <name>", "Project display name").option("--task-list <slug>", "Default task list slug").option("--dry-run", "Show discovery without writing local state").action(async (inputPath, opts) => {
17007
+ program2.command("project-bootstrap [path]").description("Discover a local workspace and initialize project task state").option("--name <name>", "Project display name").option("--task-list <slug>", "Default task list slug").option("--route-enabled", "Mark the default task list as eligible for OpenLoops task-created routing").option("--dry-run", "Show discovery without writing local state").action(async (inputPath, opts) => {
16707
17008
  const globalOpts = program2.opts();
16708
17009
  try {
16709
17010
  const { bootstrapProject: bootstrapProject2 } = await Promise.resolve().then(() => (init_project_bootstrap(), exports_project_bootstrap));
@@ -16711,6 +17012,7 @@ function registerProjectCommands(program2) {
16711
17012
  path: inputPath || globalOpts.project || process.cwd(),
16712
17013
  name: opts.name,
16713
17014
  taskListSlug: opts.taskList,
17015
+ routeEnabled: Boolean(opts.routeEnabled),
16714
17016
  dryRun: opts.dryRun
16715
17017
  });
16716
17018
  if (globalOpts.json) {
@@ -16732,6 +17034,8 @@ function registerProjectCommands(program2) {
16732
17034
  console.log(` ${chalk4.dim("Project:")} ${result.project.id.slice(0, 8)} ${result.created.project ? "(created)" : "(existing)"}`);
16733
17035
  if (result.taskList)
16734
17036
  console.log(` ${chalk4.dim("Task list:")} ${result.taskList.slug} ${result.created.taskList ? "(created)" : "(existing)"}`);
17037
+ if (result.taskList?.metadata.route_enabled === true)
17038
+ console.log(` ${chalk4.dim("Routing:")} route-enabled`);
16735
17039
  if (result.created.sources.length > 0) {
16736
17040
  console.log(` ${chalk4.dim("Sources:")} ${result.created.sources.join(", ")}`);
16737
17041
  }
@@ -37700,7 +38004,7 @@ function limitValue(value) {
37700
38004
  return 20;
37701
38005
  return Math.max(1, Math.min(500, Math.trunc(value)));
37702
38006
  }
37703
- function isTerminal(task) {
38007
+ function isTerminal2(task) {
37704
38008
  return task.status === "completed" || task.status === "failed" || task.status === "cancelled";
37705
38009
  }
37706
38010
  function sameAgent(task, agentId) {
@@ -37742,7 +38046,7 @@ function summarizeTask(task) {
37742
38046
  function overdueTasks(tasks, nowIso) {
37743
38047
  const now4 = Date.parse(nowIso);
37744
38048
  return tasks.filter((task) => {
37745
- if (isTerminal(task) || !task.due_at)
38049
+ if (isTerminal2(task) || !task.due_at)
37746
38050
  return false;
37747
38051
  const due = Date.parse(task.due_at);
37748
38052
  return Number.isFinite(due) && due < now4;
@@ -50321,7 +50625,8 @@ Repairs`));
50321
50625
  });
50322
50626
  const limited2 = ready.slice(0, parseInt(opts.limit, 10));
50323
50627
  if (opts.json || globalOpts.json) {
50324
- console.log(JSON.stringify(limited2));
50628
+ const { getTaskRouteState: getTaskRouteState2 } = await Promise.resolve().then(() => (init_task_routing(), exports_task_routing));
50629
+ console.log(JSON.stringify(limited2.map((task2) => ({ ...task2, route_state: getTaskRouteState2(task2, db) }))));
50325
50630
  return;
50326
50631
  }
50327
50632
  if (limited2.length === 0) {
@@ -52790,7 +53095,7 @@ __export(exports_dispatch2, {
52790
53095
  });
52791
53096
  import chalk9 from "chalk";
52792
53097
  function registerDispatchCommands(program2) {
52793
- const dispatchCmd = program2.command("dispatch").description("Send tasks or task lists to a tmux window").argument("<target>", "tmux target: window, session:window, or session:window.pane").option("--tasks <ids>", "Comma-separated task IDs to dispatch").option("--list <id>", "Task list ID or slug to dispatch").option("--filter-status <statuses>", "Comma-separated task statuses to include (default: pending)", "pending").option("--delay <ms>", "Delay in ms between message and Enter (auto-calculated if omitted)", parseInt).option("--at <datetime>", "ISO datetime to schedule the dispatch").option("--multiple <targets>", "Comma-separated list of additional tmux targets (fan-out)").option("--stagger <ms>", "Delay between targets when using --multiple (default: 500ms)", parseInt).option("--confirm-busy", "Send even if the target tmux pane appears busy").option("--dry-run", "Preview the formatted message without sending").action(async (target, opts) => {
53098
+ const dispatchCmd = program2.command("dispatch").description("Legacy/emergency only: send tasks or task lists to a tmux window after explicit human choice").argument("<target>", "legacy/emergency tmux target: window, session:window, or session:window.pane").option("--tasks <ids>", "Comma-separated task IDs to dispatch").option("--list <id>", "Task list ID or slug to dispatch").option("--filter-status <statuses>", "Comma-separated task statuses to include (default: pending)", "pending").option("--delay <ms>", "Delay in ms between message and Enter (auto-calculated if omitted)", parseInt).option("--at <datetime>", "ISO datetime to schedule the dispatch").option("--multiple <targets>", "Comma-separated list of additional legacy/emergency tmux targets (fan-out)").option("--stagger <ms>", "Delay between targets when using --multiple (default: 500ms)", parseInt).option("--confirm-busy", "Send even if the target tmux pane appears busy").option("--dry-run", "Preview the formatted message without sending").action(async (target, opts) => {
52794
53099
  const globalOpts = program2.opts();
52795
53100
  const useJson = globalOpts.json;
52796
53101
  const db = getDatabase();