@hasna/todos 0.11.45 → 0.11.47
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 +11 -1097
- package/dashboard/dist/assets/{index-CVF1vn7Z.js → index-B-w1tUlm.js} +23 -23
- package/dashboard/dist/assets/index-BXQ39iMX.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/commands/agent-commands.d.ts.map +1 -1
- package/dist/cli/commands/cloud-commands.d.ts +3 -0
- package/dist/cli/commands/cloud-commands.d.ts.map +1 -0
- package/dist/cli/commands/config-serve-commands.d.ts.map +1 -1
- package/dist/cli/commands/machines.d.ts.map +1 -1
- package/dist/cli/commands/mcp-hooks-commands.d.ts.map +1 -1
- package/dist/cli/commands/plan-template-commands.d.ts.map +1 -1
- 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 +28761 -42785
- package/dist/db/agent-metrics.d.ts +0 -101
- package/dist/db/agent-metrics.d.ts.map +1 -1
- package/dist/db/agent-names.d.ts +5 -1
- package/dist/db/agent-names.d.ts.map +1 -1
- package/dist/db/builtin-templates.d.ts +0 -17
- package/dist/db/builtin-templates.d.ts.map +1 -1
- package/dist/db/comments.d.ts.map +1 -1
- package/dist/db/database.d.ts +1 -2
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/handoffs.d.ts +1 -52
- package/dist/db/handoffs.d.ts.map +1 -1
- package/dist/db/machines.d.ts +6 -19
- package/dist/db/machines.d.ts.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/pg-migrate.d.ts +14 -0
- package/dist/db/pg-migrate.d.ts.map +1 -0
- package/dist/db/pg-migrations.d.ts +8 -0
- package/dist/db/pg-migrations.d.ts.map +1 -0
- package/dist/db/plans.d.ts.map +1 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/task-commits.d.ts +0 -51
- package/dist/db/task-commits.d.ts.map +1 -1
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/db/task-lifecycle.d.ts +1 -16
- package/dist/db/task-lifecycle.d.ts.map +1 -1
- package/dist/db/task-relations.d.ts +9 -69
- package/dist/db/task-relations.d.ts.map +1 -1
- package/dist/db/tasks.d.ts +4 -8
- package/dist/db/tasks.d.ts.map +1 -1
- package/dist/index.d.ts +14 -119
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15761 -24428
- package/dist/lib/auto-assign.d.ts +5 -3
- package/dist/lib/auto-assign.d.ts.map +1 -1
- package/dist/lib/config.d.ts +0 -317
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/extract.d.ts +0 -57
- package/dist/lib/extract.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +12 -6
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +22576 -28974
- package/dist/mcp/token-utils.d.ts +2 -2
- package/dist/mcp/token-utils.d.ts.map +1 -1
- package/dist/mcp/tools/cloud.d.ts +12 -0
- package/dist/mcp/tools/cloud.d.ts.map +1 -0
- package/dist/mcp/tools/code-tools.d.ts.map +1 -1
- package/dist/mcp/tools/machines.d.ts.map +1 -1
- package/dist/mcp/tools/task-adv-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-auto-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp/tools/task-meta-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-project-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-rel-tools.d.ts.map +1 -1
- package/dist/mcp/tools/task-resources.d.ts.map +1 -1
- package/dist/mcp/tools/task-workflow-tools.d.ts.map +1 -1
- package/dist/mcp/tools/templates.d.ts.map +1 -1
- package/dist/sdk/client.d.ts +1 -1
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/types.d.ts +1 -26
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/server/index.js +474 -1882
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -216
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +6 -30
- package/dashboard/dist/assets/index-DJm6m6Yy.css +0 -1
- package/dist/capabilities.d.ts +0 -32
- package/dist/capabilities.d.ts.map +0 -1
- package/dist/cli/commands/agent-reliability-commands.d.ts +0 -3
- package/dist/cli/commands/agent-reliability-commands.d.ts.map +0 -1
- package/dist/cli/commands/audit-ledger-commands.d.ts +0 -3
- package/dist/cli/commands/audit-ledger-commands.d.ts.map +0 -1
- package/dist/cli/commands/capacity-commands.d.ts +0 -3
- package/dist/cli/commands/capacity-commands.d.ts.map +0 -1
- package/dist/cli/commands/environment-snapshots.d.ts +0 -3
- package/dist/cli/commands/environment-snapshots.d.ts.map +0 -1
- package/dist/cli/commands/knowledge-commands.d.ts +0 -3
- package/dist/cli/commands/knowledge-commands.d.ts.map +0 -1
- package/dist/cli/commands/local-snapshot-commands.d.ts +0 -3
- package/dist/cli/commands/local-snapshot-commands.d.ts.map +0 -1
- package/dist/cli/commands/onboarding-commands.d.ts +0 -3
- package/dist/cli/commands/onboarding-commands.d.ts.map +0 -1
- package/dist/cli/commands/release-compatibility-commands.d.ts +0 -3
- package/dist/cli/commands/release-compatibility-commands.d.ts.map +0 -1
- package/dist/cli/commands/retrospective-commands.d.ts +0 -3
- package/dist/cli/commands/retrospective-commands.d.ts.map +0 -1
- package/dist/cli/commands/review-queue-commands.d.ts +0 -3
- package/dist/cli/commands/review-queue-commands.d.ts.map +0 -1
- package/dist/cli/commands/risk-commands.d.ts +0 -3
- package/dist/cli/commands/risk-commands.d.ts.map +0 -1
- package/dist/cli/commands/roadmap-commands.d.ts +0 -3
- package/dist/cli/commands/roadmap-commands.d.ts.map +0 -1
- package/dist/cli/commands/sdk-fixture-commands.d.ts +0 -3
- package/dist/cli/commands/sdk-fixture-commands.d.ts.map +0 -1
- package/dist/cli-mcp-parity.d.ts +0 -41
- package/dist/cli-mcp-parity.d.ts.map +0 -1
- package/dist/contracts.d.ts +0 -81
- package/dist/contracts.d.ts.map +0 -1
- package/dist/contracts.js +0 -16690
- package/dist/db/boards.d.ts +0 -56
- package/dist/db/boards.d.ts.map +0 -1
- package/dist/db/calendar.d.ts +0 -52
- package/dist/db/calendar.d.ts.map +0 -1
- package/dist/db/inbox.d.ts +0 -47
- package/dist/db/inbox.d.ts.map +0 -1
- package/dist/db/project-knowledge.d.ts +0 -88
- package/dist/db/project-knowledge.d.ts.map +0 -1
- package/dist/db/project-risks.d.ts +0 -139
- package/dist/db/project-risks.d.ts.map +0 -1
- package/dist/db/retrospectives.d.ts +0 -98
- package/dist/db/retrospectives.d.ts.map +0 -1
- package/dist/db/task-runs.d.ts +0 -130
- package/dist/db/task-runs.d.ts.map +0 -1
- package/dist/json-contracts.d.ts +0 -56
- package/dist/json-contracts.d.ts.map +0 -1
- package/dist/lib/activity-timeline.d.ts +0 -43
- package/dist/lib/activity-timeline.d.ts.map +0 -1
- package/dist/lib/agent-replay-simulator.d.ts +0 -66
- package/dist/lib/agent-replay-simulator.d.ts.map +0 -1
- package/dist/lib/agent-run-dispatcher.d.ts +0 -62
- package/dist/lib/agent-run-dispatcher.d.ts.map +0 -1
- package/dist/lib/approval-gates.d.ts +0 -52
- package/dist/lib/approval-gates.d.ts.map +0 -1
- package/dist/lib/artifact-store.d.ts +0 -68
- package/dist/lib/artifact-store.d.ts.map +0 -1
- package/dist/lib/audit-ledger.d.ts +0 -59
- package/dist/lib/audit-ledger.d.ts.map +0 -1
- package/dist/lib/branch-work-plans.d.ts +0 -46
- package/dist/lib/branch-work-plans.d.ts.map +0 -1
- package/dist/lib/capacity-forecasts.d.ts +0 -70
- package/dist/lib/capacity-forecasts.d.ts.map +0 -1
- package/dist/lib/context-packs.d.ts +0 -163
- package/dist/lib/context-packs.d.ts.map +0 -1
- package/dist/lib/doctor.d.ts +0 -46
- package/dist/lib/doctor.d.ts.map +0 -1
- package/dist/lib/environment-snapshots.d.ts +0 -111
- package/dist/lib/environment-snapshots.d.ts.map +0 -1
- package/dist/lib/event-hooks.d.ts +0 -58
- package/dist/lib/event-hooks.d.ts.map +0 -1
- package/dist/lib/external-issue-importers.d.ts +0 -60
- package/dist/lib/external-issue-importers.d.ts.map +0 -1
- package/dist/lib/local-bridge.d.ts +0 -81
- package/dist/lib/local-bridge.d.ts.map +0 -1
- package/dist/lib/local-encryption.d.ts +0 -94
- package/dist/lib/local-encryption.d.ts.map +0 -1
- package/dist/lib/local-extensions.d.ts +0 -75
- package/dist/lib/local-extensions.d.ts.map +0 -1
- package/dist/lib/local-fields.d.ts +0 -33
- package/dist/lib/local-fields.d.ts.map +0 -1
- package/dist/lib/local-notifications.d.ts +0 -55
- package/dist/lib/local-notifications.d.ts.map +0 -1
- package/dist/lib/local-snapshots.d.ts +0 -66
- package/dist/lib/local-snapshots.d.ts.map +0 -1
- package/dist/lib/mention-resolver.d.ts +0 -43
- package/dist/lib/mention-resolver.d.ts.map +0 -1
- package/dist/lib/natural-language-intake.d.ts +0 -56
- package/dist/lib/natural-language-intake.d.ts.map +0 -1
- package/dist/lib/onboarding-fixtures.d.ts +0 -31
- package/dist/lib/onboarding-fixtures.d.ts.map +0 -1
- package/dist/lib/policy-packs.d.ts +0 -87
- package/dist/lib/policy-packs.d.ts.map +0 -1
- package/dist/lib/project-bootstrap.d.ts +0 -35
- package/dist/lib/project-bootstrap.d.ts.map +0 -1
- package/dist/lib/public-release-gate.d.ts +0 -57
- package/dist/lib/public-release-gate.d.ts.map +0 -1
- package/dist/lib/redaction.d.ts +0 -12
- package/dist/lib/redaction.d.ts.map +0 -1
- package/dist/lib/release-compatibility.d.ts +0 -59
- package/dist/lib/release-compatibility.d.ts.map +0 -1
- package/dist/lib/release-notes.d.ts +0 -81
- package/dist/lib/release-notes.d.ts.map +0 -1
- package/dist/lib/retention-cleanup.d.ts +0 -63
- package/dist/lib/retention-cleanup.d.ts.map +0 -1
- package/dist/lib/review-queues.d.ts +0 -98
- package/dist/lib/review-queues.d.ts.map +0 -1
- package/dist/lib/roadmaps.d.ts +0 -133
- package/dist/lib/roadmaps.d.ts.map +0 -1
- package/dist/lib/runner-sandbox.d.ts +0 -50
- package/dist/lib/runner-sandbox.d.ts.map +0 -1
- package/dist/lib/saved-search-views.d.ts +0 -60
- package/dist/lib/saved-search-views.d.ts.map +0 -1
- package/dist/lib/sdk-integration-fixtures.d.ts +0 -65
- package/dist/lib/sdk-integration-fixtures.d.ts.map +0 -1
- package/dist/lib/task-contracts.d.ts +0 -75
- package/dist/lib/task-contracts.d.ts.map +0 -1
- package/dist/lib/task-dedupe.d.ts +0 -45
- package/dist/lib/task-dedupe.d.ts.map +0 -1
- package/dist/lib/terminal-notifications.d.ts +0 -53
- package/dist/lib/terminal-notifications.d.ts.map +0 -1
- package/dist/lib/todos-md.d.ts +0 -21
- package/dist/lib/todos-md.d.ts.map +0 -1
- package/dist/lib/verification-providers.d.ts +0 -54
- package/dist/lib/verification-providers.d.ts.map +0 -1
- package/dist/lib/workflow-prompts.d.ts +0 -38
- package/dist/lib/workflow-prompts.d.ts.map +0 -1
- package/dist/lib/workspace-trust.d.ts +0 -38
- package/dist/lib/workspace-trust.d.ts.map +0 -1
- package/dist/mcp/tools/environment-snapshots.d.ts +0 -8
- package/dist/mcp/tools/environment-snapshots.d.ts.map +0 -1
- package/dist/mcp/tools/workflow-prompts.d.ts +0 -3
- package/dist/mcp/tools/workflow-prompts.d.ts.map +0 -1
- package/dist/mcp.d.ts +0 -42
- package/dist/mcp.d.ts.map +0 -1
- package/dist/mcp.js +0 -542
- package/dist/registry.d.ts +0 -35
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -16882
- package/dist/release-provenance.json +0 -7
- package/dist/sdk/index.js +0 -635
- package/dist/storage/index.d.ts +0 -4
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/interfaces.d.ts +0 -185
- package/dist/storage/interfaces.d.ts.map +0 -1
- package/dist/storage/local-sqlite.d.ts +0 -7
- package/dist/storage/local-sqlite.d.ts.map +0 -1
- package/dist/storage.d.ts +0 -4
- package/dist/storage.d.ts.map +0 -1
- package/dist/storage.js +0 -8237
- package/dist/test/no-network.d.ts +0 -7
- package/dist/test/no-network.d.ts.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -846,300 +846,6 @@ var init_migrations = __esm(() => {
|
|
|
846
846
|
CREATE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(prefix);
|
|
847
847
|
CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(revoked_at, expires_at);
|
|
848
848
|
INSERT OR IGNORE INTO _migrations (id) VALUES (50);
|
|
849
|
-
`,
|
|
850
|
-
`
|
|
851
|
-
CREATE TABLE IF NOT EXISTS task_git_refs (
|
|
852
|
-
id TEXT PRIMARY KEY,
|
|
853
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
854
|
-
ref_type TEXT NOT NULL CHECK(ref_type IN ('branch', 'pull_request')),
|
|
855
|
-
name TEXT NOT NULL,
|
|
856
|
-
url TEXT,
|
|
857
|
-
provider TEXT,
|
|
858
|
-
metadata TEXT DEFAULT '{}',
|
|
859
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
860
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
861
|
-
UNIQUE(task_id, ref_type, name)
|
|
862
|
-
);
|
|
863
|
-
CREATE INDEX IF NOT EXISTS idx_task_git_refs_task ON task_git_refs(task_id);
|
|
864
|
-
CREATE INDEX IF NOT EXISTS idx_task_git_refs_lookup ON task_git_refs(ref_type, name);
|
|
865
|
-
CREATE INDEX IF NOT EXISTS idx_task_git_refs_url ON task_git_refs(url);
|
|
866
|
-
|
|
867
|
-
CREATE TABLE IF NOT EXISTS task_verifications (
|
|
868
|
-
id TEXT PRIMARY KEY,
|
|
869
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
870
|
-
command TEXT NOT NULL,
|
|
871
|
-
status TEXT NOT NULL DEFAULT 'unknown' CHECK(status IN ('passed', 'failed', 'unknown')),
|
|
872
|
-
output_summary TEXT,
|
|
873
|
-
artifact_path TEXT,
|
|
874
|
-
agent_id TEXT,
|
|
875
|
-
run_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
876
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
877
|
-
);
|
|
878
|
-
CREATE INDEX IF NOT EXISTS idx_task_verifications_task ON task_verifications(task_id);
|
|
879
|
-
CREATE INDEX IF NOT EXISTS idx_task_verifications_status ON task_verifications(status);
|
|
880
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (51);
|
|
881
|
-
`,
|
|
882
|
-
`
|
|
883
|
-
CREATE TABLE IF NOT EXISTS task_runs (
|
|
884
|
-
id TEXT PRIMARY KEY,
|
|
885
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
886
|
-
agent_id TEXT,
|
|
887
|
-
title TEXT,
|
|
888
|
-
status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running', 'completed', 'failed', 'cancelled')),
|
|
889
|
-
summary TEXT,
|
|
890
|
-
metadata TEXT DEFAULT '{}',
|
|
891
|
-
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
892
|
-
completed_at TEXT,
|
|
893
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
894
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
895
|
-
);
|
|
896
|
-
CREATE INDEX IF NOT EXISTS idx_task_runs_task ON task_runs(task_id);
|
|
897
|
-
CREATE INDEX IF NOT EXISTS idx_task_runs_agent ON task_runs(agent_id);
|
|
898
|
-
CREATE INDEX IF NOT EXISTS idx_task_runs_status ON task_runs(status);
|
|
899
|
-
CREATE INDEX IF NOT EXISTS idx_task_runs_started ON task_runs(started_at);
|
|
900
|
-
|
|
901
|
-
CREATE TABLE IF NOT EXISTS task_run_events (
|
|
902
|
-
id TEXT PRIMARY KEY,
|
|
903
|
-
run_id TEXT NOT NULL REFERENCES task_runs(id) ON DELETE CASCADE,
|
|
904
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
905
|
-
event_type TEXT NOT NULL CHECK(event_type IN ('started', 'progress', 'claim', 'comment', 'command', 'file', 'artifact', 'completed', 'failed', 'cancelled')),
|
|
906
|
-
message TEXT,
|
|
907
|
-
data TEXT DEFAULT '{}',
|
|
908
|
-
agent_id TEXT,
|
|
909
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
910
|
-
);
|
|
911
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_events_run ON task_run_events(run_id);
|
|
912
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_events_task ON task_run_events(task_id);
|
|
913
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_events_type ON task_run_events(event_type);
|
|
914
|
-
|
|
915
|
-
CREATE TABLE IF NOT EXISTS task_run_commands (
|
|
916
|
-
id TEXT PRIMARY KEY,
|
|
917
|
-
run_id TEXT NOT NULL REFERENCES task_runs(id) ON DELETE CASCADE,
|
|
918
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
919
|
-
command TEXT NOT NULL,
|
|
920
|
-
status TEXT NOT NULL DEFAULT 'unknown' CHECK(status IN ('passed', 'failed', 'unknown')),
|
|
921
|
-
exit_code INTEGER,
|
|
922
|
-
output_summary TEXT,
|
|
923
|
-
artifact_path TEXT,
|
|
924
|
-
agent_id TEXT,
|
|
925
|
-
started_at TEXT,
|
|
926
|
-
completed_at TEXT,
|
|
927
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
928
|
-
);
|
|
929
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_commands_run ON task_run_commands(run_id);
|
|
930
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_commands_task ON task_run_commands(task_id);
|
|
931
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_commands_status ON task_run_commands(status);
|
|
932
|
-
|
|
933
|
-
CREATE TABLE IF NOT EXISTS task_run_artifacts (
|
|
934
|
-
id TEXT PRIMARY KEY,
|
|
935
|
-
run_id TEXT NOT NULL REFERENCES task_runs(id) ON DELETE CASCADE,
|
|
936
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
937
|
-
path TEXT NOT NULL,
|
|
938
|
-
artifact_type TEXT,
|
|
939
|
-
description TEXT,
|
|
940
|
-
size_bytes INTEGER,
|
|
941
|
-
sha256 TEXT,
|
|
942
|
-
metadata TEXT DEFAULT '{}',
|
|
943
|
-
agent_id TEXT,
|
|
944
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
945
|
-
);
|
|
946
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_run ON task_run_artifacts(run_id);
|
|
947
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_task ON task_run_artifacts(task_id);
|
|
948
|
-
CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_path ON task_run_artifacts(path);
|
|
949
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (52);
|
|
950
|
-
`,
|
|
951
|
-
`
|
|
952
|
-
CREATE TABLE IF NOT EXISTS inbox_items (
|
|
953
|
-
id TEXT PRIMARY KEY,
|
|
954
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
955
|
-
source_type TEXT NOT NULL CHECK(source_type IN ('pasted_error', 'ci_log', 'git_context', 'github_issue', 'file', 'other')),
|
|
956
|
-
source_name TEXT,
|
|
957
|
-
source_url TEXT,
|
|
958
|
-
title TEXT NOT NULL,
|
|
959
|
-
body TEXT,
|
|
960
|
-
fingerprint TEXT NOT NULL UNIQUE,
|
|
961
|
-
status TEXT NOT NULL DEFAULT 'triaged' CHECK(status IN ('new', 'triaged', 'ignored')),
|
|
962
|
-
metadata TEXT DEFAULT '{}',
|
|
963
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
964
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
965
|
-
);
|
|
966
|
-
CREATE INDEX IF NOT EXISTS idx_inbox_items_task ON inbox_items(task_id);
|
|
967
|
-
CREATE INDEX IF NOT EXISTS idx_inbox_items_source ON inbox_items(source_type, source_name);
|
|
968
|
-
CREATE INDEX IF NOT EXISTS idx_inbox_items_status ON inbox_items(status);
|
|
969
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (53);
|
|
970
|
-
`,
|
|
971
|
-
`
|
|
972
|
-
ALTER TABLE handoffs ADD COLUMN session_id TEXT;
|
|
973
|
-
ALTER TABLE handoffs ADD COLUMN task_ids TEXT;
|
|
974
|
-
ALTER TABLE handoffs ADD COLUMN relevant_files TEXT;
|
|
975
|
-
ALTER TABLE handoffs ADD COLUMN run_ids TEXT;
|
|
976
|
-
CREATE TABLE IF NOT EXISTS handoff_acknowledgements (
|
|
977
|
-
handoff_id TEXT NOT NULL REFERENCES handoffs(id) ON DELETE CASCADE,
|
|
978
|
-
agent_id TEXT NOT NULL,
|
|
979
|
-
acknowledged_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
980
|
-
PRIMARY KEY (handoff_id, agent_id)
|
|
981
|
-
);
|
|
982
|
-
CREATE INDEX IF NOT EXISTS idx_handoff_acks_agent ON handoff_acknowledgements(agent_id, acknowledged_at);
|
|
983
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (54);
|
|
984
|
-
`,
|
|
985
|
-
`
|
|
986
|
-
CREATE TABLE IF NOT EXISTS saved_search_views (
|
|
987
|
-
id TEXT PRIMARY KEY,
|
|
988
|
-
name TEXT NOT NULL UNIQUE,
|
|
989
|
-
description TEXT,
|
|
990
|
-
scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('all', 'tasks', 'projects', 'plans', 'runs', 'comments')),
|
|
991
|
-
filters TEXT NOT NULL DEFAULT '{}',
|
|
992
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
993
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
994
|
-
);
|
|
995
|
-
CREATE INDEX IF NOT EXISTS idx_saved_search_views_scope ON saved_search_views(scope);
|
|
996
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (55);
|
|
997
|
-
`,
|
|
998
|
-
`
|
|
999
|
-
ALTER TABLE task_time_logs ADD COLUMN run_id TEXT;
|
|
1000
|
-
ALTER TABLE task_time_logs ADD COLUMN focus_session_id TEXT;
|
|
1001
|
-
CREATE INDEX IF NOT EXISTS idx_task_time_logs_run ON task_time_logs(run_id);
|
|
1002
|
-
CREATE INDEX IF NOT EXISTS idx_task_time_logs_focus_session ON task_time_logs(focus_session_id);
|
|
1003
|
-
CREATE TABLE IF NOT EXISTS focus_sessions (
|
|
1004
|
-
id TEXT PRIMARY KEY,
|
|
1005
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1006
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1007
|
-
run_id TEXT,
|
|
1008
|
-
agent_id TEXT,
|
|
1009
|
-
title TEXT,
|
|
1010
|
-
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'paused', 'completed', 'cancelled')),
|
|
1011
|
-
started_at TEXT NOT NULL,
|
|
1012
|
-
last_resumed_at TEXT,
|
|
1013
|
-
paused_at TEXT,
|
|
1014
|
-
ended_at TEXT,
|
|
1015
|
-
actual_minutes INTEGER NOT NULL DEFAULT 0,
|
|
1016
|
-
idle_after_minutes INTEGER,
|
|
1017
|
-
notes TEXT,
|
|
1018
|
-
metadata TEXT DEFAULT '{}',
|
|
1019
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1020
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1021
|
-
);
|
|
1022
|
-
CREATE INDEX IF NOT EXISTS idx_focus_sessions_task ON focus_sessions(task_id);
|
|
1023
|
-
CREATE INDEX IF NOT EXISTS idx_focus_sessions_plan ON focus_sessions(plan_id);
|
|
1024
|
-
CREATE INDEX IF NOT EXISTS idx_focus_sessions_run ON focus_sessions(run_id);
|
|
1025
|
-
CREATE INDEX IF NOT EXISTS idx_focus_sessions_agent ON focus_sessions(agent_id);
|
|
1026
|
-
CREATE INDEX IF NOT EXISTS idx_focus_sessions_status ON focus_sessions(status);
|
|
1027
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (56);
|
|
1028
|
-
`,
|
|
1029
|
-
`
|
|
1030
|
-
CREATE TABLE IF NOT EXISTS task_boards (
|
|
1031
|
-
id TEXT PRIMARY KEY,
|
|
1032
|
-
name TEXT NOT NULL UNIQUE,
|
|
1033
|
-
scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('tasks', 'plans')),
|
|
1034
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1035
|
-
task_list_id TEXT REFERENCES task_lists(id) ON DELETE SET NULL,
|
|
1036
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1037
|
-
agent_id TEXT,
|
|
1038
|
-
lanes TEXT NOT NULL DEFAULT '[]',
|
|
1039
|
-
filters TEXT NOT NULL DEFAULT '{}',
|
|
1040
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1041
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1042
|
-
);
|
|
1043
|
-
CREATE INDEX IF NOT EXISTS idx_task_boards_scope ON task_boards(scope);
|
|
1044
|
-
CREATE INDEX IF NOT EXISTS idx_task_boards_project ON task_boards(project_id);
|
|
1045
|
-
CREATE INDEX IF NOT EXISTS idx_task_boards_plan ON task_boards(plan_id);
|
|
1046
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (57);
|
|
1047
|
-
`,
|
|
1048
|
-
`
|
|
1049
|
-
CREATE TABLE IF NOT EXISTS local_calendar_items (
|
|
1050
|
-
id TEXT PRIMARY KEY,
|
|
1051
|
-
kind TEXT NOT NULL CHECK(kind IN ('task_due', 'task_sla', 'task_reminder', 'milestone', 'work_block', 'run', 'imported')),
|
|
1052
|
-
title TEXT NOT NULL,
|
|
1053
|
-
description TEXT,
|
|
1054
|
-
starts_at TEXT NOT NULL,
|
|
1055
|
-
ends_at TEXT,
|
|
1056
|
-
timezone TEXT,
|
|
1057
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1058
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1059
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1060
|
-
run_id TEXT,
|
|
1061
|
-
recurrence_rule TEXT,
|
|
1062
|
-
metadata TEXT DEFAULT '{}',
|
|
1063
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1064
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1065
|
-
);
|
|
1066
|
-
CREATE INDEX IF NOT EXISTS idx_local_calendar_items_time ON local_calendar_items(starts_at, ends_at);
|
|
1067
|
-
CREATE INDEX IF NOT EXISTS idx_local_calendar_items_task ON local_calendar_items(task_id);
|
|
1068
|
-
CREATE INDEX IF NOT EXISTS idx_local_calendar_items_project ON local_calendar_items(project_id);
|
|
1069
|
-
CREATE INDEX IF NOT EXISTS idx_local_calendar_items_kind ON local_calendar_items(kind);
|
|
1070
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (58);
|
|
1071
|
-
`,
|
|
1072
|
-
`
|
|
1073
|
-
CREATE TABLE IF NOT EXISTS project_knowledge_records (
|
|
1074
|
-
id TEXT PRIMARY KEY,
|
|
1075
|
-
record_type TEXT NOT NULL CHECK(record_type IN ('decision','architecture_note','tradeoff','context_snapshot')),
|
|
1076
|
-
title TEXT NOT NULL,
|
|
1077
|
-
content TEXT,
|
|
1078
|
-
decision TEXT,
|
|
1079
|
-
rationale TEXT,
|
|
1080
|
-
alternatives TEXT DEFAULT '[]',
|
|
1081
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1082
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1083
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1084
|
-
agent_id TEXT,
|
|
1085
|
-
snapshot_id TEXT REFERENCES context_snapshots(id) ON DELETE SET NULL,
|
|
1086
|
-
tags TEXT DEFAULT '[]',
|
|
1087
|
-
metadata TEXT DEFAULT '{}',
|
|
1088
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1089
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1090
|
-
);
|
|
1091
|
-
CREATE INDEX IF NOT EXISTS idx_project_knowledge_type ON project_knowledge_records(record_type);
|
|
1092
|
-
CREATE INDEX IF NOT EXISTS idx_project_knowledge_project ON project_knowledge_records(project_id);
|
|
1093
|
-
CREATE INDEX IF NOT EXISTS idx_project_knowledge_task ON project_knowledge_records(task_id);
|
|
1094
|
-
CREATE INDEX IF NOT EXISTS idx_project_knowledge_plan ON project_knowledge_records(plan_id);
|
|
1095
|
-
CREATE INDEX IF NOT EXISTS idx_project_knowledge_agent ON project_knowledge_records(agent_id);
|
|
1096
|
-
CREATE INDEX IF NOT EXISTS idx_project_knowledge_snapshot ON project_knowledge_records(snapshot_id);
|
|
1097
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (59);
|
|
1098
|
-
`,
|
|
1099
|
-
`
|
|
1100
|
-
CREATE TABLE IF NOT EXISTS project_risks (
|
|
1101
|
-
id TEXT PRIMARY KEY,
|
|
1102
|
-
title TEXT NOT NULL,
|
|
1103
|
-
description TEXT,
|
|
1104
|
-
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','mitigating','resolved','accepted')),
|
|
1105
|
-
severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low','medium','high','critical')),
|
|
1106
|
-
probability TEXT NOT NULL DEFAULT 'medium' CHECK(probability IN ('low','medium','high')),
|
|
1107
|
-
owner TEXT,
|
|
1108
|
-
mitigation TEXT,
|
|
1109
|
-
due_at TEXT,
|
|
1110
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1111
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1112
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1113
|
-
tags TEXT DEFAULT '[]',
|
|
1114
|
-
metadata TEXT DEFAULT '{}',
|
|
1115
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1116
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1117
|
-
closed_at TEXT
|
|
1118
|
-
);
|
|
1119
|
-
CREATE INDEX IF NOT EXISTS idx_project_risks_status ON project_risks(status);
|
|
1120
|
-
CREATE INDEX IF NOT EXISTS idx_project_risks_severity ON project_risks(severity);
|
|
1121
|
-
CREATE INDEX IF NOT EXISTS idx_project_risks_project ON project_risks(project_id);
|
|
1122
|
-
CREATE INDEX IF NOT EXISTS idx_project_risks_plan ON project_risks(plan_id);
|
|
1123
|
-
CREATE INDEX IF NOT EXISTS idx_project_risks_task ON project_risks(task_id);
|
|
1124
|
-
CREATE INDEX IF NOT EXISTS idx_project_risks_due ON project_risks(due_at);
|
|
1125
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (60);
|
|
1126
|
-
`,
|
|
1127
|
-
`
|
|
1128
|
-
CREATE TABLE IF NOT EXISTS local_retrospectives (
|
|
1129
|
-
id TEXT PRIMARY KEY,
|
|
1130
|
-
title TEXT NOT NULL,
|
|
1131
|
-
scope TEXT NOT NULL CHECK(scope IN ('project','plan')),
|
|
1132
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1133
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1134
|
-
agent_id TEXT,
|
|
1135
|
-
report_json TEXT NOT NULL,
|
|
1136
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1137
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1138
|
-
);
|
|
1139
|
-
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_project ON local_retrospectives(project_id);
|
|
1140
|
-
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_plan ON local_retrospectives(plan_id);
|
|
1141
|
-
CREATE INDEX IF NOT EXISTS idx_local_retrospectives_agent ON local_retrospectives(agent_id);
|
|
1142
|
-
INSERT OR IGNORE INTO _migrations (id) VALUES (61);
|
|
1143
849
|
`
|
|
1144
850
|
];
|
|
1145
851
|
});
|
|
@@ -1224,17 +930,6 @@ function ensureSchema(db) {
|
|
|
1224
930
|
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1225
931
|
tag TEXT NOT NULL, PRIMARY KEY (task_id, tag)
|
|
1226
932
|
)`);
|
|
1227
|
-
ensureTable("task_dependencies", `
|
|
1228
|
-
CREATE TABLE task_dependencies (
|
|
1229
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1230
|
-
depends_on TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1231
|
-
external_project_id TEXT,
|
|
1232
|
-
external_task_id TEXT,
|
|
1233
|
-
PRIMARY KEY (task_id, depends_on),
|
|
1234
|
-
CHECK (task_id != depends_on)
|
|
1235
|
-
)`);
|
|
1236
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_dependencies_task ON task_dependencies(task_id)");
|
|
1237
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_dependencies_depends_on ON task_dependencies(depends_on)");
|
|
1238
933
|
ensureTable("task_history", `
|
|
1239
934
|
CREATE TABLE task_history (
|
|
1240
935
|
id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
@@ -1292,124 +987,6 @@ function ensureSchema(db) {
|
|
|
1292
987
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1293
988
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1294
989
|
)`);
|
|
1295
|
-
ensureTable("handoffs", `
|
|
1296
|
-
CREATE TABLE handoffs (
|
|
1297
|
-
id TEXT PRIMARY KEY,
|
|
1298
|
-
agent_id TEXT,
|
|
1299
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1300
|
-
session_id TEXT,
|
|
1301
|
-
summary TEXT NOT NULL,
|
|
1302
|
-
completed TEXT,
|
|
1303
|
-
in_progress TEXT,
|
|
1304
|
-
blockers TEXT,
|
|
1305
|
-
next_steps TEXT,
|
|
1306
|
-
task_ids TEXT,
|
|
1307
|
-
relevant_files TEXT,
|
|
1308
|
-
run_ids TEXT,
|
|
1309
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1310
|
-
)`);
|
|
1311
|
-
ensureTable("handoff_acknowledgements", `
|
|
1312
|
-
CREATE TABLE handoff_acknowledgements (
|
|
1313
|
-
handoff_id TEXT NOT NULL REFERENCES handoffs(id) ON DELETE CASCADE,
|
|
1314
|
-
agent_id TEXT NOT NULL,
|
|
1315
|
-
acknowledged_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1316
|
-
PRIMARY KEY (handoff_id, agent_id)
|
|
1317
|
-
)`);
|
|
1318
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_handoff_acks_agent ON handoff_acknowledgements(agent_id, acknowledged_at)");
|
|
1319
|
-
ensureTable("saved_search_views", `
|
|
1320
|
-
CREATE TABLE saved_search_views (
|
|
1321
|
-
id TEXT PRIMARY KEY,
|
|
1322
|
-
name TEXT NOT NULL UNIQUE,
|
|
1323
|
-
description TEXT,
|
|
1324
|
-
scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('all', 'tasks', 'projects', 'plans', 'runs', 'comments')),
|
|
1325
|
-
filters TEXT NOT NULL DEFAULT '{}',
|
|
1326
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1327
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1328
|
-
)`);
|
|
1329
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_saved_search_views_scope ON saved_search_views(scope)");
|
|
1330
|
-
ensureTable("context_snapshots", `
|
|
1331
|
-
CREATE TABLE context_snapshots (
|
|
1332
|
-
id TEXT PRIMARY KEY,
|
|
1333
|
-
agent_id TEXT,
|
|
1334
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1335
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1336
|
-
snapshot_type TEXT NOT NULL CHECK(snapshot_type IN ('interrupt','complete','handoff','checkpoint')),
|
|
1337
|
-
plan_summary TEXT,
|
|
1338
|
-
files_open TEXT DEFAULT '[]',
|
|
1339
|
-
attempts TEXT DEFAULT '[]',
|
|
1340
|
-
blockers TEXT DEFAULT '[]',
|
|
1341
|
-
next_steps TEXT,
|
|
1342
|
-
metadata TEXT DEFAULT '{}',
|
|
1343
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1344
|
-
)`);
|
|
1345
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_snapshots_agent ON context_snapshots(agent_id)");
|
|
1346
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_snapshots_task ON context_snapshots(task_id)");
|
|
1347
|
-
ensureTable("project_knowledge_records", `
|
|
1348
|
-
CREATE TABLE project_knowledge_records (
|
|
1349
|
-
id TEXT PRIMARY KEY,
|
|
1350
|
-
record_type TEXT NOT NULL CHECK(record_type IN ('decision','architecture_note','tradeoff','context_snapshot')),
|
|
1351
|
-
title TEXT NOT NULL,
|
|
1352
|
-
content TEXT,
|
|
1353
|
-
decision TEXT,
|
|
1354
|
-
rationale TEXT,
|
|
1355
|
-
alternatives TEXT DEFAULT '[]',
|
|
1356
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1357
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1358
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1359
|
-
agent_id TEXT,
|
|
1360
|
-
snapshot_id TEXT REFERENCES context_snapshots(id) ON DELETE SET NULL,
|
|
1361
|
-
tags TEXT DEFAULT '[]',
|
|
1362
|
-
metadata TEXT DEFAULT '{}',
|
|
1363
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1364
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1365
|
-
)`);
|
|
1366
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_knowledge_type ON project_knowledge_records(record_type)");
|
|
1367
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_knowledge_project ON project_knowledge_records(project_id)");
|
|
1368
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_knowledge_task ON project_knowledge_records(task_id)");
|
|
1369
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_knowledge_plan ON project_knowledge_records(plan_id)");
|
|
1370
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_knowledge_agent ON project_knowledge_records(agent_id)");
|
|
1371
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_knowledge_snapshot ON project_knowledge_records(snapshot_id)");
|
|
1372
|
-
ensureTable("project_risks", `
|
|
1373
|
-
CREATE TABLE project_risks (
|
|
1374
|
-
id TEXT PRIMARY KEY,
|
|
1375
|
-
title TEXT NOT NULL,
|
|
1376
|
-
description TEXT,
|
|
1377
|
-
status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','mitigating','resolved','accepted')),
|
|
1378
|
-
severity TEXT NOT NULL DEFAULT 'medium' CHECK(severity IN ('low','medium','high','critical')),
|
|
1379
|
-
probability TEXT NOT NULL DEFAULT 'medium' CHECK(probability IN ('low','medium','high')),
|
|
1380
|
-
owner TEXT,
|
|
1381
|
-
mitigation TEXT,
|
|
1382
|
-
due_at TEXT,
|
|
1383
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1384
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1385
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1386
|
-
tags TEXT DEFAULT '[]',
|
|
1387
|
-
metadata TEXT DEFAULT '{}',
|
|
1388
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1389
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1390
|
-
closed_at TEXT
|
|
1391
|
-
)`);
|
|
1392
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_risks_status ON project_risks(status)");
|
|
1393
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_risks_severity ON project_risks(severity)");
|
|
1394
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_risks_project ON project_risks(project_id)");
|
|
1395
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_risks_plan ON project_risks(plan_id)");
|
|
1396
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_risks_task ON project_risks(task_id)");
|
|
1397
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_project_risks_due ON project_risks(due_at)");
|
|
1398
|
-
ensureTable("local_retrospectives", `
|
|
1399
|
-
CREATE TABLE local_retrospectives (
|
|
1400
|
-
id TEXT PRIMARY KEY,
|
|
1401
|
-
title TEXT NOT NULL,
|
|
1402
|
-
scope TEXT NOT NULL CHECK(scope IN ('project','plan')),
|
|
1403
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1404
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1405
|
-
agent_id TEXT,
|
|
1406
|
-
report_json TEXT NOT NULL,
|
|
1407
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1408
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1409
|
-
)`);
|
|
1410
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_local_retrospectives_project ON local_retrospectives(project_id)");
|
|
1411
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_local_retrospectives_plan ON local_retrospectives(plan_id)");
|
|
1412
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_local_retrospectives_agent ON local_retrospectives(agent_id)");
|
|
1413
990
|
ensureTable("task_relationships", `
|
|
1414
991
|
CREATE TABLE task_relationships (
|
|
1415
992
|
id TEXT PRIMARY KEY,
|
|
@@ -1421,121 +998,6 @@ function ensureSchema(db) {
|
|
|
1421
998
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1422
999
|
CHECK (source_task_id != target_task_id)
|
|
1423
1000
|
)`);
|
|
1424
|
-
ensureTable("task_git_refs", `
|
|
1425
|
-
CREATE TABLE task_git_refs (
|
|
1426
|
-
id TEXT PRIMARY KEY,
|
|
1427
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1428
|
-
ref_type TEXT NOT NULL CHECK(ref_type IN ('branch', 'pull_request')),
|
|
1429
|
-
name TEXT NOT NULL,
|
|
1430
|
-
url TEXT,
|
|
1431
|
-
provider TEXT,
|
|
1432
|
-
metadata TEXT DEFAULT '{}',
|
|
1433
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1434
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1435
|
-
UNIQUE(task_id, ref_type, name)
|
|
1436
|
-
)`);
|
|
1437
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_task ON task_git_refs(task_id)");
|
|
1438
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_lookup ON task_git_refs(ref_type, name)");
|
|
1439
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_git_refs_url ON task_git_refs(url)");
|
|
1440
|
-
ensureTable("task_verifications", `
|
|
1441
|
-
CREATE TABLE task_verifications (
|
|
1442
|
-
id TEXT PRIMARY KEY,
|
|
1443
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1444
|
-
command TEXT NOT NULL,
|
|
1445
|
-
status TEXT NOT NULL DEFAULT 'unknown' CHECK(status IN ('passed', 'failed', 'unknown')),
|
|
1446
|
-
output_summary TEXT,
|
|
1447
|
-
artifact_path TEXT,
|
|
1448
|
-
agent_id TEXT,
|
|
1449
|
-
run_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1450
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1451
|
-
)`);
|
|
1452
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_verifications_task ON task_verifications(task_id)");
|
|
1453
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_verifications_status ON task_verifications(status)");
|
|
1454
|
-
ensureTable("task_runs", `
|
|
1455
|
-
CREATE TABLE task_runs (
|
|
1456
|
-
id TEXT PRIMARY KEY,
|
|
1457
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1458
|
-
agent_id TEXT,
|
|
1459
|
-
title TEXT,
|
|
1460
|
-
status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running', 'completed', 'failed', 'cancelled')),
|
|
1461
|
-
summary TEXT,
|
|
1462
|
-
metadata TEXT DEFAULT '{}',
|
|
1463
|
-
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1464
|
-
completed_at TEXT,
|
|
1465
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1466
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1467
|
-
)`);
|
|
1468
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_runs_task ON task_runs(task_id)");
|
|
1469
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_runs_agent ON task_runs(agent_id)");
|
|
1470
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_runs_status ON task_runs(status)");
|
|
1471
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_runs_started ON task_runs(started_at)");
|
|
1472
|
-
ensureTable("task_run_events", `
|
|
1473
|
-
CREATE TABLE task_run_events (
|
|
1474
|
-
id TEXT PRIMARY KEY,
|
|
1475
|
-
run_id TEXT NOT NULL REFERENCES task_runs(id) ON DELETE CASCADE,
|
|
1476
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1477
|
-
event_type TEXT NOT NULL CHECK(event_type IN ('started', 'progress', 'claim', 'comment', 'command', 'file', 'artifact', 'completed', 'failed', 'cancelled')),
|
|
1478
|
-
message TEXT,
|
|
1479
|
-
data TEXT DEFAULT '{}',
|
|
1480
|
-
agent_id TEXT,
|
|
1481
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1482
|
-
)`);
|
|
1483
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_events_run ON task_run_events(run_id)");
|
|
1484
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_events_task ON task_run_events(task_id)");
|
|
1485
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_events_type ON task_run_events(event_type)");
|
|
1486
|
-
ensureTable("task_run_commands", `
|
|
1487
|
-
CREATE TABLE task_run_commands (
|
|
1488
|
-
id TEXT PRIMARY KEY,
|
|
1489
|
-
run_id TEXT NOT NULL REFERENCES task_runs(id) ON DELETE CASCADE,
|
|
1490
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1491
|
-
command TEXT NOT NULL,
|
|
1492
|
-
status TEXT NOT NULL DEFAULT 'unknown' CHECK(status IN ('passed', 'failed', 'unknown')),
|
|
1493
|
-
exit_code INTEGER,
|
|
1494
|
-
output_summary TEXT,
|
|
1495
|
-
artifact_path TEXT,
|
|
1496
|
-
agent_id TEXT,
|
|
1497
|
-
started_at TEXT,
|
|
1498
|
-
completed_at TEXT,
|
|
1499
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1500
|
-
)`);
|
|
1501
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_commands_run ON task_run_commands(run_id)");
|
|
1502
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_commands_task ON task_run_commands(task_id)");
|
|
1503
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_commands_status ON task_run_commands(status)");
|
|
1504
|
-
ensureTable("task_run_artifacts", `
|
|
1505
|
-
CREATE TABLE task_run_artifacts (
|
|
1506
|
-
id TEXT PRIMARY KEY,
|
|
1507
|
-
run_id TEXT NOT NULL REFERENCES task_runs(id) ON DELETE CASCADE,
|
|
1508
|
-
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1509
|
-
path TEXT NOT NULL,
|
|
1510
|
-
artifact_type TEXT,
|
|
1511
|
-
description TEXT,
|
|
1512
|
-
size_bytes INTEGER,
|
|
1513
|
-
sha256 TEXT,
|
|
1514
|
-
metadata TEXT DEFAULT '{}',
|
|
1515
|
-
agent_id TEXT,
|
|
1516
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1517
|
-
)`);
|
|
1518
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_run ON task_run_artifacts(run_id)");
|
|
1519
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_task ON task_run_artifacts(task_id)");
|
|
1520
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_run_artifacts_path ON task_run_artifacts(path)");
|
|
1521
|
-
ensureTable("inbox_items", `
|
|
1522
|
-
CREATE TABLE inbox_items (
|
|
1523
|
-
id TEXT PRIMARY KEY,
|
|
1524
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1525
|
-
source_type TEXT NOT NULL CHECK(source_type IN ('pasted_error', 'ci_log', 'git_context', 'github_issue', 'file', 'other')),
|
|
1526
|
-
source_name TEXT,
|
|
1527
|
-
source_url TEXT,
|
|
1528
|
-
title TEXT NOT NULL,
|
|
1529
|
-
body TEXT,
|
|
1530
|
-
fingerprint TEXT NOT NULL UNIQUE,
|
|
1531
|
-
status TEXT NOT NULL DEFAULT 'triaged' CHECK(status IN ('new', 'triaged', 'ignored')),
|
|
1532
|
-
metadata TEXT DEFAULT '{}',
|
|
1533
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1534
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1535
|
-
)`);
|
|
1536
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_inbox_items_task ON inbox_items(task_id)");
|
|
1537
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_inbox_items_source ON inbox_items(source_type, source_name)");
|
|
1538
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_inbox_items_status ON inbox_items(status)");
|
|
1539
1001
|
ensureTable("kg_edges", `
|
|
1540
1002
|
CREATE TABLE kg_edges (
|
|
1541
1003
|
id TEXT PRIMARY KEY,
|
|
@@ -1697,10 +1159,6 @@ function ensureSchema(db) {
|
|
|
1697
1159
|
ensureColumn("orgs", "synced_at", "TEXT");
|
|
1698
1160
|
ensureColumn("handoffs", "machine_id", "TEXT");
|
|
1699
1161
|
ensureColumn("handoffs", "synced_at", "TEXT");
|
|
1700
|
-
ensureColumn("handoffs", "session_id", "TEXT");
|
|
1701
|
-
ensureColumn("handoffs", "task_ids", "TEXT");
|
|
1702
|
-
ensureColumn("handoffs", "relevant_files", "TEXT");
|
|
1703
|
-
ensureColumn("handoffs", "run_ids", "TEXT");
|
|
1704
1162
|
ensureColumn("task_checklists", "machine_id", "TEXT");
|
|
1705
1163
|
ensureColumn("project_sources", "machine_id", "TEXT");
|
|
1706
1164
|
ensureColumn("project_sources", "synced_at", "TEXT");
|
|
@@ -1746,8 +1204,6 @@ function ensureSchema(db) {
|
|
|
1746
1204
|
CREATE TABLE task_time_logs (
|
|
1747
1205
|
id TEXT PRIMARY KEY,
|
|
1748
1206
|
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
1749
|
-
run_id TEXT,
|
|
1750
|
-
focus_session_id TEXT,
|
|
1751
1207
|
agent_id TEXT,
|
|
1752
1208
|
started_at TEXT,
|
|
1753
1209
|
ended_at TEXT,
|
|
@@ -1755,77 +1211,9 @@ function ensureSchema(db) {
|
|
|
1755
1211
|
notes TEXT,
|
|
1756
1212
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1757
1213
|
)`);
|
|
1758
|
-
ensureColumn("task_time_logs", "run_id", "TEXT");
|
|
1759
|
-
ensureColumn("task_time_logs", "focus_session_id", "TEXT");
|
|
1760
1214
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_time_logs_task ON task_time_logs(task_id)");
|
|
1761
1215
|
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_time_logs_agent ON task_time_logs(agent_id)");
|
|
1762
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_time_logs_run ON task_time_logs(run_id)");
|
|
1763
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_time_logs_focus_session ON task_time_logs(focus_session_id)");
|
|
1764
1216
|
ensureColumn("tasks", "actual_minutes", "INTEGER");
|
|
1765
|
-
ensureTable("focus_sessions", `
|
|
1766
|
-
CREATE TABLE focus_sessions (
|
|
1767
|
-
id TEXT PRIMARY KEY,
|
|
1768
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1769
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1770
|
-
run_id TEXT,
|
|
1771
|
-
agent_id TEXT,
|
|
1772
|
-
title TEXT,
|
|
1773
|
-
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active', 'paused', 'completed', 'cancelled')),
|
|
1774
|
-
started_at TEXT NOT NULL,
|
|
1775
|
-
last_resumed_at TEXT,
|
|
1776
|
-
paused_at TEXT,
|
|
1777
|
-
ended_at TEXT,
|
|
1778
|
-
actual_minutes INTEGER NOT NULL DEFAULT 0,
|
|
1779
|
-
idle_after_minutes INTEGER,
|
|
1780
|
-
notes TEXT,
|
|
1781
|
-
metadata TEXT DEFAULT '{}',
|
|
1782
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1783
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1784
|
-
)`);
|
|
1785
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_focus_sessions_task ON focus_sessions(task_id)");
|
|
1786
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_focus_sessions_plan ON focus_sessions(plan_id)");
|
|
1787
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_focus_sessions_run ON focus_sessions(run_id)");
|
|
1788
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_focus_sessions_agent ON focus_sessions(agent_id)");
|
|
1789
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_focus_sessions_status ON focus_sessions(status)");
|
|
1790
|
-
ensureTable("task_boards", `
|
|
1791
|
-
CREATE TABLE task_boards (
|
|
1792
|
-
id TEXT PRIMARY KEY,
|
|
1793
|
-
name TEXT NOT NULL UNIQUE,
|
|
1794
|
-
scope TEXT NOT NULL DEFAULT 'tasks' CHECK(scope IN ('tasks', 'plans')),
|
|
1795
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1796
|
-
task_list_id TEXT REFERENCES task_lists(id) ON DELETE SET NULL,
|
|
1797
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1798
|
-
agent_id TEXT,
|
|
1799
|
-
lanes TEXT NOT NULL DEFAULT '[]',
|
|
1800
|
-
filters TEXT NOT NULL DEFAULT '{}',
|
|
1801
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1802
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1803
|
-
)`);
|
|
1804
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_boards_scope ON task_boards(scope)");
|
|
1805
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_boards_project ON task_boards(project_id)");
|
|
1806
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_task_boards_plan ON task_boards(plan_id)");
|
|
1807
|
-
ensureTable("local_calendar_items", `
|
|
1808
|
-
CREATE TABLE local_calendar_items (
|
|
1809
|
-
id TEXT PRIMARY KEY,
|
|
1810
|
-
kind TEXT NOT NULL CHECK(kind IN ('task_due', 'task_sla', 'task_reminder', 'milestone', 'work_block', 'run', 'imported')),
|
|
1811
|
-
title TEXT NOT NULL,
|
|
1812
|
-
description TEXT,
|
|
1813
|
-
starts_at TEXT NOT NULL,
|
|
1814
|
-
ends_at TEXT,
|
|
1815
|
-
timezone TEXT,
|
|
1816
|
-
project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
|
|
1817
|
-
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
1818
|
-
plan_id TEXT REFERENCES plans(id) ON DELETE SET NULL,
|
|
1819
|
-
run_id TEXT,
|
|
1820
|
-
recurrence_rule TEXT,
|
|
1821
|
-
metadata TEXT DEFAULT '{}',
|
|
1822
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1823
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1824
|
-
)`);
|
|
1825
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_local_calendar_items_time ON local_calendar_items(starts_at, ends_at)");
|
|
1826
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_local_calendar_items_task ON local_calendar_items(task_id)");
|
|
1827
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_local_calendar_items_project ON local_calendar_items(project_id)");
|
|
1828
|
-
ensureIndex("CREATE INDEX IF NOT EXISTS idx_local_calendar_items_kind ON local_calendar_items(kind)");
|
|
1829
1217
|
ensureTable("task_watchers", `
|
|
1830
1218
|
CREATE TABLE task_watchers (
|
|
1831
1219
|
id TEXT PRIMARY KEY,
|
|
@@ -1905,22 +1293,12 @@ var init_schema = __esm(() => {
|
|
|
1905
1293
|
});
|
|
1906
1294
|
|
|
1907
1295
|
// src/db/machines.ts
|
|
1908
|
-
import { hostname as osHostname, platform as osPlatform
|
|
1909
|
-
function parseMetadata(value) {
|
|
1910
|
-
if (!value)
|
|
1911
|
-
return {};
|
|
1912
|
-
try {
|
|
1913
|
-
const parsed = JSON.parse(value);
|
|
1914
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1915
|
-
} catch {
|
|
1916
|
-
return {};
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1296
|
+
import { hostname as osHostname, platform as osPlatform } from "os";
|
|
1919
1297
|
function rowToMachine(row) {
|
|
1920
1298
|
return {
|
|
1921
1299
|
...row,
|
|
1922
1300
|
is_primary: !!row.is_primary,
|
|
1923
|
-
metadata:
|
|
1301
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : {}
|
|
1924
1302
|
};
|
|
1925
1303
|
}
|
|
1926
1304
|
function getOrCreateLocalMachine(db) {
|
|
@@ -1985,7 +1363,6 @@ __export(exports_database, {
|
|
|
1985
1363
|
now: () => now,
|
|
1986
1364
|
lockExpiryCutoff: () => lockExpiryCutoff,
|
|
1987
1365
|
isLockExpired: () => isLockExpired,
|
|
1988
|
-
getDatabasePath: () => getDatabasePath,
|
|
1989
1366
|
getDatabase: () => getDatabase,
|
|
1990
1367
|
closeDatabase: () => closeDatabase,
|
|
1991
1368
|
clearExpiredLocks: () => clearExpiredLocks,
|
|
@@ -2047,9 +1424,6 @@ function getDbPath() {
|
|
|
2047
1424
|
}
|
|
2048
1425
|
return newPath;
|
|
2049
1426
|
}
|
|
2050
|
-
function getDatabasePath() {
|
|
2051
|
-
return getDbPath();
|
|
2052
|
-
}
|
|
2053
1427
|
function ensureDir(filePath) {
|
|
2054
1428
|
if (isInMemoryDb(filePath))
|
|
2055
1429
|
return;
|
|
@@ -2074,564 +1448,72 @@ function getDatabase(dbPath) {
|
|
|
2074
1448
|
}
|
|
2075
1449
|
function closeDatabase() {
|
|
2076
1450
|
if (_db) {
|
|
2077
|
-
_db.close();
|
|
2078
|
-
_db = null;
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
function resetDatabase() {
|
|
2082
|
-
_db = null;
|
|
2083
|
-
}
|
|
2084
|
-
function now() {
|
|
2085
|
-
return new Date().toISOString();
|
|
2086
|
-
}
|
|
2087
|
-
function uuid() {
|
|
2088
|
-
return crypto.randomUUID();
|
|
2089
|
-
}
|
|
2090
|
-
function isLockExpired(lockedAt, nowMs = Date.now()) {
|
|
2091
|
-
if (!lockedAt)
|
|
2092
|
-
return true;
|
|
2093
|
-
const lockTime = new Date(lockedAt).getTime();
|
|
2094
|
-
const expiryMs = LOCK_EXPIRY_MINUTES * 60 * 1000;
|
|
2095
|
-
return nowMs - lockTime > expiryMs;
|
|
2096
|
-
}
|
|
2097
|
-
function lockExpiryCutoff(nowMs = Date.now()) {
|
|
2098
|
-
const expiryMs = LOCK_EXPIRY_MINUTES * 60 * 1000;
|
|
2099
|
-
return new Date(nowMs - expiryMs).toISOString();
|
|
2100
|
-
}
|
|
2101
|
-
function clearExpiredLocks(db) {
|
|
2102
|
-
const cutoff = lockExpiryCutoff();
|
|
2103
|
-
db.run("UPDATE tasks SET locked_by = NULL, locked_at = NULL WHERE locked_at IS NOT NULL AND locked_at < ?", [cutoff]);
|
|
2104
|
-
}
|
|
2105
|
-
function resolvePartialId(db, table, partialId) {
|
|
2106
|
-
if (!ALLOWED_TABLES.has(table)) {
|
|
2107
|
-
throw new Error(`Invalid table name: ${table}`);
|
|
2108
|
-
}
|
|
2109
|
-
if (partialId.length >= 36) {
|
|
2110
|
-
const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
|
|
2111
|
-
return row?.id ?? null;
|
|
2112
|
-
}
|
|
2113
|
-
const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ?`).all(`${partialId}%`);
|
|
2114
|
-
if (rows.length === 1) {
|
|
2115
|
-
return rows[0].id;
|
|
2116
|
-
}
|
|
2117
|
-
if (rows.length > 1) {
|
|
2118
|
-
return null;
|
|
2119
|
-
}
|
|
2120
|
-
if (table === "tasks") {
|
|
2121
|
-
const shortIdRows = db.query("SELECT id FROM tasks WHERE short_id = ?").all(partialId);
|
|
2122
|
-
if (shortIdRows.length === 1) {
|
|
2123
|
-
return shortIdRows[0].id;
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
if (table === "task_lists") {
|
|
2127
|
-
const slugRow = db.query("SELECT id FROM task_lists WHERE slug = ?").get(partialId);
|
|
2128
|
-
if (slugRow)
|
|
2129
|
-
return slugRow.id;
|
|
2130
|
-
}
|
|
2131
|
-
if (table === "projects") {
|
|
2132
|
-
const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
|
|
2133
|
-
if (nameRow)
|
|
2134
|
-
return nameRow.id;
|
|
2135
|
-
}
|
|
2136
|
-
return null;
|
|
2137
|
-
}
|
|
2138
|
-
var LOCK_EXPIRY_MINUTES = 30, _db = null, ALLOWED_TABLES;
|
|
2139
|
-
var init_database = __esm(() => {
|
|
2140
|
-
init_schema();
|
|
2141
|
-
init_machines();
|
|
2142
|
-
ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates", "project_knowledge_records", "project_risks", "local_retrospectives"]);
|
|
2143
|
-
});
|
|
2144
|
-
|
|
2145
|
-
// src/lib/recurrence.ts
|
|
2146
|
-
function parseRecurrenceRule(rule) {
|
|
2147
|
-
const normalized = rule.trim().toLowerCase();
|
|
2148
|
-
if (normalized === "every weekday" || normalized === "every weekdays") {
|
|
2149
|
-
return { type: "specific_days", days: [1, 2, 3, 4, 5] };
|
|
2150
|
-
}
|
|
2151
|
-
if (normalized === "every day" || normalized === "daily") {
|
|
2152
|
-
return { type: "interval", interval: 1, unit: "day" };
|
|
2153
|
-
}
|
|
2154
|
-
if (normalized === "every week" || normalized === "weekly") {
|
|
2155
|
-
return { type: "interval", interval: 1, unit: "week" };
|
|
2156
|
-
}
|
|
2157
|
-
if (normalized === "every month" || normalized === "monthly") {
|
|
2158
|
-
return { type: "interval", interval: 1, unit: "month" };
|
|
2159
|
-
}
|
|
2160
|
-
const intervalMatch = normalized.match(/^every\s+(\d+)\s+(day|week|month)s?$/);
|
|
2161
|
-
if (intervalMatch) {
|
|
2162
|
-
return {
|
|
2163
|
-
type: "interval",
|
|
2164
|
-
interval: parseInt(intervalMatch[1], 10),
|
|
2165
|
-
unit: intervalMatch[2]
|
|
2166
|
-
};
|
|
2167
|
-
}
|
|
2168
|
-
const daysMatch = normalized.match(/^every\s+(.+)$/);
|
|
2169
|
-
if (daysMatch) {
|
|
2170
|
-
const dayParts = daysMatch[1].split(/[,\s]+/).map((d) => d.trim()).filter(Boolean);
|
|
2171
|
-
const days = [];
|
|
2172
|
-
for (const part of dayParts) {
|
|
2173
|
-
const dayNum = DAY_NAMES[part];
|
|
2174
|
-
if (dayNum !== undefined) {
|
|
2175
|
-
days.push(dayNum);
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
if (days.length > 0) {
|
|
2179
|
-
return { type: "specific_days", days: days.sort((a, b) => a - b) };
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
throw new Error(`Invalid recurrence rule: "${rule}". Supported formats: "every day", "every weekday", "every week", "every 2 weeks", "every month", "every N days/weeks/months", "every monday", "every mon,wed,fri"`);
|
|
2183
|
-
}
|
|
2184
|
-
function isValidRecurrenceRule(rule) {
|
|
2185
|
-
try {
|
|
2186
|
-
parseRecurrenceRule(rule);
|
|
2187
|
-
return true;
|
|
2188
|
-
} catch {
|
|
2189
|
-
return false;
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
function nextOccurrence(rule, from) {
|
|
2193
|
-
const parsed = parseRecurrenceRule(rule);
|
|
2194
|
-
const base = from || new Date;
|
|
2195
|
-
if (parsed.type === "interval") {
|
|
2196
|
-
const next = new Date(base);
|
|
2197
|
-
if (parsed.unit === "day") {
|
|
2198
|
-
next.setDate(next.getDate() + parsed.interval);
|
|
2199
|
-
} else if (parsed.unit === "week") {
|
|
2200
|
-
next.setDate(next.getDate() + parsed.interval * 7);
|
|
2201
|
-
} else if (parsed.unit === "month") {
|
|
2202
|
-
next.setMonth(next.getMonth() + parsed.interval);
|
|
2203
|
-
}
|
|
2204
|
-
return next.toISOString();
|
|
2205
|
-
}
|
|
2206
|
-
if (parsed.type === "specific_days") {
|
|
2207
|
-
const currentDay = base.getDay();
|
|
2208
|
-
const days = parsed.days;
|
|
2209
|
-
let daysToAdd = Infinity;
|
|
2210
|
-
for (const day of days) {
|
|
2211
|
-
let diff = day - currentDay;
|
|
2212
|
-
if (diff <= 0)
|
|
2213
|
-
diff += 7;
|
|
2214
|
-
if (diff < daysToAdd)
|
|
2215
|
-
daysToAdd = diff;
|
|
2216
|
-
}
|
|
2217
|
-
const next = new Date(base);
|
|
2218
|
-
next.setDate(next.getDate() + daysToAdd);
|
|
2219
|
-
return next.toISOString();
|
|
2220
|
-
}
|
|
2221
|
-
throw new Error(`Cannot calculate next occurrence for rule: "${rule}"`);
|
|
2222
|
-
}
|
|
2223
|
-
var DAY_NAMES;
|
|
2224
|
-
var init_recurrence = __esm(() => {
|
|
2225
|
-
DAY_NAMES = {
|
|
2226
|
-
sunday: 0,
|
|
2227
|
-
sun: 0,
|
|
2228
|
-
monday: 1,
|
|
2229
|
-
mon: 1,
|
|
2230
|
-
tuesday: 2,
|
|
2231
|
-
tue: 2,
|
|
2232
|
-
wednesday: 3,
|
|
2233
|
-
wed: 3,
|
|
2234
|
-
thursday: 4,
|
|
2235
|
-
thu: 4,
|
|
2236
|
-
friday: 5,
|
|
2237
|
-
fri: 5,
|
|
2238
|
-
saturday: 6,
|
|
2239
|
-
sat: 6
|
|
2240
|
-
};
|
|
2241
|
-
});
|
|
2242
|
-
|
|
2243
|
-
// src/lib/doctor.ts
|
|
2244
|
-
var exports_doctor = {};
|
|
2245
|
-
__export(exports_doctor, {
|
|
2246
|
-
runTodosDoctor: () => runTodosDoctor
|
|
2247
|
-
});
|
|
2248
|
-
import { chmodSync, copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync2 } from "fs";
|
|
2249
|
-
import { basename, dirname as dirname5, join as join4 } from "path";
|
|
2250
|
-
function tableExists(db, table) {
|
|
2251
|
-
return Boolean(db.query("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table));
|
|
2252
|
-
}
|
|
2253
|
-
function quoteIdent(identifier) {
|
|
2254
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
2255
|
-
}
|
|
2256
|
-
function getTableColumns(db, table) {
|
|
2257
|
-
try {
|
|
2258
|
-
const rows = db.query(`PRAGMA table_info(${quoteIdent(table)})`).all();
|
|
2259
|
-
return new Set(rows.map((row) => row.name));
|
|
2260
|
-
} catch {
|
|
2261
|
-
return new Set;
|
|
2262
|
-
}
|
|
2263
|
-
}
|
|
2264
|
-
function countQuery(db, sql) {
|
|
2265
|
-
try {
|
|
2266
|
-
const row = db.query(sql).get();
|
|
2267
|
-
return row?.count ?? 0;
|
|
2268
|
-
} catch {
|
|
2269
|
-
return 0;
|
|
2270
|
-
}
|
|
2271
|
-
}
|
|
2272
|
-
function getMigrationLevel(db) {
|
|
2273
|
-
try {
|
|
2274
|
-
const row = db.query("SELECT MAX(id) as max_id FROM _migrations").get();
|
|
2275
|
-
return row?.max_id ?? 0;
|
|
2276
|
-
} catch {
|
|
2277
|
-
return 0;
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
function addCheck(checks, check) {
|
|
2281
|
-
checks.push(check);
|
|
2282
|
-
}
|
|
2283
|
-
function listUserTables(db) {
|
|
2284
|
-
return db.query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all().map((row) => row.name).sort();
|
|
2285
|
-
}
|
|
2286
|
-
function findCorruptJsonMetadata(db) {
|
|
2287
|
-
const corrupt = [];
|
|
2288
|
-
for (const table of listUserTables(db)) {
|
|
2289
|
-
const columns = getTableColumns(db, table);
|
|
2290
|
-
if (!columns.has("metadata"))
|
|
2291
|
-
continue;
|
|
2292
|
-
const rows = db.query(`SELECT rowid, metadata FROM ${quoteIdent(table)} WHERE metadata IS NOT NULL AND metadata != ''`).all();
|
|
2293
|
-
for (const row of rows) {
|
|
2294
|
-
try {
|
|
2295
|
-
JSON.parse(row.metadata);
|
|
2296
|
-
} catch {
|
|
2297
|
-
corrupt.push({ table, column: "metadata", rowid: row.rowid });
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
}
|
|
2301
|
-
return corrupt;
|
|
2302
|
-
}
|
|
2303
|
-
function getIndexColumns(db, indexName) {
|
|
2304
|
-
return db.query(`PRAGMA index_info(${quoteIdent(indexName)})`).all().map((row) => row.name).filter(Boolean);
|
|
2305
|
-
}
|
|
2306
|
-
function findDuplicateIndexes(db) {
|
|
2307
|
-
const duplicates = [];
|
|
2308
|
-
for (const table of listUserTables(db)) {
|
|
2309
|
-
const indexes = db.query(`PRAGMA index_list(${quoteIdent(table)})`).all();
|
|
2310
|
-
const groups = new Map;
|
|
2311
|
-
for (const index of indexes) {
|
|
2312
|
-
if (index.origin === "pk" || index.name.startsWith("sqlite_autoindex"))
|
|
2313
|
-
continue;
|
|
2314
|
-
const columns = getIndexColumns(db, index.name);
|
|
2315
|
-
if (columns.length === 0)
|
|
2316
|
-
continue;
|
|
2317
|
-
const key = `${index.unique}:${columns.join(",")}`;
|
|
2318
|
-
const current = groups.get(key) ?? [];
|
|
2319
|
-
current.push({ name: index.name, origin: index.origin, columns });
|
|
2320
|
-
groups.set(key, current);
|
|
2321
|
-
}
|
|
2322
|
-
for (const group of groups.values()) {
|
|
2323
|
-
if (group.length < 2)
|
|
2324
|
-
continue;
|
|
2325
|
-
const [kept, ...rest] = group;
|
|
2326
|
-
for (const duplicate of rest) {
|
|
2327
|
-
duplicates.push({ table, duplicate: duplicate.name, kept: kept.name, columns: duplicate.columns });
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
return duplicates;
|
|
2332
|
-
}
|
|
2333
|
-
function findMissingProjectRoots(db) {
|
|
2334
|
-
if (!tableExists(db, "projects"))
|
|
2335
|
-
return 0;
|
|
2336
|
-
let missing = 0;
|
|
2337
|
-
const rows = db.query("SELECT path FROM projects WHERE path IS NOT NULL AND path != ''").all();
|
|
2338
|
-
for (const row of rows) {
|
|
2339
|
-
if (/^[a-z]+:\/\//i.test(row.path))
|
|
2340
|
-
continue;
|
|
2341
|
-
if (!row.path.startsWith("/"))
|
|
2342
|
-
continue;
|
|
2343
|
-
if (!existsSync5(row.path))
|
|
2344
|
-
missing++;
|
|
2345
|
-
}
|
|
2346
|
-
return missing;
|
|
2347
|
-
}
|
|
2348
|
-
function addTaskStateChecks(db, checks) {
|
|
2349
|
-
if (!tableExists(db, "tasks"))
|
|
2350
|
-
return;
|
|
2351
|
-
const columns = getTableColumns(db, "tasks");
|
|
2352
|
-
const staleCutoff = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
2353
|
-
if (columns.has("updated_at") && columns.has("status")) {
|
|
2354
|
-
const staleTasks = countQuery(db, `SELECT COUNT(*) as count FROM tasks WHERE status = 'in_progress' AND updated_at < '${staleCutoff}'`);
|
|
2355
|
-
if (staleTasks > 0) {
|
|
2356
|
-
addCheck(checks, {
|
|
2357
|
-
severity: "warn",
|
|
2358
|
-
type: "stale_tasks",
|
|
2359
|
-
message: `${staleTasks} tasks stuck in_progress for more than 30 minutes`,
|
|
2360
|
-
count: staleTasks,
|
|
2361
|
-
repairable: false
|
|
2362
|
-
});
|
|
2363
|
-
}
|
|
2364
|
-
}
|
|
2365
|
-
if (columns.has("recurrence_rule") && columns.has("status")) {
|
|
2366
|
-
const dueAtSelect = columns.has("due_at") ? "due_at" : "NULL as due_at";
|
|
2367
|
-
const recurring = db.query(`SELECT recurrence_rule, ${dueAtSelect} FROM tasks WHERE status IN ('pending', 'in_progress') AND recurrence_rule IS NOT NULL AND recurrence_rule != ''`).all();
|
|
2368
|
-
const invalidRecurrence = recurring.filter((task) => !isValidRecurrenceRule(task.recurrence_rule));
|
|
2369
|
-
if (invalidRecurrence.length > 0) {
|
|
2370
|
-
addCheck(checks, {
|
|
2371
|
-
severity: "error",
|
|
2372
|
-
type: "invalid_recurrence",
|
|
2373
|
-
message: `${invalidRecurrence.length} tasks have invalid recurrence rules`,
|
|
2374
|
-
count: invalidRecurrence.length,
|
|
2375
|
-
repairable: false
|
|
2376
|
-
});
|
|
2377
|
-
}
|
|
2378
|
-
const nowIso = new Date().toISOString();
|
|
2379
|
-
const overdueRecurring = recurring.filter((task) => task.due_at !== null && task.due_at < nowIso);
|
|
2380
|
-
if (overdueRecurring.length > 0) {
|
|
2381
|
-
addCheck(checks, {
|
|
2382
|
-
severity: "warn",
|
|
2383
|
-
type: "overdue_recurring",
|
|
2384
|
-
message: `${overdueRecurring.length} recurring tasks are past due`,
|
|
2385
|
-
count: overdueRecurring.length,
|
|
2386
|
-
repairable: false
|
|
2387
|
-
});
|
|
2388
|
-
}
|
|
2389
|
-
}
|
|
2390
|
-
}
|
|
2391
|
-
function databasePermissionsAreUnsafe(dbPath) {
|
|
2392
|
-
if (dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
2393
|
-
return false;
|
|
2394
|
-
try {
|
|
2395
|
-
return (statSync2(dbPath).mode & 63) !== 0;
|
|
2396
|
-
} catch {
|
|
2397
|
-
return false;
|
|
1451
|
+
_db.close();
|
|
1452
|
+
_db = null;
|
|
2398
1453
|
}
|
|
2399
1454
|
}
|
|
2400
|
-
function
|
|
2401
|
-
|
|
2402
|
-
return;
|
|
2403
|
-
if (!existsSync5(dbPath))
|
|
2404
|
-
return;
|
|
2405
|
-
const stamp = now().replace(/[:.]/g, "-");
|
|
2406
|
-
const backupDir = join4(dirname5(dbPath), `${basename(dbPath)}.backup-${stamp}`);
|
|
2407
|
-
const files = [];
|
|
2408
|
-
mkdirSync4(backupDir, { recursive: true });
|
|
2409
|
-
for (const source of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
2410
|
-
if (!existsSync5(source))
|
|
2411
|
-
continue;
|
|
2412
|
-
const target = join4(backupDir, basename(source));
|
|
2413
|
-
copyFileSync(source, target);
|
|
2414
|
-
files.push(target);
|
|
2415
|
-
}
|
|
2416
|
-
return files.length > 0 ? { path: backupDir, files } : undefined;
|
|
1455
|
+
function resetDatabase() {
|
|
1456
|
+
_db = null;
|
|
2417
1457
|
}
|
|
2418
|
-
function
|
|
2419
|
-
|
|
1458
|
+
function now() {
|
|
1459
|
+
return new Date().toISOString();
|
|
2420
1460
|
}
|
|
2421
|
-
function
|
|
2422
|
-
return
|
|
2423
|
-
errors: checks.filter((check) => check.severity === "error").length,
|
|
2424
|
-
warnings: checks.filter((check) => check.severity === "warn").length,
|
|
2425
|
-
infos: checks.filter((check) => check.severity === "info").length,
|
|
2426
|
-
repairable: checks.filter((check) => check.repairable).length,
|
|
2427
|
-
applied: repairs.filter((repair) => repair.applied).length
|
|
2428
|
-
};
|
|
1461
|
+
function uuid() {
|
|
1462
|
+
return crypto.randomUUID();
|
|
2429
1463
|
}
|
|
2430
|
-
function
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
const
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
message: `Migration level ${migrationCurrent}; expected ${migrationExpected}`,
|
|
2449
|
-
repairable: true
|
|
2450
|
-
});
|
|
2451
|
-
} else {
|
|
2452
|
-
addCheck(checks, {
|
|
2453
|
-
severity: "info",
|
|
2454
|
-
type: "migration_level",
|
|
2455
|
-
message: `Schema at migration ${migrationCurrent}`
|
|
2456
|
-
});
|
|
2457
|
-
}
|
|
2458
|
-
const missingTables = REQUIRED_TABLES.filter((table) => !tableExists(db, table));
|
|
2459
|
-
if (missingTables.length > 0) {
|
|
2460
|
-
addCheck(checks, {
|
|
2461
|
-
severity: "error",
|
|
2462
|
-
type: "missing_schema_tables",
|
|
2463
|
-
message: `Missing schema tables: ${missingTables.join(", ")}`,
|
|
2464
|
-
count: missingTables.length,
|
|
2465
|
-
repairable: true
|
|
2466
|
-
});
|
|
2467
|
-
}
|
|
2468
|
-
const orphanedParents = tableExists(db, "tasks") ? countQuery(db, "SELECT COUNT(*) as count FROM tasks t WHERE t.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tasks p WHERE p.id = t.parent_id)") : 0;
|
|
2469
|
-
if (orphanedParents > 0) {
|
|
2470
|
-
addCheck(checks, {
|
|
2471
|
-
severity: "error",
|
|
2472
|
-
type: "orphaned_task_parents",
|
|
2473
|
-
message: `${orphanedParents} tasks reference missing parent tasks`,
|
|
2474
|
-
count: orphanedParents,
|
|
2475
|
-
repairable: true
|
|
2476
|
-
});
|
|
1464
|
+
function isLockExpired(lockedAt) {
|
|
1465
|
+
if (!lockedAt)
|
|
1466
|
+
return true;
|
|
1467
|
+
const lockTime = new Date(lockedAt).getTime();
|
|
1468
|
+
const expiryMs = LOCK_EXPIRY_MINUTES * 60 * 1000;
|
|
1469
|
+
return Date.now() - lockTime > expiryMs;
|
|
1470
|
+
}
|
|
1471
|
+
function lockExpiryCutoff(nowMs = Date.now()) {
|
|
1472
|
+
const expiryMs = LOCK_EXPIRY_MINUTES * 60 * 1000;
|
|
1473
|
+
return new Date(nowMs - expiryMs).toISOString();
|
|
1474
|
+
}
|
|
1475
|
+
function clearExpiredLocks(db) {
|
|
1476
|
+
const cutoff = lockExpiryCutoff();
|
|
1477
|
+
db.run("UPDATE tasks SET locked_by = NULL, locked_at = NULL WHERE locked_at IS NOT NULL AND locked_at < ?", [cutoff]);
|
|
1478
|
+
}
|
|
1479
|
+
function resolvePartialId(db, table, partialId) {
|
|
1480
|
+
if (!ALLOWED_TABLES.has(table)) {
|
|
1481
|
+
throw new Error(`Invalid table name: ${table}`);
|
|
2477
1482
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
severity: "error",
|
|
2482
|
-
type: "orphaned_task_dependencies",
|
|
2483
|
-
message: `${orphanedDependencies} dependency rows reference missing tasks`,
|
|
2484
|
-
count: orphanedDependencies,
|
|
2485
|
-
repairable: true
|
|
2486
|
-
});
|
|
1483
|
+
if (partialId.length >= 36) {
|
|
1484
|
+
const row = db.query(`SELECT id FROM ${table} WHERE id = ?`).get(partialId);
|
|
1485
|
+
return row?.id ?? null;
|
|
2487
1486
|
}
|
|
2488
|
-
const
|
|
2489
|
-
|
|
2490
|
-
[
|
|
2491
|
-
["task_run_events", "NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = task_run_events.task_id) OR NOT EXISTS (SELECT 1 FROM task_runs r WHERE r.id = task_run_events.run_id)"],
|
|
2492
|
-
["task_run_commands", "NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = task_run_commands.task_id) OR NOT EXISTS (SELECT 1 FROM task_runs r WHERE r.id = task_run_commands.run_id)"],
|
|
2493
|
-
["task_run_artifacts", "NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = task_run_artifacts.task_id) OR NOT EXISTS (SELECT 1 FROM task_runs r WHERE r.id = task_run_artifacts.run_id)"]
|
|
2494
|
-
];
|
|
2495
|
-
let orphanedRows = 0;
|
|
2496
|
-
for (const [table, where] of orphanTables) {
|
|
2497
|
-
if (!tableExists(db, table))
|
|
2498
|
-
continue;
|
|
2499
|
-
orphanedRows += countQuery(db, `SELECT COUNT(*) as count FROM ${table} WHERE ${where}`);
|
|
2500
|
-
}
|
|
2501
|
-
if (orphanedRows > 0) {
|
|
2502
|
-
addCheck(checks, {
|
|
2503
|
-
severity: "error",
|
|
2504
|
-
type: "orphaned_child_rows",
|
|
2505
|
-
message: `${orphanedRows} child rows reference missing tasks or runs`,
|
|
2506
|
-
count: orphanedRows,
|
|
2507
|
-
repairable: true
|
|
2508
|
-
});
|
|
1487
|
+
const rows = db.query(`SELECT id FROM ${table} WHERE id LIKE ?`).all(`${partialId}%`);
|
|
1488
|
+
if (rows.length === 1) {
|
|
1489
|
+
return rows[0].id;
|
|
2509
1490
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
if (corruptJson.length > 0) {
|
|
2513
|
-
addCheck(checks, {
|
|
2514
|
-
severity: "error",
|
|
2515
|
-
type: "corrupt_json_metadata",
|
|
2516
|
-
message: `${corruptJson.length} metadata values are not valid JSON`,
|
|
2517
|
-
count: corruptJson.length,
|
|
2518
|
-
repairable: true
|
|
2519
|
-
});
|
|
1491
|
+
if (rows.length > 1) {
|
|
1492
|
+
return null;
|
|
2520
1493
|
}
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
message: `${duplicateIndexes.length} duplicate index definitions found`,
|
|
2527
|
-
count: duplicateIndexes.length,
|
|
2528
|
-
repairable: true
|
|
2529
|
-
});
|
|
1494
|
+
if (table === "tasks") {
|
|
1495
|
+
const shortIdRows = db.query("SELECT id FROM tasks WHERE short_id = ?").all(partialId);
|
|
1496
|
+
if (shortIdRows.length === 1) {
|
|
1497
|
+
return shortIdRows[0].id;
|
|
1498
|
+
}
|
|
2530
1499
|
}
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
type: "missing_project_roots",
|
|
2536
|
-
message: `${missingProjectRoots} project paths do not exist on this machine`,
|
|
2537
|
-
count: missingProjectRoots,
|
|
2538
|
-
repairable: false
|
|
2539
|
-
});
|
|
1500
|
+
if (table === "task_lists") {
|
|
1501
|
+
const slugRow = db.query("SELECT id FROM task_lists WHERE slug = ?").get(partialId);
|
|
1502
|
+
if (slugRow)
|
|
1503
|
+
return slugRow.id;
|
|
2540
1504
|
}
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
message: "Database file is readable or writable by group/others",
|
|
2546
|
-
repairable: true
|
|
2547
|
-
} : {
|
|
2548
|
-
severity: "info",
|
|
2549
|
-
type: "database_permissions",
|
|
2550
|
-
message: "Database file permissions are private"
|
|
2551
|
-
});
|
|
2552
|
-
let backup;
|
|
2553
|
-
const hasRepairableIssue = checks.some((check) => check.repairable && check.severity !== "info");
|
|
2554
|
-
if (apply && hasRepairableIssue) {
|
|
2555
|
-
backup = createBackup(dbPath);
|
|
2556
|
-
if (backup)
|
|
2557
|
-
pushRepair(repairs, "backup_created", `Created backup at ${backup.path}`, true, backup.files.length);
|
|
2558
|
-
else
|
|
2559
|
-
pushRepair(repairs, "backup_created", "Backup skipped for in-memory or missing database path", false, 0);
|
|
2560
|
-
if (migrationCurrent < migrationExpected || missingTables.length > 0) {
|
|
2561
|
-
runMigrations(db);
|
|
2562
|
-
ensureSchema(db);
|
|
2563
|
-
pushRepair(repairs, "schema_repair", "Ran migration and schema safety net", true);
|
|
2564
|
-
}
|
|
2565
|
-
if (orphanedParents > 0) {
|
|
2566
|
-
db.run("UPDATE tasks SET parent_id = NULL WHERE parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tasks p WHERE p.id = tasks.parent_id)");
|
|
2567
|
-
pushRepair(repairs, "orphaned_task_parents", "Cleared missing parent references", true, orphanedParents);
|
|
2568
|
-
}
|
|
2569
|
-
if (orphanedDependencies > 0 && tableExists(db, "task_dependencies")) {
|
|
2570
|
-
const count = deleteOrphans(db, "task_dependencies", "NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = task_dependencies.task_id) OR NOT EXISTS (SELECT 1 FROM tasks t WHERE t.id = task_dependencies.depends_on)");
|
|
2571
|
-
pushRepair(repairs, "orphaned_task_dependencies", "Deleted dependency rows referencing missing tasks", true, count);
|
|
2572
|
-
}
|
|
2573
|
-
for (const [table, where] of orphanTables) {
|
|
2574
|
-
if (!tableExists(db, table))
|
|
2575
|
-
continue;
|
|
2576
|
-
const count = deleteOrphans(db, table, where);
|
|
2577
|
-
if (count > 0)
|
|
2578
|
-
pushRepair(repairs, "orphaned_child_rows", `Deleted orphaned rows from ${table}`, true, count);
|
|
2579
|
-
}
|
|
2580
|
-
if (corruptJson.length > 0) {
|
|
2581
|
-
for (const cell of corruptJson) {
|
|
2582
|
-
db.run(`UPDATE ${quoteIdent(cell.table)} SET ${quoteIdent(cell.column)} = '{}' WHERE rowid = ?`, [cell.rowid]);
|
|
2583
|
-
}
|
|
2584
|
-
pushRepair(repairs, "corrupt_json_metadata", "Reset invalid metadata JSON values to {}", true, corruptJson.length);
|
|
2585
|
-
}
|
|
2586
|
-
if (duplicateIndexes.length > 0) {
|
|
2587
|
-
let dropped = 0;
|
|
2588
|
-
for (const duplicate of duplicateIndexes) {
|
|
2589
|
-
db.run(`DROP INDEX IF EXISTS ${quoteIdent(duplicate.duplicate)}`);
|
|
2590
|
-
dropped++;
|
|
2591
|
-
}
|
|
2592
|
-
pushRepair(repairs, "duplicate_indexes", "Dropped duplicate non-primary indexes", true, dropped);
|
|
2593
|
-
}
|
|
2594
|
-
if (unsafePermissions) {
|
|
2595
|
-
try {
|
|
2596
|
-
chmodSync(dbPath, 384);
|
|
2597
|
-
pushRepair(repairs, "database_permissions", "Changed database file mode to 0600", true);
|
|
2598
|
-
} catch (error) {
|
|
2599
|
-
pushRepair(repairs, "database_permissions", error instanceof Error ? error.message : "Failed to repair database permissions", false);
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
1505
|
+
if (table === "projects") {
|
|
1506
|
+
const nameRow = db.query("SELECT id FROM projects WHERE lower(name) = ?").get(partialId.toLowerCase());
|
|
1507
|
+
if (nameRow)
|
|
1508
|
+
return nameRow.id;
|
|
2602
1509
|
}
|
|
2603
|
-
|
|
2604
|
-
const summary = summarize2(finalChecks, repairs);
|
|
2605
|
-
return {
|
|
2606
|
-
ok: !finalChecks.some((check) => check.severity === "error"),
|
|
2607
|
-
dry_run: !apply,
|
|
2608
|
-
database_path: dbPath,
|
|
2609
|
-
migration: { current: getMigrationLevel(db), expected: migrationExpected },
|
|
2610
|
-
backup,
|
|
2611
|
-
checks: finalChecks,
|
|
2612
|
-
repairs,
|
|
2613
|
-
summary
|
|
2614
|
-
};
|
|
1510
|
+
return null;
|
|
2615
1511
|
}
|
|
2616
|
-
var
|
|
2617
|
-
var
|
|
2618
|
-
init_database();
|
|
2619
|
-
init_migrations();
|
|
1512
|
+
var LOCK_EXPIRY_MINUTES = 30, _db = null, ALLOWED_TABLES;
|
|
1513
|
+
var init_database = __esm(() => {
|
|
2620
1514
|
init_schema();
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
"_migrations",
|
|
2624
|
-
"projects",
|
|
2625
|
-
"tasks",
|
|
2626
|
-
"plans",
|
|
2627
|
-
"agents",
|
|
2628
|
-
"task_dependencies",
|
|
2629
|
-
"task_comments",
|
|
2630
|
-
"task_runs",
|
|
2631
|
-
"task_run_events",
|
|
2632
|
-
"task_run_commands",
|
|
2633
|
-
"task_run_artifacts"
|
|
2634
|
-
];
|
|
1515
|
+
init_machines();
|
|
1516
|
+
ALLOWED_TABLES = new Set(["tasks", "projects", "agents", "plans", "task_lists", "task_templates"]);
|
|
2635
1517
|
});
|
|
2636
1518
|
|
|
2637
1519
|
// src/lib/package-version.ts
|
|
@@ -2659,8 +1541,8 @@ function getPackageVersion(fromUrl = import.meta.url) {
|
|
|
2659
1541
|
|
|
2660
1542
|
// src/server/serve.ts
|
|
2661
1543
|
init_database();
|
|
2662
|
-
import { existsSync as
|
|
2663
|
-
import { join as
|
|
1544
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1545
|
+
import { join as join5, dirname as dirname3, extname } from "path";
|
|
2664
1546
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2665
1547
|
|
|
2666
1548
|
// src/db/api-keys.ts
|
|
@@ -2771,7 +1653,7 @@ init_database();
|
|
|
2771
1653
|
|
|
2772
1654
|
// src/lib/config.ts
|
|
2773
1655
|
import { existsSync as existsSync4 } from "fs";
|
|
2774
|
-
import {
|
|
1656
|
+
import { join as join3 } from "path";
|
|
2775
1657
|
|
|
2776
1658
|
// src/lib/sync-utils.ts
|
|
2777
1659
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
|
|
@@ -2924,494 +1806,6 @@ function checkCompletionGuard(task, agentId, db, configOverride) {
|
|
|
2924
1806
|
}
|
|
2925
1807
|
}
|
|
2926
1808
|
|
|
2927
|
-
// src/lib/event-hooks.ts
|
|
2928
|
-
import { createHash as createHash2, randomUUID } from "crypto";
|
|
2929
|
-
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
2930
|
-
import { dirname as dirname4, resolve as resolve4 } from "path";
|
|
2931
|
-
import { createConnection } from "net";
|
|
2932
|
-
|
|
2933
|
-
// src/lib/redaction.ts
|
|
2934
|
-
var DEFAULT_SECRET_PATTERNS = [
|
|
2935
|
-
{ name: "aws-access-key", regex: /\b(AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
2936
|
-
{ name: "private-key", regex: /-----BEGIN (?:RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |)PRIVATE KEY-----/g, replacement: "[REDACTED_PRIVATE_KEY]" },
|
|
2937
|
-
{ name: "openai-token", regex: /\bsk-[A-Za-z0-9_-]{12,}\b/g, replacement: "[REDACTED_TOKEN]" },
|
|
2938
|
-
{ name: "env-secret-assignment", regex: /\b([A-Za-z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD)[A-Za-z0-9_]*)\s*=\s*['"]?[^'"\s]{8,}/gi, replacement: "$1=[REDACTED]" },
|
|
2939
|
-
{ name: "bearer-token", regex: /\b(bearer)\s+[A-Za-z0-9._~+/=-]{12,}/gi, replacement: "$1 [REDACTED]" }
|
|
2940
|
-
];
|
|
2941
|
-
var DEFAULT_SECRET_KEY_PATTERN = /api[_-]?key|token|secret|password/i;
|
|
2942
|
-
function unique(values) {
|
|
2943
|
-
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
2944
|
-
}
|
|
2945
|
-
function cloneRegex(regex) {
|
|
2946
|
-
return new RegExp(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`);
|
|
2947
|
-
}
|
|
2948
|
-
function customPatterns() {
|
|
2949
|
-
return unique(loadConfig().secret_safety?.redaction_patterns).flatMap((pattern) => {
|
|
2950
|
-
try {
|
|
2951
|
-
return [{ name: `custom:${pattern}`, regex: new RegExp(pattern, "g") }];
|
|
2952
|
-
} catch {
|
|
2953
|
-
return [];
|
|
2954
|
-
}
|
|
2955
|
-
});
|
|
2956
|
-
}
|
|
2957
|
-
function secretPatterns() {
|
|
2958
|
-
return [...customPatterns(), ...DEFAULT_SECRET_PATTERNS];
|
|
2959
|
-
}
|
|
2960
|
-
function isSecretKey(key) {
|
|
2961
|
-
if (DEFAULT_SECRET_KEY_PATTERN.test(key))
|
|
2962
|
-
return true;
|
|
2963
|
-
return unique(loadConfig().secret_safety?.redaction_keys).some((pattern) => key.toLowerCase().includes(pattern.toLowerCase()));
|
|
2964
|
-
}
|
|
2965
|
-
function redactEvidenceText(value) {
|
|
2966
|
-
let redacted = value;
|
|
2967
|
-
for (const pattern of secretPatterns()) {
|
|
2968
|
-
const regex = cloneRegex(pattern.regex);
|
|
2969
|
-
const replacement = pattern.replacement ?? "[REDACTED]";
|
|
2970
|
-
redacted = typeof replacement === "string" ? redacted.replace(regex, replacement) : redacted.replace(regex, replacement);
|
|
2971
|
-
}
|
|
2972
|
-
return redacted;
|
|
2973
|
-
}
|
|
2974
|
-
function redactValue(value) {
|
|
2975
|
-
if (typeof value === "string")
|
|
2976
|
-
return redactEvidenceText(value);
|
|
2977
|
-
if (Array.isArray(value))
|
|
2978
|
-
return value.map(redactValue);
|
|
2979
|
-
if (value && typeof value === "object") {
|
|
2980
|
-
const redacted = {};
|
|
2981
|
-
for (const [key, child] of Object.entries(value)) {
|
|
2982
|
-
if (isSecretKey(key)) {
|
|
2983
|
-
redacted[key] = "[REDACTED]";
|
|
2984
|
-
} else {
|
|
2985
|
-
redacted[key] = redactValue(child);
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
return redacted;
|
|
2989
|
-
}
|
|
2990
|
-
return value;
|
|
2991
|
-
}
|
|
2992
|
-
|
|
2993
|
-
// src/lib/runner-sandbox.ts
|
|
2994
|
-
import { relative as relative2, resolve as resolve3 } from "path";
|
|
2995
|
-
|
|
2996
|
-
// src/lib/workspace-trust.ts
|
|
2997
|
-
import { relative, resolve as resolve2 } from "path";
|
|
2998
|
-
var DEFAULT_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
2999
|
-
var DEFAULT_ENV_REDACTIONS = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
3000
|
-
var PRESET_DEFAULTS = {
|
|
3001
|
-
restricted: {
|
|
3002
|
-
trusted: false,
|
|
3003
|
-
preset: "restricted",
|
|
3004
|
-
command_allowlist: ["todos"],
|
|
3005
|
-
command_denylist: DEFAULT_DENYLIST,
|
|
3006
|
-
tool_permissions: ["read"],
|
|
3007
|
-
write_scopes: [],
|
|
3008
|
-
env_redactions: DEFAULT_ENV_REDACTIONS,
|
|
3009
|
-
require_prompt_for_unsafe: true
|
|
3010
|
-
},
|
|
3011
|
-
readonly: {
|
|
3012
|
-
trusted: false,
|
|
3013
|
-
preset: "readonly",
|
|
3014
|
-
command_allowlist: ["todos", "git status", "git diff", "bun test"],
|
|
3015
|
-
command_denylist: DEFAULT_DENYLIST,
|
|
3016
|
-
tool_permissions: ["read", "list", "search"],
|
|
3017
|
-
write_scopes: [],
|
|
3018
|
-
env_redactions: DEFAULT_ENV_REDACTIONS,
|
|
3019
|
-
require_prompt_for_unsafe: true
|
|
3020
|
-
},
|
|
3021
|
-
standard: {
|
|
3022
|
-
trusted: true,
|
|
3023
|
-
preset: "standard",
|
|
3024
|
-
command_allowlist: ["todos", "git", "bun", "rg"],
|
|
3025
|
-
command_denylist: DEFAULT_DENYLIST,
|
|
3026
|
-
tool_permissions: ["read", "write", "test", "mcp"],
|
|
3027
|
-
write_scopes: ["."],
|
|
3028
|
-
env_redactions: DEFAULT_ENV_REDACTIONS,
|
|
3029
|
-
require_prompt_for_unsafe: true
|
|
3030
|
-
},
|
|
3031
|
-
trusted: {
|
|
3032
|
-
trusted: true,
|
|
3033
|
-
preset: "trusted",
|
|
3034
|
-
command_allowlist: ["*"],
|
|
3035
|
-
command_denylist: DEFAULT_DENYLIST,
|
|
3036
|
-
tool_permissions: ["*"],
|
|
3037
|
-
write_scopes: ["."],
|
|
3038
|
-
env_redactions: DEFAULT_ENV_REDACTIONS,
|
|
3039
|
-
require_prompt_for_unsafe: false
|
|
3040
|
-
}
|
|
3041
|
-
};
|
|
3042
|
-
function normalizePath(path) {
|
|
3043
|
-
return resolve2(path);
|
|
3044
|
-
}
|
|
3045
|
-
function unique2(values) {
|
|
3046
|
-
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
3047
|
-
}
|
|
3048
|
-
function defaultProfile(root, preset) {
|
|
3049
|
-
return {
|
|
3050
|
-
root,
|
|
3051
|
-
...PRESET_DEFAULTS[preset]
|
|
3052
|
-
};
|
|
3053
|
-
}
|
|
3054
|
-
function configuredProfiles(config = loadConfig()) {
|
|
3055
|
-
return Object.values(config.workspace_trust || {}).map((profile) => ({ ...profile, root: normalizePath(profile.root) })).sort((a, b) => b.root.length - a.root.length);
|
|
3056
|
-
}
|
|
3057
|
-
function isPathInside(root, path) {
|
|
3058
|
-
const rel = relative(root, path);
|
|
3059
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/") && !/^[A-Za-z]:/.test(rel);
|
|
3060
|
-
}
|
|
3061
|
-
function matchesPattern(value, pattern) {
|
|
3062
|
-
if (pattern === "*")
|
|
3063
|
-
return true;
|
|
3064
|
-
if (pattern.includes("*")) {
|
|
3065
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
3066
|
-
return new RegExp(`^${escaped}$`, "i").test(value);
|
|
3067
|
-
}
|
|
3068
|
-
return value === pattern || value.startsWith(`${pattern} `) || value.includes(pattern);
|
|
3069
|
-
}
|
|
3070
|
-
function profileFor(path) {
|
|
3071
|
-
const resolved = normalizePath(path);
|
|
3072
|
-
for (const profile of configuredProfiles()) {
|
|
3073
|
-
if (isPathInside(profile.root, resolved))
|
|
3074
|
-
return { profile, matchedRoot: profile.root };
|
|
3075
|
-
}
|
|
3076
|
-
return { profile: defaultProfile(resolved, "restricted"), matchedRoot: null };
|
|
3077
|
-
}
|
|
3078
|
-
function getWorkspaceTrustStatus(path = process.cwd()) {
|
|
3079
|
-
const root = normalizePath(path);
|
|
3080
|
-
const { profile, matchedRoot } = profileFor(root);
|
|
3081
|
-
return {
|
|
3082
|
-
root,
|
|
3083
|
-
trusted: profile.trusted,
|
|
3084
|
-
matched_root: matchedRoot,
|
|
3085
|
-
profile
|
|
3086
|
-
};
|
|
3087
|
-
}
|
|
3088
|
-
function writeAllowed(profile, root, writePath) {
|
|
3089
|
-
const target = normalizePath(writePath.startsWith("/") ? writePath : `${root}/${writePath}`);
|
|
3090
|
-
return profile.write_scopes.some((scope) => {
|
|
3091
|
-
const scopeRoot = normalizePath(scope.startsWith("/") ? scope : `${root}/${scope}`);
|
|
3092
|
-
return isPathInside(scopeRoot, target);
|
|
3093
|
-
});
|
|
3094
|
-
}
|
|
3095
|
-
function redactedEnvKeys(profile, env) {
|
|
3096
|
-
if (!env)
|
|
3097
|
-
return [];
|
|
3098
|
-
const patterns = unique2([...DEFAULT_ENV_REDACTIONS, ...profile.env_redactions]).map((item) => item.toUpperCase());
|
|
3099
|
-
return Object.keys(env).filter((key) => patterns.some((pattern) => key.toUpperCase().includes(pattern)));
|
|
3100
|
-
}
|
|
3101
|
-
function checkWorkspacePermission(input = {}) {
|
|
3102
|
-
const status = getWorkspaceTrustStatus(input.path || process.cwd());
|
|
3103
|
-
const reasons = [];
|
|
3104
|
-
const profile = status.profile;
|
|
3105
|
-
if (!status.matched_root)
|
|
3106
|
-
reasons.push("workspace is not trusted");
|
|
3107
|
-
if (input.command) {
|
|
3108
|
-
if (profile.command_denylist.some((pattern) => matchesPattern(input.command, pattern))) {
|
|
3109
|
-
reasons.push("command matches denylist");
|
|
3110
|
-
} else if (!profile.command_allowlist.some((pattern) => matchesPattern(input.command, pattern))) {
|
|
3111
|
-
reasons.push("command is not in allowlist");
|
|
3112
|
-
}
|
|
3113
|
-
}
|
|
3114
|
-
if (input.tool && !profile.tool_permissions.some((permission) => matchesPattern(input.tool, permission))) {
|
|
3115
|
-
reasons.push("tool permission is not allowed");
|
|
3116
|
-
}
|
|
3117
|
-
if (input.write_path && !writeAllowed(profile, status.matched_root || status.root, input.write_path)) {
|
|
3118
|
-
reasons.push("write path is outside allowed scopes");
|
|
3119
|
-
}
|
|
3120
|
-
const redacted = redactedEnvKeys(profile, input.env);
|
|
3121
|
-
const allowed = reasons.length === 0;
|
|
3122
|
-
return {
|
|
3123
|
-
allowed,
|
|
3124
|
-
requires_prompt: !allowed && profile.require_prompt_for_unsafe,
|
|
3125
|
-
reasons,
|
|
3126
|
-
status,
|
|
3127
|
-
redacted_env_keys: redacted
|
|
3128
|
-
};
|
|
3129
|
-
}
|
|
3130
|
-
|
|
3131
|
-
// src/lib/runner-sandbox.ts
|
|
3132
|
-
var DEFAULT_COMMAND_DENYLIST = ["rm -rf", "mkfs", "dd if=", "curl | sh", "wget | sh"];
|
|
3133
|
-
var DEFAULT_ENV_REDACTIONS2 = ["API_KEY", "TOKEN", "SECRET", "PASSWORD", "AUTH"];
|
|
3134
|
-
function normalizePath2(path) {
|
|
3135
|
-
return resolve3(path);
|
|
3136
|
-
}
|
|
3137
|
-
function unique3(values) {
|
|
3138
|
-
return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
|
|
3139
|
-
}
|
|
3140
|
-
function configuredProfiles2(config = loadConfig()) {
|
|
3141
|
-
return Object.values(config.runner_sandboxes || {}).map((profile) => ({
|
|
3142
|
-
...profile,
|
|
3143
|
-
root: normalizePath2(profile.root),
|
|
3144
|
-
cwd_boundary: normalizePath2(profile.cwd_boundary || profile.root)
|
|
3145
|
-
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
3146
|
-
}
|
|
3147
|
-
function isPathInside2(root, path) {
|
|
3148
|
-
const rel = relative2(root, path);
|
|
3149
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/") && !/^[A-Za-z]:/.test(rel);
|
|
3150
|
-
}
|
|
3151
|
-
function matchesPattern2(value, pattern) {
|
|
3152
|
-
if (pattern === "*")
|
|
3153
|
-
return true;
|
|
3154
|
-
if (pattern.includes("*")) {
|
|
3155
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
3156
|
-
return new RegExp(`^${escaped}$`, "i").test(value);
|
|
3157
|
-
}
|
|
3158
|
-
return value === pattern || value.startsWith(`${pattern} `) || value.includes(pattern);
|
|
3159
|
-
}
|
|
3160
|
-
function resolveFromRoot(root, path) {
|
|
3161
|
-
return normalizePath2(path.startsWith("/") ? path : `${root}/${path}`);
|
|
3162
|
-
}
|
|
3163
|
-
function defaultProfile2(name, root) {
|
|
3164
|
-
const normalizedRoot = normalizePath2(root);
|
|
3165
|
-
return {
|
|
3166
|
-
name,
|
|
3167
|
-
root: normalizedRoot,
|
|
3168
|
-
command_allowlist: ["todos", "git", "bun"],
|
|
3169
|
-
command_denylist: DEFAULT_COMMAND_DENYLIST,
|
|
3170
|
-
cwd_boundary: normalizedRoot,
|
|
3171
|
-
write_scopes: ["."],
|
|
3172
|
-
env_allowlist: ["PATH", "HOME", "SHELL", "TMPDIR", "TEMP", "TMP", "CI", "NODE_ENV", "BUN_ENV"],
|
|
3173
|
-
env_redactions: DEFAULT_ENV_REDACTIONS2,
|
|
3174
|
-
network_policy: "none",
|
|
3175
|
-
require_approval: true,
|
|
3176
|
-
audit_evidence: true
|
|
3177
|
-
};
|
|
3178
|
-
}
|
|
3179
|
-
function profileByName(name, path) {
|
|
3180
|
-
const profiles = configuredProfiles2();
|
|
3181
|
-
if (name) {
|
|
3182
|
-
const found = profiles.find((profile) => profile.name === name);
|
|
3183
|
-
if (found)
|
|
3184
|
-
return found;
|
|
3185
|
-
return defaultProfile2(name, path);
|
|
3186
|
-
}
|
|
3187
|
-
const resolved = normalizePath2(path);
|
|
3188
|
-
return profiles.find((profile) => isPathInside2(profile.root, resolved)) || defaultProfile2("default", resolved);
|
|
3189
|
-
}
|
|
3190
|
-
function redactedEnvKeys2(profile, env) {
|
|
3191
|
-
if (!env)
|
|
3192
|
-
return [];
|
|
3193
|
-
const patterns = unique3([...DEFAULT_ENV_REDACTIONS2, ...profile.env_redactions]).map((item) => item.toUpperCase());
|
|
3194
|
-
return Object.keys(env).filter((key) => patterns.some((pattern) => key.toUpperCase().includes(pattern)));
|
|
3195
|
-
}
|
|
3196
|
-
function omittedEnvKeys(profile, env) {
|
|
3197
|
-
if (!env)
|
|
3198
|
-
return [];
|
|
3199
|
-
if (profile.env_allowlist.includes("*"))
|
|
3200
|
-
return [];
|
|
3201
|
-
return Object.keys(env).filter((key) => !profile.env_allowlist.some((pattern) => matchesPattern2(key, pattern)));
|
|
3202
|
-
}
|
|
3203
|
-
function resolveFromCwd(cwd, path) {
|
|
3204
|
-
return normalizePath2(path.startsWith("/") ? path : `${cwd}/${path}`);
|
|
3205
|
-
}
|
|
3206
|
-
function writeAllowed2(profile, cwd, writePath) {
|
|
3207
|
-
const target = resolveFromCwd(cwd, writePath);
|
|
3208
|
-
return profile.write_scopes.some((scope) => isPathInside2(resolveFromRoot(profile.root, scope), target));
|
|
3209
|
-
}
|
|
3210
|
-
function checkRunnerSandbox(input = {}) {
|
|
3211
|
-
const path = normalizePath2(input.path || input.cwd || process.cwd());
|
|
3212
|
-
const profile = profileByName(input.name, path);
|
|
3213
|
-
const cwd = resolveFromRoot(profile.root, input.cwd || profile.root);
|
|
3214
|
-
const reasons = [];
|
|
3215
|
-
const writePaths = input.write_paths || [];
|
|
3216
|
-
const resolvedWritePaths = writePaths.map((writePath) => resolveFromCwd(cwd, writePath));
|
|
3217
|
-
if (!isPathInside2(profile.cwd_boundary, cwd))
|
|
3218
|
-
reasons.push("cwd is outside sandbox boundary");
|
|
3219
|
-
if (input.command) {
|
|
3220
|
-
if (profile.command_denylist.some((pattern) => matchesPattern2(input.command, pattern))) {
|
|
3221
|
-
reasons.push("command matches sandbox denylist");
|
|
3222
|
-
} else if (!profile.command_allowlist.some((pattern) => matchesPattern2(input.command, pattern))) {
|
|
3223
|
-
reasons.push("command is not in sandbox allowlist");
|
|
3224
|
-
}
|
|
3225
|
-
}
|
|
3226
|
-
for (const writePath of writePaths) {
|
|
3227
|
-
if (!writeAllowed2(profile, cwd, writePath)) {
|
|
3228
|
-
reasons.push(`write path is outside sandbox scopes: ${writePath}`);
|
|
3229
|
-
}
|
|
3230
|
-
}
|
|
3231
|
-
if (input.network && profile.network_policy === "none") {
|
|
3232
|
-
reasons.push("network access is disabled by sandbox policy");
|
|
3233
|
-
}
|
|
3234
|
-
const trustChecks = [
|
|
3235
|
-
checkWorkspacePermission({ path: profile.root, command: input.command, env: input.env }),
|
|
3236
|
-
...resolvedWritePaths.map((writePath) => checkWorkspacePermission({ path: profile.root, write_path: writePath }))
|
|
3237
|
-
];
|
|
3238
|
-
for (const trust of trustChecks) {
|
|
3239
|
-
for (const reason of trust.reasons)
|
|
3240
|
-
reasons.push(`workspace trust: ${reason}`);
|
|
3241
|
-
}
|
|
3242
|
-
const redacted = redactedEnvKeys2(profile, input.env);
|
|
3243
|
-
const omitted = omittedEnvKeys(profile, input.env);
|
|
3244
|
-
const effective = Object.keys(input.env || {}).filter((key) => !omitted.includes(key));
|
|
3245
|
-
const uniqueReasons = unique3(reasons);
|
|
3246
|
-
const allowed = uniqueReasons.length === 0;
|
|
3247
|
-
return {
|
|
3248
|
-
allowed,
|
|
3249
|
-
requires_approval: !allowed && profile.require_approval,
|
|
3250
|
-
reasons: uniqueReasons,
|
|
3251
|
-
profile,
|
|
3252
|
-
redacted_env_keys: redacted,
|
|
3253
|
-
omitted_env_keys: omitted,
|
|
3254
|
-
effective_env_keys: effective,
|
|
3255
|
-
audit_evidence: profile.audit_evidence ? {
|
|
3256
|
-
sandbox: profile.name,
|
|
3257
|
-
root: profile.root,
|
|
3258
|
-
cwd,
|
|
3259
|
-
command: input.command,
|
|
3260
|
-
write_paths: writePaths,
|
|
3261
|
-
network_requested: Boolean(input.network),
|
|
3262
|
-
network_policy: profile.network_policy,
|
|
3263
|
-
allowed,
|
|
3264
|
-
reasons: uniqueReasons
|
|
3265
|
-
} : null
|
|
3266
|
-
};
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
// src/lib/event-hooks.ts
|
|
3270
|
-
var VALID_TARGETS = new Set(["stdout", "file", "socket", "script"]);
|
|
3271
|
-
function clampAttempts(value) {
|
|
3272
|
-
if (!Number.isFinite(value))
|
|
3273
|
-
return 1;
|
|
3274
|
-
return Math.min(5, Math.max(1, Math.trunc(value)));
|
|
3275
|
-
}
|
|
3276
|
-
function eventMatches(hook, eventType) {
|
|
3277
|
-
return hook.enabled !== false && (hook.events.includes("*") || hook.events.includes(eventType));
|
|
3278
|
-
}
|
|
3279
|
-
function canonicalEvent(input) {
|
|
3280
|
-
return JSON.stringify(input);
|
|
3281
|
-
}
|
|
3282
|
-
function buildEnvelope(type, payload, timestamp = new Date().toISOString()) {
|
|
3283
|
-
const base = {
|
|
3284
|
-
id: randomUUID(),
|
|
3285
|
-
type,
|
|
3286
|
-
timestamp,
|
|
3287
|
-
payload: redactValue(payload ?? {}),
|
|
3288
|
-
source: { package: "@hasna/todos", local_only: true }
|
|
3289
|
-
};
|
|
3290
|
-
const digest = createHash2("sha256").update(canonicalEvent(base)).digest("hex");
|
|
3291
|
-
return { ...base, integrity: { algorithm: "sha256", digest } };
|
|
3292
|
-
}
|
|
3293
|
-
function summarize(value) {
|
|
3294
|
-
const redacted = redactEvidenceText(value.trim());
|
|
3295
|
-
if (!redacted)
|
|
3296
|
-
return;
|
|
3297
|
-
return redacted.length > 1000 ? `${redacted.slice(0, 997)}...` : redacted;
|
|
3298
|
-
}
|
|
3299
|
-
function sleep(ms) {
|
|
3300
|
-
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
3301
|
-
}
|
|
3302
|
-
async function writeSocket(socketPath, line) {
|
|
3303
|
-
await new Promise((resolveWrite, rejectWrite) => {
|
|
3304
|
-
const socket = createConnection(socketPath);
|
|
3305
|
-
const timeout = setTimeout(() => {
|
|
3306
|
-
socket.destroy();
|
|
3307
|
-
rejectWrite(new Error(`socket write timed out: ${socketPath}`));
|
|
3308
|
-
}, 1000);
|
|
3309
|
-
socket.on("error", (error) => {
|
|
3310
|
-
clearTimeout(timeout);
|
|
3311
|
-
rejectWrite(error);
|
|
3312
|
-
});
|
|
3313
|
-
socket.on("connect", () => {
|
|
3314
|
-
socket.end(line, () => {
|
|
3315
|
-
clearTimeout(timeout);
|
|
3316
|
-
resolveWrite();
|
|
3317
|
-
});
|
|
3318
|
-
});
|
|
3319
|
-
});
|
|
3320
|
-
}
|
|
3321
|
-
async function deliverScript(hook, envelope) {
|
|
3322
|
-
const command = hook.command;
|
|
3323
|
-
const cwd = hook.cwd || process.cwd();
|
|
3324
|
-
if (hook.sandbox) {
|
|
3325
|
-
const check = checkRunnerSandbox({ name: hook.sandbox, cwd, command, env: hook.env });
|
|
3326
|
-
if (!check.allowed)
|
|
3327
|
-
throw new Error(check.reasons.join("; "));
|
|
3328
|
-
}
|
|
3329
|
-
const proc = Bun.spawn(["bash", "-lc", command], {
|
|
3330
|
-
cwd,
|
|
3331
|
-
env: {
|
|
3332
|
-
...process.env,
|
|
3333
|
-
...hook.env || {},
|
|
3334
|
-
TODOS_EVENT_JSON: JSON.stringify(envelope),
|
|
3335
|
-
TODOS_EVENT_ID: envelope.id,
|
|
3336
|
-
TODOS_EVENT_TYPE: envelope.type,
|
|
3337
|
-
TODOS_EVENT_INTEGRITY: envelope.integrity.digest,
|
|
3338
|
-
TODOS_HOOK_NAME: hook.name
|
|
3339
|
-
},
|
|
3340
|
-
stdout: "pipe",
|
|
3341
|
-
stderr: "pipe"
|
|
3342
|
-
});
|
|
3343
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
3344
|
-
new Response(proc.stdout).text(),
|
|
3345
|
-
new Response(proc.stderr).text(),
|
|
3346
|
-
proc.exited
|
|
3347
|
-
]);
|
|
3348
|
-
return { exitCode, output: summarize([stdout, stderr].filter(Boolean).join(`
|
|
3349
|
-
`)) };
|
|
3350
|
-
}
|
|
3351
|
-
async function deliverHook(hook, envelope) {
|
|
3352
|
-
const line = `${JSON.stringify(envelope)}
|
|
3353
|
-
`;
|
|
3354
|
-
const maxAttempts = clampAttempts(hook.retry?.attempts ?? 1);
|
|
3355
|
-
const backoffMs = Math.max(0, hook.retry?.backoff_ms ?? 0);
|
|
3356
|
-
let lastError;
|
|
3357
|
-
let output;
|
|
3358
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
3359
|
-
try {
|
|
3360
|
-
if (hook.target === "stdout") {
|
|
3361
|
-
output = line.trim();
|
|
3362
|
-
} else if (hook.target === "file") {
|
|
3363
|
-
const filePath = resolve4(hook.file_path);
|
|
3364
|
-
mkdirSync3(dirname4(filePath), { recursive: true });
|
|
3365
|
-
appendFileSync(filePath, line);
|
|
3366
|
-
} else if (hook.target === "socket") {
|
|
3367
|
-
await writeSocket(hook.socket_path, line);
|
|
3368
|
-
} else {
|
|
3369
|
-
const result = await deliverScript(hook, envelope);
|
|
3370
|
-
output = result.output;
|
|
3371
|
-
if (result.exitCode !== 0)
|
|
3372
|
-
throw new Error(`script exited ${result.exitCode}${output ? `: ${output}` : ""}`);
|
|
3373
|
-
}
|
|
3374
|
-
return {
|
|
3375
|
-
hook: hook.name,
|
|
3376
|
-
event_id: envelope.id,
|
|
3377
|
-
event_type: envelope.type,
|
|
3378
|
-
target: hook.target,
|
|
3379
|
-
status: "delivered",
|
|
3380
|
-
attempts: attempt,
|
|
3381
|
-
integrity: envelope.integrity,
|
|
3382
|
-
output_summary: output
|
|
3383
|
-
};
|
|
3384
|
-
} catch (error) {
|
|
3385
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
3386
|
-
if (attempt < maxAttempts && backoffMs > 0)
|
|
3387
|
-
await sleep(backoffMs);
|
|
3388
|
-
}
|
|
3389
|
-
}
|
|
3390
|
-
return {
|
|
3391
|
-
hook: hook.name,
|
|
3392
|
-
event_id: envelope.id,
|
|
3393
|
-
event_type: envelope.type,
|
|
3394
|
-
target: hook.target,
|
|
3395
|
-
status: "failed",
|
|
3396
|
-
attempts: maxAttempts,
|
|
3397
|
-
integrity: envelope.integrity,
|
|
3398
|
-
error: redactEvidenceText(lastError || "delivery failed")
|
|
3399
|
-
};
|
|
3400
|
-
}
|
|
3401
|
-
function listLocalEventHooks() {
|
|
3402
|
-
return Object.values(loadConfig().local_event_hooks || {}).sort((a, b) => a.name.localeCompare(b.name));
|
|
3403
|
-
}
|
|
3404
|
-
async function emitLocalEventHooks(input) {
|
|
3405
|
-
const hooks = (input.hooks || listLocalEventHooks()).filter((hook) => eventMatches(hook, input.type));
|
|
3406
|
-
if (hooks.length === 0)
|
|
3407
|
-
return [];
|
|
3408
|
-
const envelope = buildEnvelope(input.type, input.payload, input.timestamp);
|
|
3409
|
-
return Promise.all(hooks.map((hook) => deliverHook(hook, envelope)));
|
|
3410
|
-
}
|
|
3411
|
-
function emitLocalEventHooksQuiet(input) {
|
|
3412
|
-
emitLocalEventHooks(input).catch(() => {});
|
|
3413
|
-
}
|
|
3414
|
-
|
|
3415
1809
|
// src/db/audit.ts
|
|
3416
1810
|
init_database();
|
|
3417
1811
|
function logTaskChange(taskId, action, field, oldValue, newValue, agentId, db) {
|
|
@@ -3679,8 +2073,8 @@ function createTask(input, db) {
|
|
|
3679
2073
|
let id = uuid();
|
|
3680
2074
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
3681
2075
|
try {
|
|
3682
|
-
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes,
|
|
3683
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
2076
|
+
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, assigned_from_project, task_type)
|
|
2077
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3684
2078
|
id,
|
|
3685
2079
|
null,
|
|
3686
2080
|
input.project_id || null,
|
|
@@ -3702,7 +2096,6 @@ function createTask(input, db) {
|
|
|
3702
2096
|
timestamp,
|
|
3703
2097
|
input.due_at || null,
|
|
3704
2098
|
input.estimated_minutes || null,
|
|
3705
|
-
input.sla_minutes ?? null,
|
|
3706
2099
|
input.confidence ?? null,
|
|
3707
2100
|
input.retry_count ?? 0,
|
|
3708
2101
|
input.max_retries ?? 3,
|
|
@@ -3982,10 +2375,6 @@ function updateTask(id, input, db) {
|
|
|
3982
2375
|
sets.push("estimated_minutes = ?");
|
|
3983
2376
|
params.push(input.estimated_minutes);
|
|
3984
2377
|
}
|
|
3985
|
-
if (input.sla_minutes !== undefined) {
|
|
3986
|
-
sets.push("sla_minutes = ?");
|
|
3987
|
-
params.push(input.sla_minutes);
|
|
3988
|
-
}
|
|
3989
2378
|
if (input.actual_minutes !== undefined) {
|
|
3990
2379
|
sets.push("actual_minutes = ?");
|
|
3991
2380
|
params.push(input.actual_minutes);
|
|
@@ -4050,14 +2439,9 @@ function updateTask(id, input, db) {
|
|
|
4050
2439
|
logTaskChange(id, "approve", "approved_by", null, input.approved_by, agentId, d);
|
|
4051
2440
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to) {
|
|
4052
2441
|
dispatchWebhook("task.assigned", { id, assigned_to: input.assigned_to, title: task.title }, d).catch(() => {});
|
|
4053
|
-
emitLocalEventHooksQuiet({ type: "task.assigned", payload: { id, assigned_to: input.assigned_to, title: task.title } });
|
|
4054
2442
|
}
|
|
4055
2443
|
if (input.status !== undefined && input.status !== task.status) {
|
|
4056
2444
|
dispatchWebhook("task.status_changed", { id, old_status: task.status, new_status: input.status, title: task.title }, d).catch(() => {});
|
|
4057
|
-
emitLocalEventHooksQuiet({ type: "task.status_changed", payload: { id, old_status: task.status, new_status: input.status, title: task.title } });
|
|
4058
|
-
}
|
|
4059
|
-
if (input.approved_by !== undefined) {
|
|
4060
|
-
emitLocalEventHooksQuiet({ type: "approval.decided", payload: { id, approved_by: input.approved_by, title: task.title } });
|
|
4061
2445
|
}
|
|
4062
2446
|
return {
|
|
4063
2447
|
...task,
|
|
@@ -4067,7 +2451,6 @@ function updateTask(id, input, db) {
|
|
|
4067
2451
|
version: task.version + 1,
|
|
4068
2452
|
updated_at: timestamp,
|
|
4069
2453
|
completed_at: input.status === "completed" ? completionTimestamp : input.completed_at !== undefined ? input.completed_at : task.completed_at,
|
|
4070
|
-
sla_minutes: input.sla_minutes !== undefined ? input.sla_minutes : task.sla_minutes,
|
|
4071
2454
|
actual_minutes: input.actual_minutes ?? task.actual_minutes,
|
|
4072
2455
|
confidence: input.confidence !== undefined ? input.confidence : task.confidence,
|
|
4073
2456
|
retry_count: input.retry_count ?? task.retry_count,
|
|
@@ -4083,9 +2466,95 @@ function deleteTask(id, db) {
|
|
|
4083
2466
|
const result = d.run("DELETE FROM tasks WHERE id = ?", [id]);
|
|
4084
2467
|
return result.changes > 0;
|
|
4085
2468
|
}
|
|
4086
|
-
// src/db/task-lifecycle.ts
|
|
4087
|
-
init_database();
|
|
4088
|
-
|
|
2469
|
+
// src/db/task-lifecycle.ts
|
|
2470
|
+
init_database();
|
|
2471
|
+
|
|
2472
|
+
// src/lib/recurrence.ts
|
|
2473
|
+
var DAY_NAMES = {
|
|
2474
|
+
sunday: 0,
|
|
2475
|
+
sun: 0,
|
|
2476
|
+
monday: 1,
|
|
2477
|
+
mon: 1,
|
|
2478
|
+
tuesday: 2,
|
|
2479
|
+
tue: 2,
|
|
2480
|
+
wednesday: 3,
|
|
2481
|
+
wed: 3,
|
|
2482
|
+
thursday: 4,
|
|
2483
|
+
thu: 4,
|
|
2484
|
+
friday: 5,
|
|
2485
|
+
fri: 5,
|
|
2486
|
+
saturday: 6,
|
|
2487
|
+
sat: 6
|
|
2488
|
+
};
|
|
2489
|
+
function parseRecurrenceRule(rule) {
|
|
2490
|
+
const normalized = rule.trim().toLowerCase();
|
|
2491
|
+
if (normalized === "every weekday" || normalized === "every weekdays") {
|
|
2492
|
+
return { type: "specific_days", days: [1, 2, 3, 4, 5] };
|
|
2493
|
+
}
|
|
2494
|
+
if (normalized === "every day" || normalized === "daily") {
|
|
2495
|
+
return { type: "interval", interval: 1, unit: "day" };
|
|
2496
|
+
}
|
|
2497
|
+
if (normalized === "every week" || normalized === "weekly") {
|
|
2498
|
+
return { type: "interval", interval: 1, unit: "week" };
|
|
2499
|
+
}
|
|
2500
|
+
if (normalized === "every month" || normalized === "monthly") {
|
|
2501
|
+
return { type: "interval", interval: 1, unit: "month" };
|
|
2502
|
+
}
|
|
2503
|
+
const intervalMatch = normalized.match(/^every\s+(\d+)\s+(day|week|month)s?$/);
|
|
2504
|
+
if (intervalMatch) {
|
|
2505
|
+
return {
|
|
2506
|
+
type: "interval",
|
|
2507
|
+
interval: parseInt(intervalMatch[1], 10),
|
|
2508
|
+
unit: intervalMatch[2]
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
const daysMatch = normalized.match(/^every\s+(.+)$/);
|
|
2512
|
+
if (daysMatch) {
|
|
2513
|
+
const dayParts = daysMatch[1].split(/[,\s]+/).map((d) => d.trim()).filter(Boolean);
|
|
2514
|
+
const days = [];
|
|
2515
|
+
for (const part of dayParts) {
|
|
2516
|
+
const dayNum = DAY_NAMES[part];
|
|
2517
|
+
if (dayNum !== undefined) {
|
|
2518
|
+
days.push(dayNum);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
if (days.length > 0) {
|
|
2522
|
+
return { type: "specific_days", days: days.sort((a, b) => a - b) };
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
throw new Error(`Invalid recurrence rule: "${rule}". Supported formats: "every day", "every weekday", "every week", "every 2 weeks", "every month", "every N days/weeks/months", "every monday", "every mon,wed,fri"`);
|
|
2526
|
+
}
|
|
2527
|
+
function nextOccurrence(rule, from) {
|
|
2528
|
+
const parsed = parseRecurrenceRule(rule);
|
|
2529
|
+
const base = from || new Date;
|
|
2530
|
+
if (parsed.type === "interval") {
|
|
2531
|
+
const next = new Date(base);
|
|
2532
|
+
if (parsed.unit === "day") {
|
|
2533
|
+
next.setDate(next.getDate() + parsed.interval);
|
|
2534
|
+
} else if (parsed.unit === "week") {
|
|
2535
|
+
next.setDate(next.getDate() + parsed.interval * 7);
|
|
2536
|
+
} else if (parsed.unit === "month") {
|
|
2537
|
+
next.setMonth(next.getMonth() + parsed.interval);
|
|
2538
|
+
}
|
|
2539
|
+
return next.toISOString();
|
|
2540
|
+
}
|
|
2541
|
+
if (parsed.type === "specific_days") {
|
|
2542
|
+
const currentDay = base.getDay();
|
|
2543
|
+
const days = parsed.days;
|
|
2544
|
+
let daysToAdd = Infinity;
|
|
2545
|
+
for (const day of days) {
|
|
2546
|
+
let diff = day - currentDay;
|
|
2547
|
+
if (diff <= 0)
|
|
2548
|
+
diff += 7;
|
|
2549
|
+
if (diff < daysToAdd)
|
|
2550
|
+
daysToAdd = diff;
|
|
2551
|
+
}
|
|
2552
|
+
const next = new Date(base);
|
|
2553
|
+
next.setDate(next.getDate() + daysToAdd);
|
|
2554
|
+
return next.toISOString();
|
|
2555
|
+
}
|
|
2556
|
+
throw new Error(`Cannot calculate next occurrence for rule: "${rule}"`);
|
|
2557
|
+
}
|
|
4089
2558
|
|
|
4090
2559
|
// src/db/templates.ts
|
|
4091
2560
|
init_database();
|
|
@@ -4212,13 +2681,6 @@ function getTaskDependencies(taskId, db) {
|
|
|
4212
2681
|
|
|
4213
2682
|
// src/db/task-lifecycle.ts
|
|
4214
2683
|
var MAX_SPAWN_DEPTH = 10;
|
|
4215
|
-
function assertStartable(task, agentId) {
|
|
4216
|
-
if (task.status === "pending")
|
|
4217
|
-
return;
|
|
4218
|
-
if (task.status === "in_progress")
|
|
4219
|
-
return;
|
|
4220
|
-
throw new Error(`Task is ${task.status} and cannot be started by ${agentId}`);
|
|
4221
|
-
}
|
|
4222
2684
|
function getBlockingDeps(id, db) {
|
|
4223
2685
|
const d = db || getDatabase();
|
|
4224
2686
|
const deps = getTaskDependencies(id, d);
|
|
@@ -4237,38 +2699,22 @@ function startTask(id, agentId, db) {
|
|
|
4237
2699
|
const task = getTask(id, d);
|
|
4238
2700
|
if (!task)
|
|
4239
2701
|
throw new TaskNotFoundError(id);
|
|
4240
|
-
assertStartable(task, agentId);
|
|
4241
2702
|
const blocking = getBlockingDeps(id, d);
|
|
4242
2703
|
if (blocking.length > 0) {
|
|
4243
2704
|
const blockerIds = blocking.map((b) => b.id.slice(0, 8)).join(", ");
|
|
4244
|
-
emitLocalEventHooksQuiet({
|
|
4245
|
-
type: "task.blocked",
|
|
4246
|
-
payload: {
|
|
4247
|
-
id,
|
|
4248
|
-
agent_id: agentId,
|
|
4249
|
-
title: task.title,
|
|
4250
|
-
blockers: blocking.map((b) => ({ id: b.id, short_id: b.short_id, title: b.title, status: b.status }))
|
|
4251
|
-
}
|
|
4252
|
-
});
|
|
4253
2705
|
throw new Error(`Task is blocked by ${blocking.length} unfinished dependency(ies): ${blockerIds}`);
|
|
4254
2706
|
}
|
|
4255
2707
|
const cutoff = lockExpiryCutoff();
|
|
4256
2708
|
const timestamp = now();
|
|
4257
2709
|
const result = d.run(`UPDATE tasks SET status = 'in_progress', assigned_to = ?, locked_by = ?, locked_at = ?, started_at = COALESCE(started_at, ?), version = version + 1, updated_at = ?
|
|
4258
|
-
WHERE id = ? AND
|
|
2710
|
+
WHERE id = ? AND (locked_by IS NULL OR locked_by = ? OR locked_at < ?)`, [agentId, agentId, timestamp, timestamp, timestamp, id, agentId, cutoff]);
|
|
4259
2711
|
if (result.changes === 0) {
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
throw new TaskNotFoundError(id);
|
|
4263
|
-
assertStartable(current, agentId);
|
|
4264
|
-
if (current.locked_by && current.locked_by !== agentId && !isLockExpired(current.locked_at)) {
|
|
4265
|
-
throw new LockError(id, current.locked_by);
|
|
2712
|
+
if (task.locked_by && task.locked_by !== agentId && !isLockExpired(task.locked_at)) {
|
|
2713
|
+
throw new LockError(id, task.locked_by);
|
|
4266
2714
|
}
|
|
4267
|
-
throw new Error(`Task ${id} could not be started because it changed during claim`);
|
|
4268
2715
|
}
|
|
4269
2716
|
logTaskChange(id, "start", "status", "pending", "in_progress", agentId, d);
|
|
4270
2717
|
dispatchWebhook("task.started", { id, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
4271
|
-
emitLocalEventHooksQuiet({ type: "task.started", payload: { id, agent_id: agentId, title: task.title } });
|
|
4272
2718
|
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 };
|
|
4273
2719
|
}
|
|
4274
2720
|
function completeTask(id, agentId, db, options) {
|
|
@@ -4306,10 +2752,9 @@ function completeTask(id, agentId, db, options) {
|
|
|
4306
2752
|
tx();
|
|
4307
2753
|
logTaskChange(id, "complete", "status", task.status, "completed", agentId || null, d);
|
|
4308
2754
|
dispatchWebhook("task.completed", { id, agent_id: agentId, title: task.title, completed_at: timestamp }, d).catch(() => {});
|
|
4309
|
-
emitLocalEventHooksQuiet({ type: "task.completed", payload: { id, agent_id: agentId, title: task.title, completed_at: timestamp } });
|
|
4310
2755
|
let spawnedTask = null;
|
|
4311
2756
|
if (task.recurrence_rule && !options?.skip_recurrence) {
|
|
4312
|
-
spawnedTask = spawnNextRecurrence(task, d
|
|
2757
|
+
spawnedTask = spawnNextRecurrence(task, d);
|
|
4313
2758
|
}
|
|
4314
2759
|
let spawnedFromTemplate = null;
|
|
4315
2760
|
if (task.spawns_template_id) {
|
|
@@ -4348,7 +2793,6 @@ function completeTask(id, agentId, db, options) {
|
|
|
4348
2793
|
meta._unblocked = unblockedDeps.map((d2) => ({ id: d2.id, short_id: d2.short_id, title: d2.title }));
|
|
4349
2794
|
for (const dep of unblockedDeps) {
|
|
4350
2795
|
dispatchWebhook("task.unblocked", { id: dep.id, unblocked_by: id, title: dep.title }, d).catch(() => {});
|
|
4351
|
-
emitLocalEventHooksQuiet({ type: "task.unblocked", payload: { id: dep.id, unblocked_by: id, title: dep.title } });
|
|
4352
2796
|
}
|
|
4353
2797
|
}
|
|
4354
2798
|
return { ...task, status: "completed", locked_by: null, locked_at: null, completed_at: timestamp, confidence, version: task.version + 1, updated_at: timestamp, metadata: meta };
|
|
@@ -4461,7 +2905,6 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
4461
2905
|
WHERE id = ?`, [JSON.stringify(meta), timestamp, id]);
|
|
4462
2906
|
logTaskChange(id, "fail", "status", task.status, "failed", agentId || null, d);
|
|
4463
2907
|
dispatchWebhook("task.failed", { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title }, d).catch(() => {});
|
|
4464
|
-
emitLocalEventHooksQuiet({ type: "task.failed", payload: { id, reason, error_code: options?.error_code, agent_id: agentId, title: task.title } });
|
|
4465
2908
|
const failedTask = {
|
|
4466
2909
|
...task,
|
|
4467
2910
|
status: "failed",
|
|
@@ -4506,31 +2949,28 @@ function failTask(id, agentId, reason, options, db) {
|
|
|
4506
2949
|
}
|
|
4507
2950
|
return { task: failedTask, retryTask };
|
|
4508
2951
|
}
|
|
4509
|
-
function getStaleTasks(
|
|
2952
|
+
function getStaleTasks(staleMinutes = 30, filters, db) {
|
|
4510
2953
|
const d = db || getDatabase();
|
|
4511
|
-
const staleMinutes = typeof staleQuery === "number" ? staleQuery : staleQuery.minutes ?? (staleQuery.hours !== undefined ? staleQuery.hours * 60 : 30);
|
|
4512
|
-
const effectiveFilters = typeof staleQuery === "number" ? filters : { project_id: staleQuery.project_id, task_list_id: staleQuery.task_list_id };
|
|
4513
2954
|
const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000).toISOString();
|
|
4514
2955
|
const conditions = [
|
|
4515
2956
|
"status = 'in_progress'",
|
|
4516
2957
|
"(updated_at < ? OR (locked_at IS NOT NULL AND locked_at < ?))"
|
|
4517
2958
|
];
|
|
4518
2959
|
const params = [cutoff, cutoff];
|
|
4519
|
-
if (
|
|
2960
|
+
if (filters?.project_id) {
|
|
4520
2961
|
conditions.push("project_id = ?");
|
|
4521
|
-
params.push(
|
|
2962
|
+
params.push(filters.project_id);
|
|
4522
2963
|
}
|
|
4523
|
-
if (
|
|
2964
|
+
if (filters?.task_list_id) {
|
|
4524
2965
|
conditions.push("task_list_id = ?");
|
|
4525
|
-
params.push(
|
|
2966
|
+
params.push(filters.task_list_id);
|
|
4526
2967
|
}
|
|
4527
2968
|
const where = conditions.join(" AND ");
|
|
4528
2969
|
const rows = d.query(`SELECT * FROM tasks WHERE ${where} ORDER BY updated_at ASC`).all(...params);
|
|
4529
2970
|
return rows.map(rowToTask);
|
|
4530
2971
|
}
|
|
4531
|
-
function spawnNextRecurrence(completedTask, db
|
|
4532
|
-
const
|
|
4533
|
-
const dueAt = nextOccurrence(completedTask.recurrence_rule, recurrenceBase);
|
|
2972
|
+
function spawnNextRecurrence(completedTask, db) {
|
|
2973
|
+
const dueAt = nextOccurrence(completedTask.recurrence_rule, new Date);
|
|
4534
2974
|
let title = completedTask.title;
|
|
4535
2975
|
if (completedTask.short_id && title.startsWith(completedTask.short_id + ": ")) {
|
|
4536
2976
|
title = title.slice(completedTask.short_id.length + 2);
|
|
@@ -4547,7 +2987,6 @@ function spawnNextRecurrence(completedTask, db, completedAt) {
|
|
|
4547
2987
|
tags: completedTask.tags,
|
|
4548
2988
|
metadata: completedTask.metadata,
|
|
4549
2989
|
estimated_minutes: completedTask.estimated_minutes ?? undefined,
|
|
4550
|
-
sla_minutes: completedTask.sla_minutes ?? undefined,
|
|
4551
2990
|
recurrence_rule: completedTask.recurrence_rule,
|
|
4552
2991
|
recurrence_parent_id: recurrenceParentId,
|
|
4553
2992
|
due_at: dueAt
|
|
@@ -4639,130 +3078,6 @@ function getTaskStats(filters, db) {
|
|
|
4639
3078
|
}
|
|
4640
3079
|
// src/db/task-relations.ts
|
|
4641
3080
|
init_database();
|
|
4642
|
-
// src/db/boards.ts
|
|
4643
|
-
init_database();
|
|
4644
|
-
|
|
4645
|
-
// src/db/plans.ts
|
|
4646
|
-
init_database();
|
|
4647
|
-
function createPlan(input, db) {
|
|
4648
|
-
const d = db || getDatabase();
|
|
4649
|
-
const id = uuid();
|
|
4650
|
-
const timestamp = now();
|
|
4651
|
-
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
|
|
4652
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4653
|
-
id,
|
|
4654
|
-
input.project_id || null,
|
|
4655
|
-
input.task_list_id || null,
|
|
4656
|
-
input.agent_id || null,
|
|
4657
|
-
input.name,
|
|
4658
|
-
input.description || null,
|
|
4659
|
-
input.status || "active",
|
|
4660
|
-
timestamp,
|
|
4661
|
-
timestamp
|
|
4662
|
-
]);
|
|
4663
|
-
return getPlan(id, d);
|
|
4664
|
-
}
|
|
4665
|
-
function getPlan(id, db) {
|
|
4666
|
-
const d = db || getDatabase();
|
|
4667
|
-
const row = d.query("SELECT * FROM plans WHERE id = ?").get(id);
|
|
4668
|
-
return row;
|
|
4669
|
-
}
|
|
4670
|
-
function listPlans(projectId, db) {
|
|
4671
|
-
const d = db || getDatabase();
|
|
4672
|
-
if (projectId) {
|
|
4673
|
-
return d.query("SELECT * FROM plans WHERE project_id = ? ORDER BY created_at DESC").all(projectId);
|
|
4674
|
-
}
|
|
4675
|
-
return d.query("SELECT * FROM plans ORDER BY created_at DESC").all();
|
|
4676
|
-
}
|
|
4677
|
-
function updatePlan(id, input, db) {
|
|
4678
|
-
const d = db || getDatabase();
|
|
4679
|
-
const plan = getPlan(id, d);
|
|
4680
|
-
if (!plan)
|
|
4681
|
-
throw new PlanNotFoundError(id);
|
|
4682
|
-
const sets = ["updated_at = ?"];
|
|
4683
|
-
const params = [now()];
|
|
4684
|
-
if (input.name !== undefined) {
|
|
4685
|
-
sets.push("name = ?");
|
|
4686
|
-
params.push(input.name);
|
|
4687
|
-
}
|
|
4688
|
-
if (input.description !== undefined) {
|
|
4689
|
-
sets.push("description = ?");
|
|
4690
|
-
params.push(input.description);
|
|
4691
|
-
}
|
|
4692
|
-
if (input.status !== undefined) {
|
|
4693
|
-
sets.push("status = ?");
|
|
4694
|
-
params.push(input.status);
|
|
4695
|
-
}
|
|
4696
|
-
if (input.task_list_id !== undefined) {
|
|
4697
|
-
sets.push("task_list_id = ?");
|
|
4698
|
-
params.push(input.task_list_id);
|
|
4699
|
-
}
|
|
4700
|
-
if (input.agent_id !== undefined) {
|
|
4701
|
-
sets.push("agent_id = ?");
|
|
4702
|
-
params.push(input.agent_id);
|
|
4703
|
-
}
|
|
4704
|
-
params.push(id);
|
|
4705
|
-
d.run(`UPDATE plans SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
4706
|
-
const updated = getPlan(id, d);
|
|
4707
|
-
emitLocalEventHooksQuiet({
|
|
4708
|
-
type: "plan.updated",
|
|
4709
|
-
payload: { id, old_status: plan.status, new_status: updated.status, name: updated.name, project_id: updated.project_id }
|
|
4710
|
-
});
|
|
4711
|
-
return updated;
|
|
4712
|
-
}
|
|
4713
|
-
function deletePlan(id, db) {
|
|
4714
|
-
const d = db || getDatabase();
|
|
4715
|
-
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
4716
|
-
return result.changes > 0;
|
|
4717
|
-
}
|
|
4718
|
-
// src/db/calendar.ts
|
|
4719
|
-
init_database();
|
|
4720
|
-
|
|
4721
|
-
// src/lib/artifact-store.ts
|
|
4722
|
-
init_database();
|
|
4723
|
-
|
|
4724
|
-
// src/db/comments.ts
|
|
4725
|
-
init_database();
|
|
4726
|
-
function addComment(input, db) {
|
|
4727
|
-
const d = db || getDatabase();
|
|
4728
|
-
if (!getTask(input.task_id, d)) {
|
|
4729
|
-
throw new TaskNotFoundError(input.task_id);
|
|
4730
|
-
}
|
|
4731
|
-
const id = uuid();
|
|
4732
|
-
const timestamp = now();
|
|
4733
|
-
d.run(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
4734
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
4735
|
-
id,
|
|
4736
|
-
input.task_id,
|
|
4737
|
-
input.agent_id || null,
|
|
4738
|
-
input.session_id || null,
|
|
4739
|
-
redactEvidenceText(input.content),
|
|
4740
|
-
input.type || "comment",
|
|
4741
|
-
input.progress_pct ?? null,
|
|
4742
|
-
timestamp
|
|
4743
|
-
]);
|
|
4744
|
-
return getComment(id, d);
|
|
4745
|
-
}
|
|
4746
|
-
function logProgress(taskId, message, pct, agentId, db) {
|
|
4747
|
-
return addComment({ task_id: taskId, content: message, type: "progress", progress_pct: pct, agent_id: agentId }, db);
|
|
4748
|
-
}
|
|
4749
|
-
function getComment(id, db) {
|
|
4750
|
-
const d = db || getDatabase();
|
|
4751
|
-
return d.query("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
4752
|
-
}
|
|
4753
|
-
function listComments(taskId, db) {
|
|
4754
|
-
const d = db || getDatabase();
|
|
4755
|
-
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
4756
|
-
}
|
|
4757
|
-
|
|
4758
|
-
// src/db/task-runs.ts
|
|
4759
|
-
init_database();
|
|
4760
|
-
|
|
4761
|
-
// src/db/task-files.ts
|
|
4762
|
-
init_database();
|
|
4763
|
-
|
|
4764
|
-
// src/db/task-commits.ts
|
|
4765
|
-
init_database();
|
|
4766
3081
|
// src/db/agents.ts
|
|
4767
3082
|
init_database();
|
|
4768
3083
|
|
|
@@ -4838,10 +3153,63 @@ var NICE_AGENT_NAMES = [
|
|
|
4838
3153
|
"vesper",
|
|
4839
3154
|
"zephyr"
|
|
4840
3155
|
];
|
|
3156
|
+
var EXTENDED_AGENT_NAMES = [
|
|
3157
|
+
"agrippa",
|
|
3158
|
+
"antonius",
|
|
3159
|
+
"aurelian",
|
|
3160
|
+
"aurelius",
|
|
3161
|
+
"camillus",
|
|
3162
|
+
"cassius",
|
|
3163
|
+
"celer",
|
|
3164
|
+
"cincinnatus",
|
|
3165
|
+
"corvus",
|
|
3166
|
+
"drusus",
|
|
3167
|
+
"fabius",
|
|
3168
|
+
"faustus",
|
|
3169
|
+
"flaccus",
|
|
3170
|
+
"gallus",
|
|
3171
|
+
"gaius",
|
|
3172
|
+
"horatius",
|
|
3173
|
+
"lucius",
|
|
3174
|
+
"lucullus",
|
|
3175
|
+
"marius",
|
|
3176
|
+
"marcellus",
|
|
3177
|
+
"maximus",
|
|
3178
|
+
"nerva",
|
|
3179
|
+
"pompey",
|
|
3180
|
+
"quintus",
|
|
3181
|
+
"regulus",
|
|
3182
|
+
"romulus",
|
|
3183
|
+
"scipio",
|
|
3184
|
+
"seneca",
|
|
3185
|
+
"sertorius",
|
|
3186
|
+
"sulla",
|
|
3187
|
+
"tacitus",
|
|
3188
|
+
"varro",
|
|
3189
|
+
"vitruvius",
|
|
3190
|
+
"plato",
|
|
3191
|
+
"socrates",
|
|
3192
|
+
"aristotle",
|
|
3193
|
+
"heraclitus",
|
|
3194
|
+
"democritus",
|
|
3195
|
+
"pythagoras",
|
|
3196
|
+
"hipparchus",
|
|
3197
|
+
"euclid",
|
|
3198
|
+
"archimedes",
|
|
3199
|
+
"zeno",
|
|
3200
|
+
"anaximander",
|
|
3201
|
+
"epictetus",
|
|
3202
|
+
"aeschylus",
|
|
3203
|
+
"sophocles",
|
|
3204
|
+
"euripides",
|
|
3205
|
+
"xenophon",
|
|
3206
|
+
"diogenes"
|
|
3207
|
+
];
|
|
4841
3208
|
var PREFERRED_AGENT_NAMES = [
|
|
4842
3209
|
...ROMAN_AGENT_NAMES,
|
|
4843
3210
|
...GREEK_AGENT_NAMES,
|
|
4844
|
-
...NICE_AGENT_NAMES
|
|
3211
|
+
...NICE_AGENT_NAMES,
|
|
3212
|
+
...EXTENDED_AGENT_NAMES
|
|
4845
3213
|
];
|
|
4846
3214
|
var RESERVED_GENERIC_NAMES = new Set([
|
|
4847
3215
|
"agent",
|
|
@@ -4859,12 +3227,48 @@ var RESERVED_GENERIC_NAMES = new Set([
|
|
|
4859
3227
|
]);
|
|
4860
3228
|
var NUMERIC_SUFFIX_RE = /[-_]\d+$/;
|
|
4861
3229
|
var ONE_WORD_NAME_RE = /^[a-z]+$/;
|
|
3230
|
+
var ROMAN_NUMERAL_SUFFIXES = new Set([
|
|
3231
|
+
"i",
|
|
3232
|
+
"ii",
|
|
3233
|
+
"iii",
|
|
3234
|
+
"iv",
|
|
3235
|
+
"v",
|
|
3236
|
+
"vi",
|
|
3237
|
+
"vii",
|
|
3238
|
+
"viii",
|
|
3239
|
+
"ix",
|
|
3240
|
+
"x",
|
|
3241
|
+
"xi",
|
|
3242
|
+
"xii",
|
|
3243
|
+
"xiii",
|
|
3244
|
+
"xiv",
|
|
3245
|
+
"xv",
|
|
3246
|
+
"xvi",
|
|
3247
|
+
"xvii",
|
|
3248
|
+
"xviii",
|
|
3249
|
+
"xix",
|
|
3250
|
+
"xx"
|
|
3251
|
+
]);
|
|
3252
|
+
var PREFERRED_NAMES_BY_LENGTH = [...PREFERRED_AGENT_NAMES].sort((a, b) => b.length - a.length);
|
|
4862
3253
|
function normalizeAgentNameInput(name) {
|
|
4863
3254
|
return name.trim().toLowerCase();
|
|
4864
3255
|
}
|
|
4865
3256
|
function hasGeneratedNumericSuffix(name) {
|
|
4866
3257
|
return NUMERIC_SUFFIX_RE.test(normalizeAgentNameInput(name));
|
|
4867
3258
|
}
|
|
3259
|
+
function generatedAgentNameBase(name) {
|
|
3260
|
+
const normalized = normalizeAgentNameInput(name);
|
|
3261
|
+
for (const base of PREFERRED_NAMES_BY_LENGTH) {
|
|
3262
|
+
if (normalized === base || !normalized.startsWith(base))
|
|
3263
|
+
continue;
|
|
3264
|
+
const suffix = normalized.slice(base.length);
|
|
3265
|
+
const separatedRoman = suffix.match(/^[-_](.+)$/)?.[1];
|
|
3266
|
+
if (ROMAN_NUMERAL_SUFFIXES.has(suffix) || separatedRoman && ROMAN_NUMERAL_SUFFIXES.has(separatedRoman)) {
|
|
3267
|
+
return base;
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
return null;
|
|
3271
|
+
}
|
|
4868
3272
|
function isGenericAgentName(name) {
|
|
4869
3273
|
const normalized = normalizeAgentNameInput(name);
|
|
4870
3274
|
if (RESERVED_GENERIC_NAMES.has(normalized))
|
|
@@ -4879,29 +3283,93 @@ function isGenericAgentName(name) {
|
|
|
4879
3283
|
}
|
|
4880
3284
|
return false;
|
|
4881
3285
|
}
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
3286
|
+
var FALLBACK_PREFIXES = [
|
|
3287
|
+
"arv",
|
|
3288
|
+
"bel",
|
|
3289
|
+
"cyr",
|
|
3290
|
+
"dax",
|
|
3291
|
+
"elun",
|
|
3292
|
+
"feno",
|
|
3293
|
+
"gavor",
|
|
3294
|
+
"hiro",
|
|
3295
|
+
"ivar",
|
|
3296
|
+
"jaro",
|
|
3297
|
+
"kavo",
|
|
3298
|
+
"lumo",
|
|
3299
|
+
"myr",
|
|
3300
|
+
"navo",
|
|
3301
|
+
"prax",
|
|
3302
|
+
"quor",
|
|
3303
|
+
"riven",
|
|
3304
|
+
"sovan",
|
|
3305
|
+
"tavor",
|
|
3306
|
+
"ulmor",
|
|
3307
|
+
"vexo",
|
|
3308
|
+
"wiro",
|
|
3309
|
+
"yaro",
|
|
3310
|
+
"zel"
|
|
3311
|
+
];
|
|
3312
|
+
var FALLBACK_STEMS = [
|
|
3313
|
+
"al",
|
|
3314
|
+
"ber",
|
|
3315
|
+
"cor",
|
|
3316
|
+
"dren",
|
|
3317
|
+
"el",
|
|
3318
|
+
"far",
|
|
3319
|
+
"gor",
|
|
3320
|
+
"hal",
|
|
3321
|
+
"ion",
|
|
3322
|
+
"jor",
|
|
3323
|
+
"kel",
|
|
3324
|
+
"lor",
|
|
3325
|
+
"mor",
|
|
3326
|
+
"nel",
|
|
3327
|
+
"or",
|
|
3328
|
+
"per",
|
|
3329
|
+
"quil",
|
|
3330
|
+
"ron",
|
|
3331
|
+
"ser",
|
|
3332
|
+
"tor",
|
|
3333
|
+
"um",
|
|
3334
|
+
"ver",
|
|
3335
|
+
"wyn",
|
|
3336
|
+
"xil"
|
|
3337
|
+
];
|
|
3338
|
+
var FALLBACK_ENDINGS = [
|
|
3339
|
+
"a",
|
|
3340
|
+
"en",
|
|
3341
|
+
"ia",
|
|
3342
|
+
"is",
|
|
3343
|
+
"on",
|
|
3344
|
+
"or",
|
|
3345
|
+
"um",
|
|
3346
|
+
"us",
|
|
3347
|
+
"yn",
|
|
3348
|
+
"ar",
|
|
3349
|
+
"el",
|
|
3350
|
+
"ir"
|
|
3351
|
+
];
|
|
3352
|
+
function generatedFallbackAgentName(index) {
|
|
3353
|
+
const perPrefix = FALLBACK_STEMS.length * FALLBACK_ENDINGS.length;
|
|
3354
|
+
const total = FALLBACK_PREFIXES.length * perPrefix;
|
|
3355
|
+
if (index >= total)
|
|
3356
|
+
return null;
|
|
3357
|
+
const prefix = FALLBACK_PREFIXES[Math.floor(index / perPrefix)];
|
|
3358
|
+
const rest = index % perPrefix;
|
|
3359
|
+
const stem = FALLBACK_STEMS[Math.floor(rest / FALLBACK_ENDINGS.length)];
|
|
3360
|
+
const ending = FALLBACK_ENDINGS[rest % FALLBACK_ENDINGS.length];
|
|
3361
|
+
return `${prefix}${stem}${ending}`;
|
|
4891
3362
|
}
|
|
4892
3363
|
function suggestAgentNames(existingNames = []) {
|
|
4893
3364
|
const existing = new Set([...existingNames].map(normalizeAgentNameInput));
|
|
4894
3365
|
const suggestions = PREFERRED_AGENT_NAMES.filter((name) => !existing.has(name));
|
|
4895
|
-
for (let
|
|
4896
|
-
const
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
if (suggestions.length >= 20)
|
|
4903
|
-
break;
|
|
4904
|
-
}
|
|
3366
|
+
for (let index = 0;suggestions.length < 20; index++) {
|
|
3367
|
+
const candidate = generatedFallbackAgentName(index);
|
|
3368
|
+
if (!candidate)
|
|
3369
|
+
break;
|
|
3370
|
+
if (existing.has(candidate) || suggestions.includes(candidate))
|
|
3371
|
+
continue;
|
|
3372
|
+
suggestions.push(candidate);
|
|
4905
3373
|
}
|
|
4906
3374
|
return suggestions;
|
|
4907
3375
|
}
|
|
@@ -4923,11 +3391,21 @@ function validateAgentName(name, existingNames = []) {
|
|
|
4923
3391
|
if (hasGeneratedNumericSuffix(normalized)) {
|
|
4924
3392
|
throw new InvalidAgentNameError(name, "numbered suffix names are not allowed; pick a distinct human-readable name", suggestions);
|
|
4925
3393
|
}
|
|
3394
|
+
if (generatedAgentNameBase(normalized)) {
|
|
3395
|
+
throw new InvalidAgentNameError(name, "generated suffix variants like caesarii or valeria-ii are reserved; use the base name or a distinct name", suggestions);
|
|
3396
|
+
}
|
|
4926
3397
|
if (!ONE_WORD_NAME_RE.test(normalized)) {
|
|
4927
3398
|
throw new InvalidAgentNameError(name, "use one word made of letters only, preferably a Roman or Greek name", suggestions);
|
|
4928
3399
|
}
|
|
4929
3400
|
return normalized;
|
|
4930
3401
|
}
|
|
3402
|
+
var MERGE_DEDUP_REFERENCE_COLUMNS = new Set([
|
|
3403
|
+
"agent_budgets.agent_id",
|
|
3404
|
+
"file_locks.agent_id",
|
|
3405
|
+
"project_agent_roles.agent_id",
|
|
3406
|
+
"resource_locks.agent_id",
|
|
3407
|
+
"task_watchers.agent_id"
|
|
3408
|
+
]);
|
|
4931
3409
|
|
|
4932
3410
|
// src/db/agents.ts
|
|
4933
3411
|
function getActiveWindowMs() {
|
|
@@ -5164,6 +3642,78 @@ function getOrgChart(db) {
|
|
|
5164
3642
|
return buildTree(null);
|
|
5165
3643
|
}
|
|
5166
3644
|
|
|
3645
|
+
// src/db/plans.ts
|
|
3646
|
+
init_database();
|
|
3647
|
+
function createPlan(input, db) {
|
|
3648
|
+
const d = db || getDatabase();
|
|
3649
|
+
const id = uuid();
|
|
3650
|
+
const timestamp = now();
|
|
3651
|
+
d.run(`INSERT INTO plans (id, project_id, task_list_id, agent_id, name, description, status, created_at, updated_at)
|
|
3652
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3653
|
+
id,
|
|
3654
|
+
input.project_id || null,
|
|
3655
|
+
input.task_list_id || null,
|
|
3656
|
+
input.agent_id || null,
|
|
3657
|
+
input.name,
|
|
3658
|
+
input.description || null,
|
|
3659
|
+
input.status || "active",
|
|
3660
|
+
timestamp,
|
|
3661
|
+
timestamp
|
|
3662
|
+
]);
|
|
3663
|
+
return getPlan(id, d);
|
|
3664
|
+
}
|
|
3665
|
+
function getPlan(id, db) {
|
|
3666
|
+
const d = db || getDatabase();
|
|
3667
|
+
const row = d.query("SELECT * FROM plans WHERE id = ?").get(id);
|
|
3668
|
+
return row;
|
|
3669
|
+
}
|
|
3670
|
+
function listPlans(projectId, db) {
|
|
3671
|
+
const d = db || getDatabase();
|
|
3672
|
+
if (projectId) {
|
|
3673
|
+
return d.query("SELECT * FROM plans WHERE project_id = ? ORDER BY created_at DESC").all(projectId);
|
|
3674
|
+
}
|
|
3675
|
+
return d.query("SELECT * FROM plans ORDER BY created_at DESC").all();
|
|
3676
|
+
}
|
|
3677
|
+
function updatePlan(id, input, db) {
|
|
3678
|
+
const d = db || getDatabase();
|
|
3679
|
+
const plan = getPlan(id, d);
|
|
3680
|
+
if (!plan)
|
|
3681
|
+
throw new PlanNotFoundError(id);
|
|
3682
|
+
const sets = ["updated_at = ?"];
|
|
3683
|
+
const params = [now()];
|
|
3684
|
+
if (input.name !== undefined) {
|
|
3685
|
+
sets.push("name = ?");
|
|
3686
|
+
params.push(input.name);
|
|
3687
|
+
}
|
|
3688
|
+
if (input.description !== undefined) {
|
|
3689
|
+
sets.push("description = ?");
|
|
3690
|
+
params.push(input.description);
|
|
3691
|
+
}
|
|
3692
|
+
if (input.status !== undefined) {
|
|
3693
|
+
sets.push("status = ?");
|
|
3694
|
+
params.push(input.status);
|
|
3695
|
+
}
|
|
3696
|
+
if (input.task_list_id !== undefined) {
|
|
3697
|
+
sets.push("task_list_id = ?");
|
|
3698
|
+
params.push(input.task_list_id);
|
|
3699
|
+
}
|
|
3700
|
+
if (input.agent_id !== undefined) {
|
|
3701
|
+
sets.push("agent_id = ?");
|
|
3702
|
+
params.push(input.agent_id);
|
|
3703
|
+
}
|
|
3704
|
+
params.push(id);
|
|
3705
|
+
d.run(`UPDATE plans SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
3706
|
+
return getPlan(id, d);
|
|
3707
|
+
}
|
|
3708
|
+
function deletePlan(id, db) {
|
|
3709
|
+
const d = db || getDatabase();
|
|
3710
|
+
const result = d.run("DELETE FROM plans WHERE id = ?", [id]);
|
|
3711
|
+
return result.changes > 0;
|
|
3712
|
+
}
|
|
3713
|
+
|
|
3714
|
+
// src/server/routes.ts
|
|
3715
|
+
init_database();
|
|
3716
|
+
|
|
5167
3717
|
// src/db/orgs.ts
|
|
5168
3718
|
init_database();
|
|
5169
3719
|
function rowToOrg(row) {
|
|
@@ -5213,8 +3763,42 @@ function deleteOrg(id, db) {
|
|
|
5213
3763
|
return d.run("DELETE FROM orgs WHERE id = ?", [id]).changes > 0;
|
|
5214
3764
|
}
|
|
5215
3765
|
|
|
3766
|
+
// src/db/comments.ts
|
|
3767
|
+
init_database();
|
|
3768
|
+
function addComment(input, db) {
|
|
3769
|
+
const d = db || getDatabase();
|
|
3770
|
+
if (!getTask(input.task_id, d)) {
|
|
3771
|
+
throw new TaskNotFoundError(input.task_id);
|
|
3772
|
+
}
|
|
3773
|
+
const id = uuid();
|
|
3774
|
+
const timestamp = now();
|
|
3775
|
+
d.run(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, type, progress_pct, created_at)
|
|
3776
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
3777
|
+
id,
|
|
3778
|
+
input.task_id,
|
|
3779
|
+
input.agent_id || null,
|
|
3780
|
+
input.session_id || null,
|
|
3781
|
+
input.content,
|
|
3782
|
+
input.type || "comment",
|
|
3783
|
+
input.progress_pct ?? null,
|
|
3784
|
+
timestamp
|
|
3785
|
+
]);
|
|
3786
|
+
return getComment(id, d);
|
|
3787
|
+
}
|
|
3788
|
+
function logProgress(taskId, message, pct, agentId, db) {
|
|
3789
|
+
return addComment({ task_id: taskId, content: message, type: "progress", progress_pct: pct, agent_id: agentId }, db);
|
|
3790
|
+
}
|
|
3791
|
+
function getComment(id, db) {
|
|
3792
|
+
const d = db || getDatabase();
|
|
3793
|
+
return d.query("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
3794
|
+
}
|
|
3795
|
+
function listComments(taskId, db) {
|
|
3796
|
+
const d = db || getDatabase();
|
|
3797
|
+
return d.query("SELECT * FROM task_comments WHERE task_id = ? ORDER BY created_at").all(taskId);
|
|
3798
|
+
}
|
|
3799
|
+
|
|
5216
3800
|
// src/server/routes.ts
|
|
5217
|
-
import { join as
|
|
3801
|
+
import { join as join4, resolve as resolve2, sep } from "path";
|
|
5218
3802
|
function parseFieldsParam(url) {
|
|
5219
3803
|
const fieldsParam = url.searchParams.get("fields");
|
|
5220
3804
|
return fieldsParam ? fieldsParam.split(",").map((f) => f.trim()).filter(Boolean) : undefined;
|
|
@@ -5785,8 +4369,16 @@ async function handleBulkDeleteProjects(req, _ctx, json2) {
|
|
|
5785
4369
|
}
|
|
5786
4370
|
}
|
|
5787
4371
|
function handleDoctor(_ctx, json2) {
|
|
5788
|
-
const
|
|
5789
|
-
|
|
4372
|
+
const issues = [];
|
|
4373
|
+
const staleItems = getStaleTasks(30);
|
|
4374
|
+
if (staleItems.length > 0)
|
|
4375
|
+
issues.push({ severity: "warn", type: "stale_tasks", message: `${staleItems.length} tasks stuck in_progress >30min`, count: staleItems.length });
|
|
4376
|
+
const withParent = getDatabase().query("SELECT COUNT(*) as c FROM tasks t WHERE t.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM tasks p WHERE p.id = t.parent_id)").get();
|
|
4377
|
+
if (withParent.c > 0)
|
|
4378
|
+
issues.push({ severity: "error", type: "orphaned_parents", message: `${withParent.c} tasks reference non-existent parent IDs`, count: withParent.c });
|
|
4379
|
+
if (issues.length === 0)
|
|
4380
|
+
issues.push({ severity: "info", type: "healthy", message: "No issues found" });
|
|
4381
|
+
return json2({ ok: !issues.some((i) => i.severity === "error"), issues });
|
|
5790
4382
|
}
|
|
5791
4383
|
function handleReport(_req, url, _ctx, json2) {
|
|
5792
4384
|
const days = parseInt(url.searchParams.get("days") || "7", 10);
|
|
@@ -5914,9 +4506,9 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
5914
4506
|
if (!ctx.dashboardExists || method !== "GET" && method !== "HEAD")
|
|
5915
4507
|
return null;
|
|
5916
4508
|
if (path !== "/") {
|
|
5917
|
-
const filePath =
|
|
5918
|
-
const resolvedFile =
|
|
5919
|
-
const resolvedBase =
|
|
4509
|
+
const filePath = join4(ctx.dashboardDir, path);
|
|
4510
|
+
const resolvedFile = resolve2(filePath);
|
|
4511
|
+
const resolvedBase = resolve2(ctx.dashboardDir);
|
|
5920
4512
|
if (!resolvedFile.startsWith(resolvedBase + sep) && resolvedFile !== resolvedBase) {
|
|
5921
4513
|
return json2({ error: "Forbidden" }, 403);
|
|
5922
4514
|
}
|
|
@@ -5924,7 +4516,7 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
5924
4516
|
if (res2)
|
|
5925
4517
|
return res2;
|
|
5926
4518
|
}
|
|
5927
|
-
const indexPath =
|
|
4519
|
+
const indexPath = join4(ctx.dashboardDir, "index.html");
|
|
5928
4520
|
const res = serveStaticFile2(indexPath);
|
|
5929
4521
|
if (res)
|
|
5930
4522
|
return res;
|
|
@@ -5935,21 +4527,21 @@ function handleStaticFiles(path, method, ctx, json2, serveStaticFile2) {
|
|
|
5935
4527
|
function resolveDashboardDir() {
|
|
5936
4528
|
const candidates = [];
|
|
5937
4529
|
try {
|
|
5938
|
-
const scriptDir =
|
|
5939
|
-
candidates.push(
|
|
5940
|
-
candidates.push(
|
|
4530
|
+
const scriptDir = dirname3(fileURLToPath2(import.meta.url));
|
|
4531
|
+
candidates.push(join5(scriptDir, "..", "dashboard", "dist"));
|
|
4532
|
+
candidates.push(join5(scriptDir, "..", "..", "dashboard", "dist"));
|
|
5941
4533
|
} catch {}
|
|
5942
4534
|
if (process.argv[1]) {
|
|
5943
|
-
const mainDir =
|
|
5944
|
-
candidates.push(
|
|
5945
|
-
candidates.push(
|
|
4535
|
+
const mainDir = dirname3(process.argv[1]);
|
|
4536
|
+
candidates.push(join5(mainDir, "..", "dashboard", "dist"));
|
|
4537
|
+
candidates.push(join5(mainDir, "..", "..", "dashboard", "dist"));
|
|
5946
4538
|
}
|
|
5947
|
-
candidates.push(
|
|
4539
|
+
candidates.push(join5(process.cwd(), "dashboard", "dist"));
|
|
5948
4540
|
for (const candidate of candidates) {
|
|
5949
|
-
if (
|
|
4541
|
+
if (existsSync5(candidate))
|
|
5950
4542
|
return candidate;
|
|
5951
4543
|
}
|
|
5952
|
-
return
|
|
4544
|
+
return join5(process.cwd(), "dashboard", "dist");
|
|
5953
4545
|
}
|
|
5954
4546
|
var MIME_TYPES = {
|
|
5955
4547
|
".html": "text/html; charset=utf-8",
|
|
@@ -6021,7 +4613,7 @@ function json(data, status = 200, headers) {
|
|
|
6021
4613
|
});
|
|
6022
4614
|
}
|
|
6023
4615
|
function serveStaticFile(filePath) {
|
|
6024
|
-
if (!
|
|
4616
|
+
if (!existsSync5(filePath))
|
|
6025
4617
|
return null;
|
|
6026
4618
|
const ext = extname(filePath);
|
|
6027
4619
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -6100,7 +4692,7 @@ data: ${data}
|
|
|
6100
4692
|
filteredSseClients.delete(client);
|
|
6101
4693
|
}
|
|
6102
4694
|
const dashboardDir = resolveDashboardDir();
|
|
6103
|
-
const dashboardExists =
|
|
4695
|
+
const dashboardExists = existsSync5(dashboardDir);
|
|
6104
4696
|
if (!dashboardExists) {
|
|
6105
4697
|
console.error(`
|
|
6106
4698
|
Dashboard not found at: ${dashboardDir}`);
|