@sireai/optimus 0.1.1
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/.env.example +16 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/cli/optimus.d.ts +2 -0
- package/dist/cli/optimus.js +2951 -0
- package/dist/cli/optimus.js.map +1 -0
- package/dist/cli/self-update.d.ts +49 -0
- package/dist/cli/self-update.js +264 -0
- package/dist/cli/self-update.js.map +1 -0
- package/dist/config/load-config.d.ts +3 -0
- package/dist/config/load-config.js +321 -0
- package/dist/config/load-config.js.map +1 -0
- package/dist/config/optimus-paths.d.ts +13 -0
- package/dist/config/optimus-paths.js +44 -0
- package/dist/config/optimus-paths.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/jira/jira-cli.d.ts +1 -0
- package/dist/integrations/jira/jira-cli.js +278 -0
- package/dist/integrations/jira/jira-cli.js.map +1 -0
- package/dist/integrations/jira/jira-client.d.ts +99 -0
- package/dist/integrations/jira/jira-client.js +521 -0
- package/dist/integrations/jira/jira-client.js.map +1 -0
- package/dist/integrations/jira/jira-submit.d.ts +71 -0
- package/dist/integrations/jira/jira-submit.js +351 -0
- package/dist/integrations/jira/jira-submit.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.d.ts +23 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.js +136 -0
- package/dist/problem-solving-core/codex/codex-auth-resolver.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.d.ts +6 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.js +81 -0
- package/dist/problem-solving-core/codex/codex-connectivity-checks.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.d.ts +2 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.js +49 -0
- package/dist/problem-solving-core/codex/codex-failure-classifier.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-global-config.d.ts +17 -0
- package/dist/problem-solving-core/codex/codex-global-config.js +100 -0
- package/dist/problem-solving-core/codex/codex-global-config.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-preflight.d.ts +13 -0
- package/dist/problem-solving-core/codex/codex-preflight.js +142 -0
- package/dist/problem-solving-core/codex/codex-preflight.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.d.ts +14 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.js +68 -0
- package/dist/problem-solving-core/codex/codex-provider-profile.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-required-env.d.ts +3 -0
- package/dist/problem-solving-core/codex/codex-required-env.js +21 -0
- package/dist/problem-solving-core/codex/codex-required-env.js.map +1 -0
- package/dist/problem-solving-core/codex/codex-runner.d.ts +37 -0
- package/dist/problem-solving-core/codex/codex-runner.js +926 -0
- package/dist/problem-solving-core/codex/codex-runner.js.map +1 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.d.ts +36 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.js +143 -0
- package/dist/problem-solving-core/codex/evolution-skill-guard.js.map +1 -0
- package/dist/problem-solving-core/codex/repo-memory-service.d.ts +24 -0
- package/dist/problem-solving-core/codex/repo-memory-service.js +114 -0
- package/dist/problem-solving-core/codex/repo-memory-service.js.map +1 -0
- package/dist/problem-solving-core/codex/skill-sync-service.d.ts +35 -0
- package/dist/problem-solving-core/codex/skill-sync-service.js +280 -0
- package/dist/problem-solving-core/codex/skill-sync-service.js.map +1 -0
- package/dist/task-environment/cancellation/task-abort-registry.d.ts +17 -0
- package/dist/task-environment/cancellation/task-abort-registry.js +51 -0
- package/dist/task-environment/cancellation/task-abort-registry.js.map +1 -0
- package/dist/task-environment/cancellation/task-cancellation-service.d.ts +25 -0
- package/dist/task-environment/cancellation/task-cancellation-service.js +54 -0
- package/dist/task-environment/cancellation/task-cancellation-service.js.map +1 -0
- package/dist/task-environment/cancellation/task-cleanup-service.d.ts +22 -0
- package/dist/task-environment/cancellation/task-cleanup-service.js +67 -0
- package/dist/task-environment/cancellation/task-cleanup-service.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.d.ts +13 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js +83 -0
- package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.d.ts +6 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.js +15 -0
- package/dist/task-environment/delivery/commit-message/commit-message-builder.js.map +1 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.d.ts +16 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.js +2 -0
- package/dist/task-environment/delivery/commit-message/commit-message-template-types.js.map +1 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.d.ts +50 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js +454 -0
- package/dist/task-environment/delivery/feishu-analysis-doc-service.js.map +1 -0
- package/dist/task-environment/delivery/feishu-card-renderer.d.ts +38 -0
- package/dist/task-environment/delivery/feishu-card-renderer.js +449 -0
- package/dist/task-environment/delivery/feishu-card-renderer.js.map +1 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.d.ts +34 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js +201 -0
- package/dist/task-environment/delivery/feishu-content/feishu-content-renderer.js.map +1 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.d.ts +27 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js +74 -0
- package/dist/task-environment/delivery/feishu-content/feishu-copy-config.js.map +1 -0
- package/dist/task-environment/delivery/feishu-notifier.d.ts +45 -0
- package/dist/task-environment/delivery/feishu-notifier.js +250 -0
- package/dist/task-environment/delivery/feishu-notifier.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js +39 -0
- package/dist/task-environment/delivery/feishu-templates/analysis-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.js +40 -0
- package/dist/task-environment/delivery/feishu-templates/bugfix-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.js +33 -0
- package/dist/task-environment/delivery/feishu-templates/default-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.d.ts +6 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.js +40 -0
- package/dist/task-environment/delivery/feishu-templates/patch-message-template.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.d.ts +2 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.js +11 -0
- package/dist/task-environment/delivery/feishu-templates/template-registry.js.map +1 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.d.ts +20 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.js +2 -0
- package/dist/task-environment/delivery/feishu-templates/template-types.js.map +1 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.d.ts +14 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.js +109 -0
- package/dist/task-environment/delivery/task-delivery-dispatcher.js.map +1 -0
- package/dist/task-environment/delivery/task-delivery-service.d.ts +33 -0
- package/dist/task-environment/delivery/task-delivery-service.js +432 -0
- package/dist/task-environment/delivery/task-delivery-service.js.map +1 -0
- package/dist/task-environment/delivery/task-publication-service.d.ts +97 -0
- package/dist/task-environment/delivery/task-publication-service.js +1369 -0
- package/dist/task-environment/delivery/task-publication-service.js.map +1 -0
- package/dist/task-environment/execution-addresses.d.ts +40 -0
- package/dist/task-environment/execution-addresses.js +63 -0
- package/dist/task-environment/execution-addresses.js.map +1 -0
- package/dist/task-environment/intake/cli-file-intake.d.ts +12 -0
- package/dist/task-environment/intake/cli-file-intake.js +56 -0
- package/dist/task-environment/intake/cli-file-intake.js.map +1 -0
- package/dist/task-environment/intake/manual-problem-intake.d.ts +3 -0
- package/dist/task-environment/intake/manual-problem-intake.js +57 -0
- package/dist/task-environment/intake/manual-problem-intake.js.map +1 -0
- package/dist/task-environment/intake/polling-problem-intake.d.ts +14 -0
- package/dist/task-environment/intake/polling-problem-intake.js +232 -0
- package/dist/task-environment/intake/polling-problem-intake.js.map +1 -0
- package/dist/task-environment/observability/logger.d.ts +76 -0
- package/dist/task-environment/observability/logger.js +604 -0
- package/dist/task-environment/observability/logger.js.map +1 -0
- package/dist/task-environment/observability/runtime-panel.d.ts +82 -0
- package/dist/task-environment/observability/runtime-panel.js +1008 -0
- package/dist/task-environment/observability/runtime-panel.js.map +1 -0
- package/dist/task-environment/observability/sound-notifier.d.ts +18 -0
- package/dist/task-environment/observability/sound-notifier.js +71 -0
- package/dist/task-environment/observability/sound-notifier.js.map +1 -0
- package/dist/task-environment/orchestration/execution-context-assembler.d.ts +41 -0
- package/dist/task-environment/orchestration/execution-context-assembler.js +464 -0
- package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -0
- package/dist/task-environment/orchestration/git-change-classifier.d.ts +19 -0
- package/dist/task-environment/orchestration/git-change-classifier.js +106 -0
- package/dist/task-environment/orchestration/git-change-classifier.js.map +1 -0
- package/dist/task-environment/orchestration/harness-registry.d.ts +27 -0
- package/dist/task-environment/orchestration/harness-registry.js +116 -0
- package/dist/task-environment/orchestration/harness-registry.js.map +1 -0
- package/dist/task-environment/orchestration/harness-resolver.d.ts +8 -0
- package/dist/task-environment/orchestration/harness-resolver.js +39 -0
- package/dist/task-environment/orchestration/harness-resolver.js.map +1 -0
- package/dist/task-environment/orchestration/task-orchestrator.d.ts +45 -0
- package/dist/task-environment/orchestration/task-orchestrator.js +1122 -0
- package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -0
- package/dist/task-environment/orchestration/task-package-assembler.d.ts +4 -0
- package/dist/task-environment/orchestration/task-package-assembler.js +10 -0
- package/dist/task-environment/orchestration/task-package-assembler.js.map +1 -0
- package/dist/task-environment/orchestration/triage-agent.d.ts +54 -0
- package/dist/task-environment/orchestration/triage-agent.js +636 -0
- package/dist/task-environment/orchestration/triage-agent.js.map +1 -0
- package/dist/task-environment/orchestration/triage-runner.d.ts +65 -0
- package/dist/task-environment/orchestration/triage-runner.js +655 -0
- package/dist/task-environment/orchestration/triage-runner.js.map +1 -0
- package/dist/task-environment/publication-target.d.ts +12 -0
- package/dist/task-environment/publication-target.js +174 -0
- package/dist/task-environment/publication-target.js.map +1 -0
- package/dist/task-environment/runtime/blocking-event-queue.d.ts +7 -0
- package/dist/task-environment/runtime/blocking-event-queue.js +27 -0
- package/dist/task-environment/runtime/blocking-event-queue.js.map +1 -0
- package/dist/task-environment/runtime/optimus-runtime.d.ts +69 -0
- package/dist/task-environment/runtime/optimus-runtime.js +751 -0
- package/dist/task-environment/runtime/optimus-runtime.js.map +1 -0
- package/dist/task-environment/storage/sqlite-event-store.d.ts +52 -0
- package/dist/task-environment/storage/sqlite-event-store.js +288 -0
- package/dist/task-environment/storage/sqlite-event-store.js.map +1 -0
- package/dist/task-environment/storage/sqlite-task-store.d.ts +122 -0
- package/dist/task-environment/storage/sqlite-task-store.js +1182 -0
- package/dist/task-environment/storage/sqlite-task-store.js.map +1 -0
- package/dist/types.d.ts +629 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/embedded-skills/shared/repo-inspection/SKILL.md +9 -0
- package/embedded-skills/shared/repo-inspection/skill.json +5 -0
- package/embedded-skills/task/bugfix/android-debug-protocol/SKILL.md +10 -0
- package/embedded-skills/task/bugfix/android-debug-protocol/skill.json +6 -0
- package/harness/AGENTS.md +30 -0
- package/harness/CHECKLIST.md +44 -0
- package/harness/CONSTRAINTS.md +60 -0
- package/harness/FRAMEWORK.md +28 -0
- package/harness/GOAL.md +28 -0
- package/harness/HANDOFF.md +45 -0
- package/harness/TASK_PLAN.md +79 -0
- package/optimus.config.template.json +34 -0
- package/package.json +109 -0
- package/task-harnesses/bugfix/ACCEPT.md +47 -0
- package/task-harnesses/bugfix/CONSTRAINTS.md +46 -0
- package/task-harnesses/bugfix/CONTEXT.md +29 -0
- package/task-harnesses/bugfix/EVOLUTION.md +82 -0
- package/task-harnesses/bugfix/ROLE.md +29 -0
- package/task-harnesses/bugfix/STANDARD.md +250 -0
- package/task-harnesses/bugfix/manifest.json +13 -0
- package/task-harnesses/registry.json +8 -0
|
@@ -0,0 +1,1182 @@
|
|
|
1
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
const MANAGED_TASK_TABLES = ["runtime_external_refs", "repository_roots", "observation_hints", "task_publication_attempts", "task_delivery_attempts", "task_delivery_bundles", "task_artifacts", "task_events", "task_runs", "tasks", "task_packages"];
|
|
6
|
+
export class SQLiteTaskStore {
|
|
7
|
+
rootDir;
|
|
8
|
+
dbPath;
|
|
9
|
+
db;
|
|
10
|
+
constructor(rootDir) {
|
|
11
|
+
this.rootDir = rootDir;
|
|
12
|
+
this.dbPath = join(rootDir, "optimus.db");
|
|
13
|
+
}
|
|
14
|
+
// SQLite is the source of truth for packaged tasks, orchestrated tasks, runs, and artifacts.
|
|
15
|
+
async init() {
|
|
16
|
+
await mkdir(this.rootDir, { recursive: true });
|
|
17
|
+
await rm(join(this.rootDir, "store.json"), { force: true });
|
|
18
|
+
await rm(join(this.rootDir, "events.json"), { force: true });
|
|
19
|
+
const db = this.getDb();
|
|
20
|
+
db.pragma("journal_mode = WAL");
|
|
21
|
+
db.pragma("foreign_keys = ON");
|
|
22
|
+
if (this.shouldResetLegacySchema(db)) {
|
|
23
|
+
this.resetManagedTaskSchema(db);
|
|
24
|
+
}
|
|
25
|
+
this.migrateTaskRunAddressColumns(db);
|
|
26
|
+
db.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS task_packages (
|
|
28
|
+
task_package_id TEXT PRIMARY KEY,
|
|
29
|
+
source_event_id TEXT NOT NULL,
|
|
30
|
+
source TEXT NOT NULL,
|
|
31
|
+
source_ref TEXT,
|
|
32
|
+
idempotency_key TEXT,
|
|
33
|
+
task_type TEXT NOT NULL,
|
|
34
|
+
title TEXT NOT NULL,
|
|
35
|
+
repo TEXT,
|
|
36
|
+
branch TEXT,
|
|
37
|
+
input_json TEXT NOT NULL,
|
|
38
|
+
created_at TEXT NOT NULL
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
42
|
+
task_id TEXT PRIMARY KEY,
|
|
43
|
+
task_package_id TEXT NOT NULL UNIQUE,
|
|
44
|
+
source_event_id TEXT NOT NULL,
|
|
45
|
+
task_type TEXT NOT NULL,
|
|
46
|
+
title TEXT NOT NULL,
|
|
47
|
+
status TEXT NOT NULL,
|
|
48
|
+
risk_level TEXT NOT NULL,
|
|
49
|
+
created_at TEXT NOT NULL,
|
|
50
|
+
updated_at TEXT NOT NULL,
|
|
51
|
+
active_run_id TEXT,
|
|
52
|
+
run_count INTEGER NOT NULL DEFAULT 0,
|
|
53
|
+
FOREIGN KEY(task_package_id) REFERENCES task_packages(task_package_id) ON DELETE CASCADE
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE IF NOT EXISTS task_runs (
|
|
57
|
+
run_id TEXT PRIMARY KEY,
|
|
58
|
+
task_id TEXT NOT NULL,
|
|
59
|
+
task_package_id TEXT NOT NULL,
|
|
60
|
+
source_event_id TEXT,
|
|
61
|
+
status TEXT NOT NULL,
|
|
62
|
+
task_status TEXT NOT NULL,
|
|
63
|
+
health TEXT,
|
|
64
|
+
started_at TEXT NOT NULL,
|
|
65
|
+
ended_at TEXT,
|
|
66
|
+
summary TEXT,
|
|
67
|
+
failure_category TEXT,
|
|
68
|
+
addresses_workspace_dir TEXT,
|
|
69
|
+
addresses_visible_repo_dir TEXT,
|
|
70
|
+
addresses_configured_execution_mode TEXT,
|
|
71
|
+
addresses_resolved_execution_mode TEXT,
|
|
72
|
+
sandbox_mode TEXT,
|
|
73
|
+
approval_policy TEXT,
|
|
74
|
+
sdk_thread_id TEXT,
|
|
75
|
+
workspace_skill_mount_dir TEXT,
|
|
76
|
+
mounted_skills_json TEXT,
|
|
77
|
+
last_event_at TEXT,
|
|
78
|
+
last_event_type TEXT,
|
|
79
|
+
heartbeat_at TEXT,
|
|
80
|
+
progress_counter INTEGER DEFAULT 0,
|
|
81
|
+
command_count INTEGER DEFAULT 0,
|
|
82
|
+
file_change_count INTEGER DEFAULT 0,
|
|
83
|
+
last_agent_message_preview TEXT,
|
|
84
|
+
cancel_requested_at TEXT,
|
|
85
|
+
cancel_request_source_event_id TEXT,
|
|
86
|
+
FOREIGN KEY(task_id) REFERENCES tasks(task_id) ON DELETE CASCADE,
|
|
87
|
+
FOREIGN KEY(task_package_id) REFERENCES task_packages(task_package_id) ON DELETE CASCADE
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE IF NOT EXISTS task_events (
|
|
91
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
92
|
+
task_id TEXT NOT NULL,
|
|
93
|
+
type TEXT NOT NULL,
|
|
94
|
+
detail TEXT NOT NULL,
|
|
95
|
+
created_at TEXT NOT NULL,
|
|
96
|
+
run_id TEXT,
|
|
97
|
+
source_event_id TEXT,
|
|
98
|
+
FOREIGN KEY(task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
CREATE TABLE IF NOT EXISTS task_artifacts (
|
|
102
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
103
|
+
task_id TEXT NOT NULL,
|
|
104
|
+
kind TEXT NOT NULL,
|
|
105
|
+
content TEXT NOT NULL,
|
|
106
|
+
created_at TEXT NOT NULL,
|
|
107
|
+
FOREIGN KEY(task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE TABLE IF NOT EXISTS task_delivery_bundles (
|
|
111
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
112
|
+
task_id TEXT NOT NULL,
|
|
113
|
+
run_id TEXT,
|
|
114
|
+
task_type TEXT NOT NULL,
|
|
115
|
+
outcome TEXT NOT NULL,
|
|
116
|
+
summary_json TEXT NOT NULL,
|
|
117
|
+
warnings_json TEXT,
|
|
118
|
+
artifacts_json TEXT NOT NULL,
|
|
119
|
+
publication_json TEXT,
|
|
120
|
+
created_at TEXT NOT NULL,
|
|
121
|
+
FOREIGN KEY(task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
CREATE TABLE IF NOT EXISTS task_delivery_attempts (
|
|
125
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
126
|
+
task_id TEXT NOT NULL,
|
|
127
|
+
run_id TEXT,
|
|
128
|
+
channel TEXT NOT NULL,
|
|
129
|
+
status TEXT NOT NULL,
|
|
130
|
+
summary TEXT NOT NULL,
|
|
131
|
+
created_at TEXT NOT NULL,
|
|
132
|
+
delivered_at TEXT,
|
|
133
|
+
error TEXT,
|
|
134
|
+
FOREIGN KEY(task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
CREATE TABLE IF NOT EXISTS task_publication_attempts (
|
|
138
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
139
|
+
task_id TEXT NOT NULL,
|
|
140
|
+
run_id TEXT,
|
|
141
|
+
mode TEXT NOT NULL,
|
|
142
|
+
status TEXT NOT NULL,
|
|
143
|
+
summary TEXT NOT NULL,
|
|
144
|
+
created_at TEXT NOT NULL,
|
|
145
|
+
artifact_path TEXT,
|
|
146
|
+
topic TEXT,
|
|
147
|
+
topic_url TEXT,
|
|
148
|
+
review_url TEXT,
|
|
149
|
+
review_urls_json TEXT,
|
|
150
|
+
change_id TEXT,
|
|
151
|
+
change_ids_json TEXT,
|
|
152
|
+
error TEXT,
|
|
153
|
+
FOREIGN KEY(task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
CREATE TABLE IF NOT EXISTS repository_roots (
|
|
157
|
+
path TEXT PRIMARY KEY,
|
|
158
|
+
alias TEXT UNIQUE,
|
|
159
|
+
execution_mode TEXT NOT NULL DEFAULT "copy",
|
|
160
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
161
|
+
created_at TEXT NOT NULL,
|
|
162
|
+
updated_at TEXT NOT NULL
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
CREATE TABLE IF NOT EXISTS runtime_external_refs (
|
|
166
|
+
ref_key TEXT PRIMARY KEY,
|
|
167
|
+
ref_value_json TEXT NOT NULL,
|
|
168
|
+
updated_at TEXT NOT NULL
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
CREATE TABLE IF NOT EXISTS observation_hints (
|
|
172
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
173
|
+
task_id TEXT NOT NULL,
|
|
174
|
+
area TEXT NOT NULL,
|
|
175
|
+
signal TEXT NOT NULL,
|
|
176
|
+
reason TEXT NOT NULL,
|
|
177
|
+
suggested_action TEXT NOT NULL,
|
|
178
|
+
FOREIGN KEY(task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
CREATE INDEX IF NOT EXISTS idx_repository_roots_alias ON repository_roots(alias);
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_task_packages_idempotency_key ON task_packages(idempotency_key);
|
|
184
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_active_run_id ON tasks(active_run_id);
|
|
185
|
+
CREATE INDEX IF NOT EXISTS idx_task_runs_task_id ON task_runs(task_id, started_at);
|
|
186
|
+
CREATE INDEX IF NOT EXISTS idx_task_events_task_id ON task_events(task_id, created_at);
|
|
187
|
+
CREATE INDEX IF NOT EXISTS idx_task_delivery_bundles_task_id ON task_delivery_bundles(task_id, created_at);
|
|
188
|
+
CREATE INDEX IF NOT EXISTS idx_task_delivery_attempts_task_id ON task_delivery_attempts(task_id, created_at);
|
|
189
|
+
CREATE INDEX IF NOT EXISTS idx_task_publication_attempts_task_id ON task_publication_attempts(task_id, created_at);
|
|
190
|
+
`);
|
|
191
|
+
this.migrateTaskPublicationAttemptColumns(db);
|
|
192
|
+
}
|
|
193
|
+
// Repository roots are runtime-managed candidate code repositories used later for repo discovery and worktree creation.
|
|
194
|
+
async listRepositoryRoots() {
|
|
195
|
+
const rows = this.getDb().prepare(`SELECT * FROM repository_roots ORDER BY created_at ASC`).all();
|
|
196
|
+
return rows.map((row) => this.mapRepositoryRootRow(row));
|
|
197
|
+
}
|
|
198
|
+
async addRepositoryRoot(input) {
|
|
199
|
+
const now = new Date().toISOString();
|
|
200
|
+
this.getDb().prepare(`
|
|
201
|
+
INSERT INTO repository_roots (path, alias, execution_mode, enabled, created_at, updated_at)
|
|
202
|
+
VALUES (@path, @alias, @execution_mode, 1, @created_at, @updated_at)
|
|
203
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
204
|
+
alias = excluded.alias,
|
|
205
|
+
execution_mode = excluded.execution_mode,
|
|
206
|
+
enabled = 1,
|
|
207
|
+
updated_at = excluded.updated_at
|
|
208
|
+
`).run({
|
|
209
|
+
path: input.path,
|
|
210
|
+
alias: input.alias ?? null,
|
|
211
|
+
execution_mode: input.executionMode ?? "copy",
|
|
212
|
+
created_at: now,
|
|
213
|
+
updated_at: now
|
|
214
|
+
});
|
|
215
|
+
const row = this.getDb().prepare(`SELECT * FROM repository_roots WHERE path = ?`).get(input.path);
|
|
216
|
+
return this.mapRepositoryRootRow(row);
|
|
217
|
+
}
|
|
218
|
+
async getRuntimeExternalRef(key) {
|
|
219
|
+
const row = this.getDb().prepare(`
|
|
220
|
+
SELECT ref_key, ref_value_json, updated_at
|
|
221
|
+
FROM runtime_external_refs
|
|
222
|
+
WHERE ref_key = ?
|
|
223
|
+
`).get(key);
|
|
224
|
+
if (!row) {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
return JSON.parse(row.ref_value_json);
|
|
228
|
+
}
|
|
229
|
+
async setRuntimeExternalRef(key, value) {
|
|
230
|
+
const now = new Date().toISOString();
|
|
231
|
+
this.getDb().prepare(`
|
|
232
|
+
INSERT INTO runtime_external_refs (ref_key, ref_value_json, updated_at)
|
|
233
|
+
VALUES (?, ?, ?)
|
|
234
|
+
ON CONFLICT(ref_key) DO UPDATE SET
|
|
235
|
+
ref_value_json = excluded.ref_value_json,
|
|
236
|
+
updated_at = excluded.updated_at
|
|
237
|
+
`).run(key, JSON.stringify(value), now);
|
|
238
|
+
}
|
|
239
|
+
async deleteRuntimeExternalRef(key) {
|
|
240
|
+
this.getDb().prepare(`DELETE FROM runtime_external_refs WHERE ref_key = ?`).run(key);
|
|
241
|
+
}
|
|
242
|
+
async removeRepositoryRoot(pathOrAlias) {
|
|
243
|
+
const row = this.getDb().prepare(`SELECT * FROM repository_roots WHERE path = ? OR alias = ? LIMIT 1`).get(pathOrAlias, pathOrAlias);
|
|
244
|
+
if (!row) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
this.getDb().prepare(`DELETE FROM repository_roots WHERE path = ?`).run(row.path);
|
|
248
|
+
return this.mapRepositoryRootRow(row);
|
|
249
|
+
}
|
|
250
|
+
// Repository updates keep the same identity row and only mutate the explicitly requested fields.
|
|
251
|
+
// This lets CLI callers change mode or alias without forcing a remove+add cycle.
|
|
252
|
+
async updateRepositoryRoot(pathOrAlias, input) {
|
|
253
|
+
const row = this.getDb().prepare(`SELECT * FROM repository_roots WHERE path = ? OR alias = ? LIMIT 1`).get(pathOrAlias, pathOrAlias);
|
|
254
|
+
if (!row) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
const nextAlias = input.alias !== undefined ? (input.alias || null) : row.alias;
|
|
258
|
+
const nextExecutionMode = input.executionMode ?? row.execution_mode ?? "copy";
|
|
259
|
+
const updatedAt = new Date().toISOString();
|
|
260
|
+
this.getDb().prepare(`
|
|
261
|
+
UPDATE repository_roots
|
|
262
|
+
SET alias = ?, execution_mode = ?, updated_at = ?, enabled = 1
|
|
263
|
+
WHERE path = ?
|
|
264
|
+
`).run(nextAlias, nextExecutionMode, updatedAt, row.path);
|
|
265
|
+
const updatedRow = this.getDb().prepare(`SELECT * FROM repository_roots WHERE path = ?`).get(row.path);
|
|
266
|
+
return this.mapRepositoryRootRow(updatedRow);
|
|
267
|
+
}
|
|
268
|
+
async createTaskFromPackage(taskPackage, options = {}) {
|
|
269
|
+
const now = new Date().toISOString();
|
|
270
|
+
const task = {
|
|
271
|
+
taskId: `task-${Date.now()}-${randomUUID().slice(0, 8)}`,
|
|
272
|
+
taskPackageId: taskPackage.taskPackageId,
|
|
273
|
+
sourceEventId: taskPackage.sourceEventId,
|
|
274
|
+
taskType: taskPackage.taskType,
|
|
275
|
+
title: taskPackage.title,
|
|
276
|
+
status: "queued",
|
|
277
|
+
riskLevel: "low",
|
|
278
|
+
createdAt: now,
|
|
279
|
+
updatedAt: now,
|
|
280
|
+
runCount: 0
|
|
281
|
+
};
|
|
282
|
+
const db = this.getDb();
|
|
283
|
+
const tx = db.transaction(() => {
|
|
284
|
+
db.prepare(`
|
|
285
|
+
INSERT INTO task_packages (
|
|
286
|
+
task_package_id, source_event_id, source, source_ref, idempotency_key, task_type, title,
|
|
287
|
+
repo, branch, input_json, created_at
|
|
288
|
+
) VALUES (
|
|
289
|
+
@task_package_id, @source_event_id, @source, @source_ref, @idempotency_key, @task_type, @title,
|
|
290
|
+
@repo, @branch, @input_json, @created_at
|
|
291
|
+
)
|
|
292
|
+
`).run(this.toTaskPackageParams(taskPackage));
|
|
293
|
+
db.prepare(`
|
|
294
|
+
INSERT INTO tasks (
|
|
295
|
+
task_id, task_package_id, source_event_id, task_type, title,
|
|
296
|
+
status, risk_level, created_at, updated_at, active_run_id, run_count
|
|
297
|
+
) VALUES (
|
|
298
|
+
@task_id, @task_package_id, @source_event_id, @task_type, @title,
|
|
299
|
+
@status, @risk_level, @created_at, @updated_at, @active_run_id, @run_count
|
|
300
|
+
)
|
|
301
|
+
`).run(this.toTaskParams(task));
|
|
302
|
+
db.prepare(`
|
|
303
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
304
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
305
|
+
`).run(task.taskId, "task.created", `Task package accepted for ${taskPackage.taskType}.`, now, null, options.sourceEventId ?? taskPackage.sourceEventId);
|
|
306
|
+
});
|
|
307
|
+
tx();
|
|
308
|
+
return task;
|
|
309
|
+
}
|
|
310
|
+
async listTasks() {
|
|
311
|
+
const rows = this.getDb().prepare(`SELECT * FROM tasks ORDER BY created_at ASC`).all();
|
|
312
|
+
return rows.map((row) => this.mapTaskRow(row));
|
|
313
|
+
}
|
|
314
|
+
async listTaskPackages() {
|
|
315
|
+
const rows = this.getDb().prepare(`SELECT * FROM task_packages ORDER BY created_at ASC`).all();
|
|
316
|
+
return rows.map((row) => this.mapTaskPackageRow(row));
|
|
317
|
+
}
|
|
318
|
+
async getTaskPackage(taskPackageId) {
|
|
319
|
+
const row = this.getDb().prepare(`SELECT * FROM task_packages WHERE task_package_id = ?`).get(taskPackageId);
|
|
320
|
+
return row ? this.mapTaskPackageRow(row) : undefined;
|
|
321
|
+
}
|
|
322
|
+
async findTaskBySourceEventId(sourceEventId) {
|
|
323
|
+
const row = this.getDb().prepare(`SELECT * FROM tasks WHERE source_event_id = ?`).get(sourceEventId);
|
|
324
|
+
return row ? this.mapTaskRow(row) : undefined;
|
|
325
|
+
}
|
|
326
|
+
async findTaskByIdempotencyKey(idempotencyKey) {
|
|
327
|
+
const row = this.getDb().prepare(`
|
|
328
|
+
SELECT tasks.*
|
|
329
|
+
FROM tasks
|
|
330
|
+
INNER JOIN task_packages ON task_packages.task_package_id = tasks.task_package_id
|
|
331
|
+
WHERE task_packages.idempotency_key = ?
|
|
332
|
+
ORDER BY tasks.created_at DESC
|
|
333
|
+
LIMIT 1
|
|
334
|
+
`).get(idempotencyKey);
|
|
335
|
+
return row ? this.mapTaskRow(row) : undefined;
|
|
336
|
+
}
|
|
337
|
+
async listTaskRuns() {
|
|
338
|
+
const rows = this.getDb().prepare(`SELECT * FROM task_runs ORDER BY started_at ASC`).all();
|
|
339
|
+
return rows.map((row) => this.mapRunRow(row));
|
|
340
|
+
}
|
|
341
|
+
// Doctor and operator tooling read recent task events from SQLite instead of scraping runtime logs.
|
|
342
|
+
// This keeps task-state diagnostics available even after process restarts or log rotation.
|
|
343
|
+
async listRecentTaskEvents(limit = 20, types) {
|
|
344
|
+
const normalizedLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 20;
|
|
345
|
+
const params = [];
|
|
346
|
+
let sql = `SELECT task_id, type, detail, created_at, run_id, source_event_id FROM task_events`;
|
|
347
|
+
if (types && types.length > 0) {
|
|
348
|
+
sql += ` WHERE type IN (${types.map(() => "?").join(",")})`;
|
|
349
|
+
params.push(...types);
|
|
350
|
+
}
|
|
351
|
+
sql += ` ORDER BY created_at DESC, id DESC LIMIT ?`;
|
|
352
|
+
params.push(normalizedLimit);
|
|
353
|
+
const rows = this.getDb().prepare(sql).all(...params);
|
|
354
|
+
return rows.map((row) => this.mapTaskEventRow(row));
|
|
355
|
+
}
|
|
356
|
+
// Result inspection reads persisted artifacts and observation hints directly from SQLite so CLI views do not need raw SQL.
|
|
357
|
+
async listTaskArtifacts(taskId) {
|
|
358
|
+
const rows = this.getDb().prepare(`SELECT task_id, kind, content, created_at FROM task_artifacts WHERE task_id = ? ORDER BY id ASC`).all(taskId);
|
|
359
|
+
return rows.map((row) => {
|
|
360
|
+
if (row.kind && row.kind !== "attachment") {
|
|
361
|
+
return {
|
|
362
|
+
kind: row.kind,
|
|
363
|
+
path: row.content,
|
|
364
|
+
createdAt: row.created_at
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
path: row.content,
|
|
369
|
+
createdAt: row.created_at
|
|
370
|
+
};
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
async getLatestTaskDeliveryBundle(taskId) {
|
|
374
|
+
const row = this.getDb().prepare(`SELECT * FROM task_delivery_bundles WHERE task_id = ? ORDER BY id DESC LIMIT 1`).get(taskId);
|
|
375
|
+
if (!row) {
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
taskId: row.task_id,
|
|
380
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
381
|
+
taskType: row.task_type,
|
|
382
|
+
outcome: row.outcome,
|
|
383
|
+
createdAt: row.created_at,
|
|
384
|
+
summary: JSON.parse(row.summary_json),
|
|
385
|
+
...(row.warnings_json ? { warnings: JSON.parse(row.warnings_json) } : {}),
|
|
386
|
+
artifacts: JSON.parse(row.artifacts_json),
|
|
387
|
+
...(row.publication_json ? { publication: JSON.parse(row.publication_json) } : {})
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async listTaskDeliveryAttempts(taskId) {
|
|
391
|
+
const rows = this.getDb().prepare(`SELECT task_id, run_id, channel, status, summary, created_at, delivered_at, error FROM task_delivery_attempts WHERE task_id = ? ORDER BY id ASC`).all(taskId);
|
|
392
|
+
return rows.map((row) => ({
|
|
393
|
+
taskId: row.task_id,
|
|
394
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
395
|
+
channel: row.channel,
|
|
396
|
+
status: row.status,
|
|
397
|
+
summary: row.summary,
|
|
398
|
+
createdAt: row.created_at,
|
|
399
|
+
...(row.delivered_at ? { deliveredAt: row.delivered_at } : {}),
|
|
400
|
+
...(row.error ? { error: row.error } : {})
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
async listRetryableDeliveryAttempts(limit) {
|
|
404
|
+
const rows = this.getDb().prepare(`
|
|
405
|
+
SELECT latest.task_id, latest.run_id, latest.channel, latest.status, latest.summary, latest.created_at, latest.delivered_at, latest.error
|
|
406
|
+
FROM task_delivery_attempts latest
|
|
407
|
+
INNER JOIN (
|
|
408
|
+
SELECT task_id, channel, MAX(id) AS max_id
|
|
409
|
+
FROM task_delivery_attempts
|
|
410
|
+
GROUP BY task_id, channel
|
|
411
|
+
) grouped
|
|
412
|
+
ON grouped.max_id = latest.id
|
|
413
|
+
WHERE latest.status = 'failed'
|
|
414
|
+
ORDER BY latest.created_at ASC
|
|
415
|
+
LIMIT ?
|
|
416
|
+
`).all(limit);
|
|
417
|
+
return rows.map((row) => ({
|
|
418
|
+
taskId: row.task_id,
|
|
419
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
420
|
+
channel: row.channel,
|
|
421
|
+
status: row.status,
|
|
422
|
+
summary: row.summary,
|
|
423
|
+
createdAt: row.created_at,
|
|
424
|
+
...(row.delivered_at ? { deliveredAt: row.delivered_at } : {}),
|
|
425
|
+
...(row.error ? { error: row.error } : {})
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
async listTaskPublicationAttempts(taskId) {
|
|
429
|
+
const rows = this.getDb().prepare(`SELECT task_id, run_id, mode, status, summary, created_at, artifact_path, topic, topic_url, review_url, review_urls_json, change_id, change_ids_json, error FROM task_publication_attempts WHERE task_id = ? ORDER BY id ASC`).all(taskId);
|
|
430
|
+
return rows.map((row) => ({
|
|
431
|
+
taskId: row.task_id,
|
|
432
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
433
|
+
mode: row.mode,
|
|
434
|
+
status: row.status,
|
|
435
|
+
summary: row.summary,
|
|
436
|
+
createdAt: row.created_at,
|
|
437
|
+
...(row.artifact_path ? { artifactPath: row.artifact_path } : {}),
|
|
438
|
+
...(row.topic ? { topic: row.topic } : {}),
|
|
439
|
+
...(row.topic_url ? { topicUrl: row.topic_url } : {}),
|
|
440
|
+
...(row.review_url ? { reviewUrl: row.review_url } : {}),
|
|
441
|
+
...(row.review_urls_json ? { reviewUrls: JSON.parse(row.review_urls_json) } : {}),
|
|
442
|
+
...(row.change_id ? { changeId: row.change_id } : {}),
|
|
443
|
+
...(row.change_ids_json ? { changeIds: JSON.parse(row.change_ids_json) } : {}),
|
|
444
|
+
...(row.error ? { error: row.error } : {})
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
async listObservationHints(taskId) {
|
|
448
|
+
const rows = this.getDb().prepare(`SELECT task_id, area, signal, reason, suggested_action FROM observation_hints WHERE task_id = ? ORDER BY id ASC`).all(taskId);
|
|
449
|
+
return rows.map((row) => ({
|
|
450
|
+
area: row.area,
|
|
451
|
+
signal: row.signal,
|
|
452
|
+
reason: row.reason,
|
|
453
|
+
suggestedAction: row.suggested_action
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
async getTask(taskId) {
|
|
457
|
+
const row = this.getDb().prepare(`SELECT * FROM tasks WHERE task_id = ?`).get(taskId);
|
|
458
|
+
return row ? this.mapTaskRow(row) : undefined;
|
|
459
|
+
}
|
|
460
|
+
async retryTask(taskId, sourceEventId) {
|
|
461
|
+
const db = this.getDb();
|
|
462
|
+
const task = this.requireTask(taskId, false);
|
|
463
|
+
if (!task || task.activeRunId) {
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
if (!["failed", "needs_human", "completed"].includes(task.status)) {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
const now = new Date().toISOString();
|
|
470
|
+
task.status = "queued";
|
|
471
|
+
task.updatedAt = now;
|
|
472
|
+
const tx = db.transaction(() => {
|
|
473
|
+
this.writeTask(task);
|
|
474
|
+
db.prepare(`
|
|
475
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
476
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
477
|
+
`).run(taskId, "task.retry_requested", "Task was re-queued for another execution attempt.", now, null, sourceEventId ?? task.sourceEventId ?? null);
|
|
478
|
+
});
|
|
479
|
+
tx();
|
|
480
|
+
return this.getTask(taskId);
|
|
481
|
+
}
|
|
482
|
+
async cancelTask(taskId, sourceEventId) {
|
|
483
|
+
const db = this.getDb();
|
|
484
|
+
const task = this.requireTask(taskId, false);
|
|
485
|
+
if (!task) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
const now = new Date().toISOString();
|
|
489
|
+
const activeRun = task.activeRunId ? this.getRun(taskId, task.activeRunId) : undefined;
|
|
490
|
+
if (task.activeRunId && activeRun && !activeRun.endedAt) {
|
|
491
|
+
activeRun.status = "cancel_requested";
|
|
492
|
+
activeRun.health = "cancel_requested";
|
|
493
|
+
activeRun.lastEventAt = now;
|
|
494
|
+
activeRun.lastEventType = "task.cancel_requested";
|
|
495
|
+
activeRun.heartbeatAt = now;
|
|
496
|
+
activeRun.cancelRequestedAt = now;
|
|
497
|
+
if (sourceEventId) {
|
|
498
|
+
activeRun.cancelRequestSourceEventId = sourceEventId;
|
|
499
|
+
}
|
|
500
|
+
const tx = db.transaction(() => {
|
|
501
|
+
this.writeRun(activeRun);
|
|
502
|
+
db.prepare(`
|
|
503
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
504
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
505
|
+
`).run(taskId, "task.cancel_requested", "Cancellation requested for running task; runtime will interrupt the active run.", now, task.activeRunId ?? null, sourceEventId ?? null);
|
|
506
|
+
});
|
|
507
|
+
tx();
|
|
508
|
+
return this.getTask(taskId);
|
|
509
|
+
}
|
|
510
|
+
if (!["queued", "failed", "needs_human"].includes(task.status)) {
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
task.status = "canceled";
|
|
514
|
+
task.updatedAt = now;
|
|
515
|
+
const tx = db.transaction(() => {
|
|
516
|
+
this.writeTask(task);
|
|
517
|
+
db.prepare(`
|
|
518
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
519
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
520
|
+
`).run(taskId, "task.cancel_requested", "Task was canceled before a new execution started.", now, null, sourceEventId ?? null);
|
|
521
|
+
});
|
|
522
|
+
tx();
|
|
523
|
+
return this.getTask(taskId);
|
|
524
|
+
}
|
|
525
|
+
async listRunnableTasks(options) {
|
|
526
|
+
const excluded = options.excludeTaskIds ?? [];
|
|
527
|
+
const params = ["queued"];
|
|
528
|
+
let sql = `SELECT * FROM tasks WHERE status = ? AND active_run_id IS NULL`;
|
|
529
|
+
if (excluded.length > 0) {
|
|
530
|
+
sql += ` AND task_id NOT IN (${excluded.map(() => "?").join(",")})`;
|
|
531
|
+
params.push(...excluded);
|
|
532
|
+
}
|
|
533
|
+
sql += ` ORDER BY created_at ASC LIMIT ?`;
|
|
534
|
+
params.push(options.limit);
|
|
535
|
+
const rows = this.getDb().prepare(sql).all(...params);
|
|
536
|
+
return rows.map((row) => this.mapTaskRow(row));
|
|
537
|
+
}
|
|
538
|
+
async claimTask(taskId) {
|
|
539
|
+
const db = this.getDb();
|
|
540
|
+
const task = this.requireTask(taskId, false);
|
|
541
|
+
if (!task || task.status !== "queued" || task.activeRunId) {
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
const taskPackage = this.requireTaskPackage(task.taskPackageId);
|
|
545
|
+
const now = new Date().toISOString();
|
|
546
|
+
const runId = `run-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
547
|
+
task.status = "preparing";
|
|
548
|
+
task.activeRunId = runId;
|
|
549
|
+
task.runCount = (task.runCount ?? 0) + 1;
|
|
550
|
+
task.updatedAt = now;
|
|
551
|
+
const taskRun = {
|
|
552
|
+
runId,
|
|
553
|
+
taskId,
|
|
554
|
+
taskPackageId: task.taskPackageId,
|
|
555
|
+
status: "leased",
|
|
556
|
+
taskStatus: "preparing",
|
|
557
|
+
health: "waiting_start",
|
|
558
|
+
startedAt: now,
|
|
559
|
+
lastEventAt: now,
|
|
560
|
+
lastEventType: "task.claimed",
|
|
561
|
+
heartbeatAt: now,
|
|
562
|
+
progressCounter: 0,
|
|
563
|
+
commandCount: 0,
|
|
564
|
+
fileChangeCount: 0,
|
|
565
|
+
...(task.sourceEventId ? { sourceEventId: task.sourceEventId } : {})
|
|
566
|
+
};
|
|
567
|
+
const tx = db.transaction(() => {
|
|
568
|
+
this.writeTask(task);
|
|
569
|
+
db.prepare(`
|
|
570
|
+
INSERT INTO task_runs (
|
|
571
|
+
run_id, task_id, task_package_id, source_event_id, status, task_status, health,
|
|
572
|
+
started_at, ended_at, summary, failure_category, addresses_workspace_dir, addresses_visible_repo_dir,
|
|
573
|
+
addresses_configured_execution_mode, addresses_resolved_execution_mode, sandbox_mode,
|
|
574
|
+
approval_policy, sdk_thread_id, workspace_skill_mount_dir, mounted_skills_json, last_event_at, last_event_type, heartbeat_at,
|
|
575
|
+
progress_counter, command_count, file_change_count, last_agent_message_preview,
|
|
576
|
+
cancel_requested_at, cancel_request_source_event_id
|
|
577
|
+
) VALUES (
|
|
578
|
+
@run_id, @task_id, @task_package_id, @source_event_id, @status, @task_status, @health,
|
|
579
|
+
@started_at, @ended_at, @summary, @failure_category, @addresses_workspace_dir, @addresses_visible_repo_dir,
|
|
580
|
+
@addresses_configured_execution_mode, @addresses_resolved_execution_mode, @sandbox_mode,
|
|
581
|
+
@approval_policy, @sdk_thread_id, @workspace_skill_mount_dir, @mounted_skills_json, @last_event_at, @last_event_type, @heartbeat_at,
|
|
582
|
+
@progress_counter, @command_count, @file_change_count, @last_agent_message_preview,
|
|
583
|
+
@cancel_requested_at, @cancel_request_source_event_id
|
|
584
|
+
)
|
|
585
|
+
`).run(this.toRunParams(taskRun));
|
|
586
|
+
db.prepare(`
|
|
587
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
588
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
589
|
+
`).run(taskId, "task.claimed", `Task claimed by scheduler for package ${taskPackage.taskType}.`, now, runId, task.sourceEventId ?? null);
|
|
590
|
+
});
|
|
591
|
+
tx();
|
|
592
|
+
return this.getTask(taskId);
|
|
593
|
+
}
|
|
594
|
+
async transitionTask(taskId, status, detail, options = {}) {
|
|
595
|
+
const db = this.getDb();
|
|
596
|
+
const task = this.requireTask(taskId);
|
|
597
|
+
const now = new Date().toISOString();
|
|
598
|
+
task.status = status;
|
|
599
|
+
task.updatedAt = now;
|
|
600
|
+
const run = this.getRun(taskId, options.runId ?? task.activeRunId);
|
|
601
|
+
if (run) {
|
|
602
|
+
run.taskStatus = status;
|
|
603
|
+
run.status = options.runStatus ?? this.toRunStatus(status, run.status);
|
|
604
|
+
run.health = options.health ?? this.toRunHealth(run.status, status);
|
|
605
|
+
run.lastEventAt = now;
|
|
606
|
+
run.lastEventType = options.eventType ?? "task.status.changed";
|
|
607
|
+
run.heartbeatAt = options.heartbeatAt ?? now;
|
|
608
|
+
if (options.sdkThreadId) {
|
|
609
|
+
run.sdkThreadId = options.sdkThreadId;
|
|
610
|
+
}
|
|
611
|
+
if (options.agentMessagePreview) {
|
|
612
|
+
run.lastAgentMessagePreview = options.agentMessagePreview;
|
|
613
|
+
}
|
|
614
|
+
if (options.incrementProgressCounter) {
|
|
615
|
+
run.progressCounter = (run.progressCounter ?? 0) + 1;
|
|
616
|
+
}
|
|
617
|
+
if (options.incrementCommandCount) {
|
|
618
|
+
run.commandCount = (run.commandCount ?? 0) + 1;
|
|
619
|
+
}
|
|
620
|
+
if (options.incrementFileChangeCount) {
|
|
621
|
+
run.fileChangeCount = (run.fileChangeCount ?? 0) + 1;
|
|
622
|
+
}
|
|
623
|
+
if (options.addresses) {
|
|
624
|
+
run.addresses = {
|
|
625
|
+
...(run.addresses ?? {}),
|
|
626
|
+
...options.addresses
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
if (options.workspaceSkillMountDir) {
|
|
630
|
+
run.workspaceSkillMountDir = options.workspaceSkillMountDir;
|
|
631
|
+
}
|
|
632
|
+
if (options.mountedSkills) {
|
|
633
|
+
run.mountedSkills = options.mountedSkills;
|
|
634
|
+
}
|
|
635
|
+
if (options.sandboxMode) {
|
|
636
|
+
run.sandboxMode = options.sandboxMode;
|
|
637
|
+
}
|
|
638
|
+
if (options.approvalPolicy) {
|
|
639
|
+
run.approvalPolicy = options.approvalPolicy;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const tx = db.transaction(() => {
|
|
643
|
+
this.writeTask(task);
|
|
644
|
+
if (run) {
|
|
645
|
+
this.writeRun(run);
|
|
646
|
+
}
|
|
647
|
+
db.prepare(`
|
|
648
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
649
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
650
|
+
`).run(taskId, options.eventType ?? "task.status.changed", detail, now, options.runId ?? task.activeRunId ?? null, options.sourceEventId ?? null);
|
|
651
|
+
});
|
|
652
|
+
tx();
|
|
653
|
+
}
|
|
654
|
+
async recoverActiveTasks() {
|
|
655
|
+
const db = this.getDb();
|
|
656
|
+
const tasks = await this.listTasks();
|
|
657
|
+
const recovered = [];
|
|
658
|
+
const now = new Date().toISOString();
|
|
659
|
+
const tx = db.transaction(() => {
|
|
660
|
+
for (const task of tasks) {
|
|
661
|
+
if (!task.activeRunId) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const runId = task.activeRunId;
|
|
665
|
+
const previousStatus = task.status;
|
|
666
|
+
task.status = "queued";
|
|
667
|
+
task.updatedAt = now;
|
|
668
|
+
delete task.activeRunId;
|
|
669
|
+
this.writeTask(task);
|
|
670
|
+
const run = this.getRun(task.taskId, runId);
|
|
671
|
+
if (run) {
|
|
672
|
+
run.taskStatus = "failed";
|
|
673
|
+
run.status = "recovered";
|
|
674
|
+
run.health = "recovered";
|
|
675
|
+
run.endedAt = now;
|
|
676
|
+
run.summary = "Runtime restarted before the task completed; active lease was recovered and the task was re-queued.";
|
|
677
|
+
run.failureCategory = "environment_error";
|
|
678
|
+
run.lastEventAt = now;
|
|
679
|
+
run.lastEventType = "task.recovered";
|
|
680
|
+
run.heartbeatAt = now;
|
|
681
|
+
this.writeRun(run);
|
|
682
|
+
}
|
|
683
|
+
db.prepare(`
|
|
684
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
685
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
686
|
+
`).run(task.taskId, "task.recovered", `Recovered stale run ${runId} from ${previousStatus}; task moved back to queued.`, now, runId, task.sourceEventId ?? null);
|
|
687
|
+
recovered.push({ taskId: task.taskId, runId, previousStatus });
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
tx();
|
|
691
|
+
return recovered;
|
|
692
|
+
}
|
|
693
|
+
async updateTaskRun(taskId, runId, options) {
|
|
694
|
+
const run = this.getRun(taskId, runId);
|
|
695
|
+
if (!run) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const now = new Date().toISOString();
|
|
699
|
+
if (options.runStatus) {
|
|
700
|
+
run.status = options.runStatus;
|
|
701
|
+
}
|
|
702
|
+
if (options.health) {
|
|
703
|
+
run.health = options.health;
|
|
704
|
+
}
|
|
705
|
+
if (options.failureCategory) {
|
|
706
|
+
run.failureCategory = options.failureCategory;
|
|
707
|
+
}
|
|
708
|
+
if (options.summary) {
|
|
709
|
+
run.summary = options.summary;
|
|
710
|
+
}
|
|
711
|
+
if (options.endedAt) {
|
|
712
|
+
run.endedAt = options.endedAt;
|
|
713
|
+
}
|
|
714
|
+
if (options.cancelRequestedAt) {
|
|
715
|
+
run.cancelRequestedAt = options.cancelRequestedAt;
|
|
716
|
+
}
|
|
717
|
+
if (options.cancelRequestSourceEventId) {
|
|
718
|
+
run.cancelRequestSourceEventId = options.cancelRequestSourceEventId;
|
|
719
|
+
}
|
|
720
|
+
if (options.workspaceSkillMountDir) {
|
|
721
|
+
run.workspaceSkillMountDir = options.workspaceSkillMountDir;
|
|
722
|
+
}
|
|
723
|
+
if (options.mountedSkills) {
|
|
724
|
+
run.mountedSkills = options.mountedSkills;
|
|
725
|
+
}
|
|
726
|
+
run.lastEventAt = now;
|
|
727
|
+
run.lastEventType = options.eventType ?? run.lastEventType ?? "runtime.monitor";
|
|
728
|
+
run.heartbeatAt = options.heartbeatAt ?? now;
|
|
729
|
+
this.writeRun(run);
|
|
730
|
+
}
|
|
731
|
+
async completeTaskRun(taskId, status, summary, options = {}) {
|
|
732
|
+
const db = this.getDb();
|
|
733
|
+
const task = this.requireTask(taskId);
|
|
734
|
+
const now = new Date().toISOString();
|
|
735
|
+
const activeRunId = task.activeRunId;
|
|
736
|
+
task.status = status;
|
|
737
|
+
task.updatedAt = now;
|
|
738
|
+
delete task.activeRunId;
|
|
739
|
+
const run = this.getRun(taskId, activeRunId);
|
|
740
|
+
if (run) {
|
|
741
|
+
run.taskStatus = status;
|
|
742
|
+
run.status = status === "failed" ? "failed" : status === "canceled" ? "canceled" : "completed";
|
|
743
|
+
run.health = status === "failed" ? "failed" : status === "canceled" ? "canceled" : "completed";
|
|
744
|
+
run.endedAt = now;
|
|
745
|
+
run.summary = summary;
|
|
746
|
+
run.lastEventAt = now;
|
|
747
|
+
run.lastEventType = status === "failed" ? "execution.failed" : status === "canceled" ? "execution.canceled" : "execution.completed";
|
|
748
|
+
run.heartbeatAt = now;
|
|
749
|
+
if (options.failureCategory) {
|
|
750
|
+
run.failureCategory = options.failureCategory;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
delete run.failureCategory;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
const eventType = status === "failed" ? "execution.failed" : status === "canceled" ? "execution.canceled" : "execution.completed";
|
|
757
|
+
const tx = db.transaction(() => {
|
|
758
|
+
this.writeTask(task);
|
|
759
|
+
if (run) {
|
|
760
|
+
this.writeRun(run);
|
|
761
|
+
}
|
|
762
|
+
db.prepare(`
|
|
763
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
764
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
765
|
+
`).run(taskId, eventType, summary, now, activeRunId ?? null, task.sourceEventId ?? null);
|
|
766
|
+
});
|
|
767
|
+
tx();
|
|
768
|
+
}
|
|
769
|
+
async updateTask(taskId, patch) {
|
|
770
|
+
const task = this.requireTask(taskId);
|
|
771
|
+
Object.assign(task, patch, { updatedAt: new Date().toISOString() });
|
|
772
|
+
this.writeTask(task);
|
|
773
|
+
}
|
|
774
|
+
async appendArtifacts(taskId, artifacts) {
|
|
775
|
+
if (artifacts.length === 0) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const db = this.getDb();
|
|
779
|
+
const insert = db.prepare(`
|
|
780
|
+
INSERT INTO task_artifacts (task_id, kind, content, created_at)
|
|
781
|
+
VALUES (?, ?, ?, ?)
|
|
782
|
+
`);
|
|
783
|
+
const tx = db.transaction(() => {
|
|
784
|
+
for (const artifact of artifacts) {
|
|
785
|
+
insert.run(taskId, artifact.kind ?? "attachment", artifact.path, artifact.createdAt);
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
tx();
|
|
789
|
+
}
|
|
790
|
+
async appendTaskDeliveryBundle(bundle) {
|
|
791
|
+
this.getDb().prepare(`
|
|
792
|
+
INSERT INTO task_delivery_bundles (
|
|
793
|
+
task_id, run_id, task_type, outcome, summary_json, warnings_json, artifacts_json, publication_json, created_at
|
|
794
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
795
|
+
`).run(bundle.taskId, bundle.runId ?? null, bundle.taskType, bundle.outcome, JSON.stringify(bundle.summary), bundle.warnings ? JSON.stringify(bundle.warnings) : null, JSON.stringify(bundle.artifacts), bundle.publication ? JSON.stringify(bundle.publication) : null, bundle.createdAt);
|
|
796
|
+
}
|
|
797
|
+
async appendTaskDeliveryAttempts(attempts) {
|
|
798
|
+
if (attempts.length === 0) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const db = this.getDb();
|
|
802
|
+
const insert = db.prepare(`
|
|
803
|
+
INSERT INTO task_delivery_attempts (
|
|
804
|
+
task_id, run_id, channel, status, summary, created_at, delivered_at, error
|
|
805
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
806
|
+
`);
|
|
807
|
+
const tx = db.transaction(() => {
|
|
808
|
+
for (const attempt of attempts) {
|
|
809
|
+
insert.run(attempt.taskId, attempt.runId ?? null, attempt.channel, attempt.status, attempt.summary, attempt.createdAt, attempt.deliveredAt ?? null, attempt.error ?? null);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
tx();
|
|
813
|
+
}
|
|
814
|
+
async appendTaskPublicationAttempts(attempts) {
|
|
815
|
+
if (attempts.length === 0) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const db = this.getDb();
|
|
819
|
+
const insert = db.prepare(`
|
|
820
|
+
INSERT INTO task_publication_attempts (
|
|
821
|
+
task_id, run_id, mode, status, summary, created_at, artifact_path, topic, topic_url, review_url, review_urls_json, change_id, change_ids_json, error
|
|
822
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
823
|
+
`);
|
|
824
|
+
const tx = db.transaction(() => {
|
|
825
|
+
for (const attempt of attempts) {
|
|
826
|
+
insert.run(attempt.taskId, attempt.runId ?? null, attempt.mode, attempt.status, attempt.summary, attempt.createdAt, attempt.artifactPath ?? null, attempt.topic ?? null, attempt.topicUrl ?? null, attempt.reviewUrl ?? null, attempt.reviewUrls ? JSON.stringify(attempt.reviewUrls) : null, attempt.changeId ?? null, attempt.changeIds ? JSON.stringify(attempt.changeIds) : null, attempt.error ?? null);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
tx();
|
|
830
|
+
}
|
|
831
|
+
migrateTaskPublicationAttemptColumns(db) {
|
|
832
|
+
if (!this.tableExists(db, "task_publication_attempts")) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (!this.tableHasColumn(db, "task_publication_attempts", "review_url")) {
|
|
836
|
+
db.exec("ALTER TABLE task_publication_attempts ADD COLUMN review_url TEXT");
|
|
837
|
+
}
|
|
838
|
+
if (!this.tableHasColumn(db, "task_publication_attempts", "topic")) {
|
|
839
|
+
db.exec("ALTER TABLE task_publication_attempts ADD COLUMN topic TEXT");
|
|
840
|
+
}
|
|
841
|
+
if (!this.tableHasColumn(db, "task_publication_attempts", "topic_url")) {
|
|
842
|
+
db.exec("ALTER TABLE task_publication_attempts ADD COLUMN topic_url TEXT");
|
|
843
|
+
}
|
|
844
|
+
if (!this.tableHasColumn(db, "task_publication_attempts", "review_urls_json")) {
|
|
845
|
+
db.exec("ALTER TABLE task_publication_attempts ADD COLUMN review_urls_json TEXT");
|
|
846
|
+
}
|
|
847
|
+
if (!this.tableHasColumn(db, "task_publication_attempts", "change_id")) {
|
|
848
|
+
db.exec("ALTER TABLE task_publication_attempts ADD COLUMN change_id TEXT");
|
|
849
|
+
}
|
|
850
|
+
if (!this.tableHasColumn(db, "task_publication_attempts", "change_ids_json")) {
|
|
851
|
+
db.exec("ALTER TABLE task_publication_attempts ADD COLUMN change_ids_json TEXT");
|
|
852
|
+
}
|
|
853
|
+
if (!this.tableHasColumn(db, "task_delivery_bundles", "warnings_json")) {
|
|
854
|
+
db.exec("ALTER TABLE task_delivery_bundles ADD COLUMN warnings_json TEXT");
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
async appendObservationHints(taskId, hints) {
|
|
858
|
+
if (hints.length === 0) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const db = this.getDb();
|
|
862
|
+
const insert = db.prepare(`
|
|
863
|
+
INSERT INTO observation_hints (task_id, area, signal, reason, suggested_action)
|
|
864
|
+
VALUES (?, ?, ?, ?, ?)
|
|
865
|
+
`);
|
|
866
|
+
const tx = db.transaction(() => {
|
|
867
|
+
for (const hint of hints) {
|
|
868
|
+
insert.run(taskId, hint.area, hint.signal, hint.reason, hint.suggestedAction);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
tx();
|
|
872
|
+
}
|
|
873
|
+
async appendEvent(taskId, type, detail, options = {}) {
|
|
874
|
+
this.getDb().prepare(`
|
|
875
|
+
INSERT INTO task_events (task_id, type, detail, created_at, run_id, source_event_id)
|
|
876
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
877
|
+
`).run(taskId, type, detail, new Date().toISOString(), options.runId ?? null, options.sourceEventId ?? null);
|
|
878
|
+
}
|
|
879
|
+
// Old JSON-backed and early SQLite schemas are not compatible with the packaged-task model.
|
|
880
|
+
// For this stage we intentionally discard legacy task history and recreate the managed tables.
|
|
881
|
+
shouldResetLegacySchema(db) {
|
|
882
|
+
const requiredColumnsByTable = {
|
|
883
|
+
tasks: ["task_package_id", "active_run_id", "run_count"],
|
|
884
|
+
task_runs: ["task_package_id", "health", "last_event_type", "progress_counter", "cancel_requested_at", "addresses_visible_repo_dir", "addresses_configured_execution_mode", "addresses_resolved_execution_mode", "workspace_skill_mount_dir", "mounted_skills_json"],
|
|
885
|
+
task_packages: ["task_package_id", "source_ref", "idempotency_key", "repo", "branch", "input_json"],
|
|
886
|
+
repository_roots: ["execution_mode"]
|
|
887
|
+
};
|
|
888
|
+
return Object.entries(requiredColumnsByTable).some(([tableName, requiredColumns]) => {
|
|
889
|
+
if (!this.tableExists(db, tableName)) {
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
return requiredColumns.some((columnName) => !this.tableHasColumn(db, tableName, columnName));
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
resetManagedTaskSchema(db) {
|
|
896
|
+
db.exec("PRAGMA foreign_keys = OFF");
|
|
897
|
+
for (const tableName of MANAGED_TASK_TABLES) {
|
|
898
|
+
db.exec(`DROP TABLE IF EXISTS ${tableName}`);
|
|
899
|
+
}
|
|
900
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
901
|
+
}
|
|
902
|
+
// Address columns were renamed from flat run fields to grouped addresses_* fields.
|
|
903
|
+
// Migrate older SQLite files in place so runtime state survives local upgrades without keeping dual mappings forever.
|
|
904
|
+
migrateTaskRunAddressColumns(db) {
|
|
905
|
+
if (!this.tableExists(db, "task_runs")) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const columnRenames = [
|
|
909
|
+
{ from: "workspace_dir", to: "addresses_workspace_dir" },
|
|
910
|
+
{ from: "repo_path", to: "addresses_visible_repo_dir" },
|
|
911
|
+
{ from: "configured_execution_mode", to: "addresses_configured_execution_mode" },
|
|
912
|
+
{ from: "resolved_execution_mode", to: "addresses_resolved_execution_mode" }
|
|
913
|
+
];
|
|
914
|
+
for (const rename of columnRenames) {
|
|
915
|
+
if (!this.tableHasColumn(db, "task_runs", rename.from) || this.tableHasColumn(db, "task_runs", rename.to)) {
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
db.exec(`ALTER TABLE task_runs RENAME COLUMN ${rename.from} TO ${rename.to}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
tableExists(db, tableName) {
|
|
922
|
+
const row = db.prepare("SELECT name FROM sqlite_master WHERE type = ? AND name = ?").get("table", tableName);
|
|
923
|
+
return Boolean(row);
|
|
924
|
+
}
|
|
925
|
+
tableHasColumn(db, tableName, columnName) {
|
|
926
|
+
if (!this.tableExists(db, tableName)) {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
930
|
+
return columns.some((column) => column.name === columnName);
|
|
931
|
+
}
|
|
932
|
+
getDb() {
|
|
933
|
+
if (!this.db) {
|
|
934
|
+
this.db = new Database(this.dbPath);
|
|
935
|
+
}
|
|
936
|
+
return this.db;
|
|
937
|
+
}
|
|
938
|
+
requireTask(taskId, throwOnMissing = true) {
|
|
939
|
+
const row = this.getDb().prepare(`SELECT * FROM tasks WHERE task_id = ?`).get(taskId);
|
|
940
|
+
if (!row) {
|
|
941
|
+
if (throwOnMissing) {
|
|
942
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
943
|
+
}
|
|
944
|
+
return undefined;
|
|
945
|
+
}
|
|
946
|
+
return this.mapTaskRow(row);
|
|
947
|
+
}
|
|
948
|
+
requireTaskPackage(taskPackageId, throwOnMissing = true) {
|
|
949
|
+
const row = this.getDb().prepare(`SELECT * FROM task_packages WHERE task_package_id = ?`).get(taskPackageId);
|
|
950
|
+
if (!row) {
|
|
951
|
+
if (throwOnMissing) {
|
|
952
|
+
throw new Error(`Task package not found: ${taskPackageId}`);
|
|
953
|
+
}
|
|
954
|
+
return undefined;
|
|
955
|
+
}
|
|
956
|
+
return this.mapTaskPackageRow(row);
|
|
957
|
+
}
|
|
958
|
+
getRun(taskId, runId) {
|
|
959
|
+
if (!runId) {
|
|
960
|
+
return undefined;
|
|
961
|
+
}
|
|
962
|
+
const row = this.getDb().prepare(`SELECT * FROM task_runs WHERE task_id = ? AND run_id = ?`).get(taskId, runId);
|
|
963
|
+
return row ? this.mapRunRow(row) : undefined;
|
|
964
|
+
}
|
|
965
|
+
writeTask(task) {
|
|
966
|
+
this.getDb().prepare(`
|
|
967
|
+
UPDATE tasks SET
|
|
968
|
+
task_package_id = @task_package_id,
|
|
969
|
+
source_event_id = @source_event_id,
|
|
970
|
+
task_type = @task_type,
|
|
971
|
+
title = @title,
|
|
972
|
+
status = @status,
|
|
973
|
+
risk_level = @risk_level,
|
|
974
|
+
created_at = @created_at,
|
|
975
|
+
updated_at = @updated_at,
|
|
976
|
+
active_run_id = @active_run_id,
|
|
977
|
+
run_count = @run_count
|
|
978
|
+
WHERE task_id = @task_id
|
|
979
|
+
`).run(this.toTaskParams(task));
|
|
980
|
+
}
|
|
981
|
+
writeRun(run) {
|
|
982
|
+
this.getDb().prepare(`
|
|
983
|
+
UPDATE task_runs SET
|
|
984
|
+
task_package_id = @task_package_id,
|
|
985
|
+
source_event_id = @source_event_id,
|
|
986
|
+
status = @status,
|
|
987
|
+
task_status = @task_status,
|
|
988
|
+
health = @health,
|
|
989
|
+
started_at = @started_at,
|
|
990
|
+
ended_at = @ended_at,
|
|
991
|
+
summary = @summary,
|
|
992
|
+
failure_category = @failure_category,
|
|
993
|
+
addresses_workspace_dir = @addresses_workspace_dir,
|
|
994
|
+
addresses_visible_repo_dir = @addresses_visible_repo_dir,
|
|
995
|
+
addresses_configured_execution_mode = @addresses_configured_execution_mode,
|
|
996
|
+
addresses_resolved_execution_mode = @addresses_resolved_execution_mode,
|
|
997
|
+
sandbox_mode = @sandbox_mode,
|
|
998
|
+
approval_policy = @approval_policy,
|
|
999
|
+
sdk_thread_id = @sdk_thread_id,
|
|
1000
|
+
workspace_skill_mount_dir = @workspace_skill_mount_dir,
|
|
1001
|
+
mounted_skills_json = @mounted_skills_json,
|
|
1002
|
+
last_event_at = @last_event_at,
|
|
1003
|
+
last_event_type = @last_event_type,
|
|
1004
|
+
heartbeat_at = @heartbeat_at,
|
|
1005
|
+
progress_counter = @progress_counter,
|
|
1006
|
+
command_count = @command_count,
|
|
1007
|
+
file_change_count = @file_change_count,
|
|
1008
|
+
last_agent_message_preview = @last_agent_message_preview,
|
|
1009
|
+
cancel_requested_at = @cancel_requested_at,
|
|
1010
|
+
cancel_request_source_event_id = @cancel_request_source_event_id
|
|
1011
|
+
WHERE run_id = @run_id
|
|
1012
|
+
`).run(this.toRunParams(run));
|
|
1013
|
+
}
|
|
1014
|
+
mapRepositoryRootRow(row) {
|
|
1015
|
+
return {
|
|
1016
|
+
path: row.path,
|
|
1017
|
+
...(row.alias ? { alias: row.alias } : {}),
|
|
1018
|
+
executionMode: row.execution_mode ?? "copy",
|
|
1019
|
+
enabled: row.enabled === 1,
|
|
1020
|
+
createdAt: row.created_at,
|
|
1021
|
+
updatedAt: row.updated_at
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
mapTaskPackageRow(row) {
|
|
1025
|
+
return {
|
|
1026
|
+
taskPackageId: row.task_package_id,
|
|
1027
|
+
sourceEventId: row.source_event_id,
|
|
1028
|
+
source: row.source,
|
|
1029
|
+
...(row.source_ref ? { sourceRef: row.source_ref } : {}),
|
|
1030
|
+
...(row.idempotency_key ? { idempotencyKey: row.idempotency_key } : {}),
|
|
1031
|
+
taskType: row.task_type,
|
|
1032
|
+
title: row.title,
|
|
1033
|
+
...(row.repo ? { repo: row.repo } : {}),
|
|
1034
|
+
...(row.branch ? { branch: row.branch } : {}),
|
|
1035
|
+
input: JSON.parse(row.input_json),
|
|
1036
|
+
createdAt: row.created_at
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
mapTaskRow(row) {
|
|
1040
|
+
return {
|
|
1041
|
+
taskId: row.task_id,
|
|
1042
|
+
taskPackageId: row.task_package_id,
|
|
1043
|
+
sourceEventId: row.source_event_id,
|
|
1044
|
+
taskType: row.task_type,
|
|
1045
|
+
title: row.title,
|
|
1046
|
+
status: row.status,
|
|
1047
|
+
riskLevel: row.risk_level,
|
|
1048
|
+
createdAt: row.created_at,
|
|
1049
|
+
updatedAt: row.updated_at,
|
|
1050
|
+
...(row.active_run_id ? { activeRunId: row.active_run_id } : {}),
|
|
1051
|
+
runCount: row.run_count,
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
mapTaskEventRow(row) {
|
|
1055
|
+
return {
|
|
1056
|
+
taskId: row.task_id,
|
|
1057
|
+
type: row.type,
|
|
1058
|
+
detail: row.detail,
|
|
1059
|
+
createdAt: row.created_at,
|
|
1060
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
1061
|
+
...(row.source_event_id ? { sourceEventId: row.source_event_id } : {})
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
mapRunRow(row) {
|
|
1065
|
+
const addresses = {
|
|
1066
|
+
...(row.addresses_workspace_dir ? { workspaceDir: row.addresses_workspace_dir } : {}),
|
|
1067
|
+
...(row.addresses_visible_repo_dir ? { visibleRepoDir: row.addresses_visible_repo_dir } : {}),
|
|
1068
|
+
...(row.addresses_configured_execution_mode ? { configuredExecutionMode: row.addresses_configured_execution_mode } : {}),
|
|
1069
|
+
...(row.addresses_resolved_execution_mode ? { resolvedExecutionMode: row.addresses_resolved_execution_mode } : {})
|
|
1070
|
+
};
|
|
1071
|
+
return {
|
|
1072
|
+
runId: row.run_id,
|
|
1073
|
+
taskId: row.task_id,
|
|
1074
|
+
taskPackageId: row.task_package_id,
|
|
1075
|
+
...(row.source_event_id ? { sourceEventId: row.source_event_id } : {}),
|
|
1076
|
+
status: row.status,
|
|
1077
|
+
taskStatus: row.task_status,
|
|
1078
|
+
...(row.health ? { health: row.health } : {}),
|
|
1079
|
+
startedAt: row.started_at,
|
|
1080
|
+
...(row.ended_at ? { endedAt: row.ended_at } : {}),
|
|
1081
|
+
...(row.summary ? { summary: row.summary } : {}),
|
|
1082
|
+
...(row.failure_category ? { failureCategory: row.failure_category } : {}),
|
|
1083
|
+
...(Object.keys(addresses).length > 0 ? { addresses } : {}),
|
|
1084
|
+
...(row.sandbox_mode ? { sandboxMode: row.sandbox_mode } : {}),
|
|
1085
|
+
...(row.approval_policy ? { approvalPolicy: row.approval_policy } : {}),
|
|
1086
|
+
...(row.sdk_thread_id ? { sdkThreadId: row.sdk_thread_id } : {}),
|
|
1087
|
+
...(row.workspace_skill_mount_dir ? { workspaceSkillMountDir: row.workspace_skill_mount_dir } : {}),
|
|
1088
|
+
...(row.mounted_skills_json ? { mountedSkills: JSON.parse(row.mounted_skills_json) } : {}),
|
|
1089
|
+
...(row.last_event_at ? { lastEventAt: row.last_event_at } : {}),
|
|
1090
|
+
...(row.last_event_type ? { lastEventType: row.last_event_type } : {}),
|
|
1091
|
+
...(row.heartbeat_at ? { heartbeatAt: row.heartbeat_at } : {}),
|
|
1092
|
+
progressCounter: row.progress_counter ?? 0,
|
|
1093
|
+
commandCount: row.command_count ?? 0,
|
|
1094
|
+
fileChangeCount: row.file_change_count ?? 0,
|
|
1095
|
+
...(row.last_agent_message_preview ? { lastAgentMessagePreview: row.last_agent_message_preview } : {}),
|
|
1096
|
+
...(row.cancel_requested_at ? { cancelRequestedAt: row.cancel_requested_at } : {}),
|
|
1097
|
+
...(row.cancel_request_source_event_id ? { cancelRequestSourceEventId: row.cancel_request_source_event_id } : {})
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
toTaskPackageParams(taskPackage) {
|
|
1101
|
+
return {
|
|
1102
|
+
task_package_id: taskPackage.taskPackageId,
|
|
1103
|
+
source_event_id: taskPackage.sourceEventId,
|
|
1104
|
+
source: taskPackage.source,
|
|
1105
|
+
source_ref: taskPackage.sourceRef ?? null,
|
|
1106
|
+
idempotency_key: taskPackage.idempotencyKey ?? null,
|
|
1107
|
+
task_type: taskPackage.taskType,
|
|
1108
|
+
title: taskPackage.title,
|
|
1109
|
+
repo: taskPackage.repo ?? null,
|
|
1110
|
+
branch: taskPackage.branch ?? null,
|
|
1111
|
+
input_json: JSON.stringify(taskPackage.input),
|
|
1112
|
+
created_at: taskPackage.createdAt
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
toTaskParams(task) {
|
|
1116
|
+
return {
|
|
1117
|
+
task_id: task.taskId,
|
|
1118
|
+
task_package_id: task.taskPackageId,
|
|
1119
|
+
source_event_id: task.sourceEventId,
|
|
1120
|
+
task_type: task.taskType,
|
|
1121
|
+
title: task.title,
|
|
1122
|
+
status: task.status,
|
|
1123
|
+
risk_level: task.riskLevel,
|
|
1124
|
+
created_at: task.createdAt,
|
|
1125
|
+
updated_at: task.updatedAt,
|
|
1126
|
+
active_run_id: task.activeRunId ?? null,
|
|
1127
|
+
run_count: task.runCount ?? 0,
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
toRunParams(run) {
|
|
1131
|
+
return {
|
|
1132
|
+
run_id: run.runId,
|
|
1133
|
+
task_id: run.taskId,
|
|
1134
|
+
task_package_id: run.taskPackageId,
|
|
1135
|
+
source_event_id: run.sourceEventId ?? null,
|
|
1136
|
+
status: run.status,
|
|
1137
|
+
task_status: run.taskStatus,
|
|
1138
|
+
health: run.health ?? null,
|
|
1139
|
+
started_at: run.startedAt,
|
|
1140
|
+
ended_at: run.endedAt ?? null,
|
|
1141
|
+
summary: run.summary ?? null,
|
|
1142
|
+
failure_category: run.failureCategory ?? null,
|
|
1143
|
+
addresses_workspace_dir: run.addresses?.workspaceDir ?? null,
|
|
1144
|
+
addresses_visible_repo_dir: run.addresses?.visibleRepoDir ?? null,
|
|
1145
|
+
addresses_configured_execution_mode: run.addresses?.configuredExecutionMode ?? null,
|
|
1146
|
+
addresses_resolved_execution_mode: run.addresses?.resolvedExecutionMode ?? null,
|
|
1147
|
+
sandbox_mode: run.sandboxMode ?? null,
|
|
1148
|
+
approval_policy: run.approvalPolicy ?? null,
|
|
1149
|
+
sdk_thread_id: run.sdkThreadId ?? null,
|
|
1150
|
+
workspace_skill_mount_dir: run.workspaceSkillMountDir ?? null,
|
|
1151
|
+
mounted_skills_json: run.mountedSkills ? JSON.stringify(run.mountedSkills) : null,
|
|
1152
|
+
last_event_at: run.lastEventAt ?? null,
|
|
1153
|
+
last_event_type: run.lastEventType ?? null,
|
|
1154
|
+
heartbeat_at: run.heartbeatAt ?? null,
|
|
1155
|
+
progress_counter: run.progressCounter ?? 0,
|
|
1156
|
+
command_count: run.commandCount ?? 0,
|
|
1157
|
+
file_change_count: run.fileChangeCount ?? 0,
|
|
1158
|
+
last_agent_message_preview: run.lastAgentMessagePreview ?? null,
|
|
1159
|
+
cancel_requested_at: run.cancelRequestedAt ?? null,
|
|
1160
|
+
cancel_request_source_event_id: run.cancelRequestSourceEventId ?? null
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
toRunStatus(status, current) {
|
|
1164
|
+
if (status === "running") {
|
|
1165
|
+
return current === "leased" ? "sdk_starting" : current;
|
|
1166
|
+
}
|
|
1167
|
+
if (status === "patch_generated") {
|
|
1168
|
+
return "sdk_streaming";
|
|
1169
|
+
}
|
|
1170
|
+
if (status === "validating") {
|
|
1171
|
+
return "validating";
|
|
1172
|
+
}
|
|
1173
|
+
return current;
|
|
1174
|
+
}
|
|
1175
|
+
toRunHealth(runStatus, status) {
|
|
1176
|
+
if (status === "running" || status === "patch_generated" || status === "validating") {
|
|
1177
|
+
return runStatus === "sdk_starting" ? "waiting_start" : "healthy_running";
|
|
1178
|
+
}
|
|
1179
|
+
return "waiting_start";
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
//# sourceMappingURL=sqlite-task-store.js.map
|