@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/README.md +76 -47
- package/dist/cli/commands/project-commands.d.ts.map +1 -1
- package/dist/cli/commands/query-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/index.js +368 -63
- package/dist/contracts.js +108 -58
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +300 -59
- package/dist/lib/project-bootstrap.d.ts +1 -0
- package/dist/lib/project-bootstrap.d.ts.map +1 -1
- package/dist/lib/shared-events.d.ts.map +1 -1
- package/dist/lib/task-route-contract.d.ts +33 -0
- package/dist/lib/task-route-contract.d.ts.map +1 -0
- package/dist/lib/task-routing.d.ts +55 -0
- package/dist/lib/task-routing.d.ts.map +1 -0
- package/dist/mcp/index.js +116 -58
- package/dist/registry.js +108 -58
- package/dist/release-provenance.json +3 -3
- package/dist/server/index.js +116 -58
- package/dist/storage.js +108 -58
- package/package.json +1 -1
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/
|
|
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
|
|
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
|
-
|
|
6947
|
+
values.push(value);
|
|
6976
6948
|
}
|
|
6977
6949
|
}
|
|
6978
|
-
return;
|
|
6950
|
+
return values;
|
|
6979
6951
|
}
|
|
6980
|
-
function
|
|
6981
|
-
const
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
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 =
|
|
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
|
|
7014
|
-
|
|
7015
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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("
|
|
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();
|