smart-context-mcp 1.16.2 → 1.16.4
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 +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/client-contract.js +17 -1
- package/src/orchestration/adapters/claude-adapter.js +64 -4
- package/src/orchestration/adapters/cursor-adapter.js +64 -4
- package/src/server.js +15 -4
- package/src/storage/sqlite.js +459 -5
- package/src/tools/smart-summary.js +306 -5
- package/src/tools/smart-turn.js +11 -0
package/src/storage/sqlite.js
CHANGED
|
@@ -6,7 +6,7 @@ import { setTimeout as delay } from 'node:timers/promises';
|
|
|
6
6
|
import { projectRoot } from '../utils/runtime-config.js';
|
|
7
7
|
|
|
8
8
|
export const STATE_DB_FILENAME = 'state.sqlite';
|
|
9
|
-
export const SQLITE_SCHEMA_VERSION =
|
|
9
|
+
export const SQLITE_SCHEMA_VERSION = 6;
|
|
10
10
|
export const ACTIVE_SESSION_SCOPE = 'project';
|
|
11
11
|
export const STATE_DB_SOFT_MAX_BYTES = 32 * 1024 * 1024;
|
|
12
12
|
const STATE_DB_BUSY_TIMEOUT_MS = 1000;
|
|
@@ -14,6 +14,7 @@ const STATE_DB_LOCK_RETRY_ATTEMPTS = 3;
|
|
|
14
14
|
const STATE_DB_LOCK_RETRY_DELAY_MS = 75;
|
|
15
15
|
export const EXPECTED_TABLES = [
|
|
16
16
|
'active_session',
|
|
17
|
+
'agent_runs',
|
|
17
18
|
'context_access',
|
|
18
19
|
'hook_turn_state',
|
|
19
20
|
'meta',
|
|
@@ -21,6 +22,8 @@ export const EXPECTED_TABLES = [
|
|
|
21
22
|
'session_events',
|
|
22
23
|
'sessions',
|
|
23
24
|
'summary_cache',
|
|
25
|
+
'task_handoffs',
|
|
26
|
+
'tasks',
|
|
24
27
|
'workflow_metrics',
|
|
25
28
|
];
|
|
26
29
|
|
|
@@ -179,6 +182,69 @@ const MIGRATIONS = [
|
|
|
179
182
|
ON workflow_metrics(session_id, created_at DESC)`,
|
|
180
183
|
],
|
|
181
184
|
},
|
|
185
|
+
{
|
|
186
|
+
version: 6,
|
|
187
|
+
statements: [
|
|
188
|
+
'ALTER TABLE sessions ADD COLUMN task_id TEXT',
|
|
189
|
+
'ALTER TABLE sessions ADD COLUMN agent_id TEXT',
|
|
190
|
+
'ALTER TABLE sessions ADD COLUMN branch_name TEXT',
|
|
191
|
+
'ALTER TABLE sessions ADD COLUMN worktree_path TEXT',
|
|
192
|
+
'ALTER TABLE session_events ADD COLUMN task_id TEXT',
|
|
193
|
+
'ALTER TABLE session_events ADD COLUMN agent_id TEXT',
|
|
194
|
+
'ALTER TABLE session_events ADD COLUMN event_kind TEXT',
|
|
195
|
+
'ALTER TABLE summary_cache ADD COLUMN task_id TEXT',
|
|
196
|
+
'ALTER TABLE hook_turn_state ADD COLUMN task_id TEXT',
|
|
197
|
+
'ALTER TABLE hook_turn_state ADD COLUMN agent_id TEXT',
|
|
198
|
+
'ALTER TABLE hook_turn_state ADD COLUMN parent_agent_id TEXT',
|
|
199
|
+
'ALTER TABLE hook_turn_state ADD COLUMN conversation_id TEXT',
|
|
200
|
+
`CREATE TABLE IF NOT EXISTS tasks (
|
|
201
|
+
task_id TEXT PRIMARY KEY,
|
|
202
|
+
project_scope TEXT NOT NULL,
|
|
203
|
+
canonical_goal TEXT NOT NULL DEFAULT '',
|
|
204
|
+
normalized_goal TEXT NOT NULL DEFAULT '',
|
|
205
|
+
status TEXT NOT NULL DEFAULT 'planning',
|
|
206
|
+
branch_name TEXT,
|
|
207
|
+
worktree_path TEXT,
|
|
208
|
+
last_session_id TEXT,
|
|
209
|
+
created_at TEXT NOT NULL,
|
|
210
|
+
updated_at TEXT NOT NULL
|
|
211
|
+
)`,
|
|
212
|
+
`CREATE TABLE IF NOT EXISTS task_handoffs (
|
|
213
|
+
handoff_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
214
|
+
task_id TEXT NOT NULL,
|
|
215
|
+
session_id TEXT,
|
|
216
|
+
from_agent_id TEXT,
|
|
217
|
+
to_agent_id TEXT,
|
|
218
|
+
trigger TEXT NOT NULL,
|
|
219
|
+
summary_json TEXT NOT NULL,
|
|
220
|
+
created_at TEXT NOT NULL
|
|
221
|
+
)`,
|
|
222
|
+
`CREATE TABLE IF NOT EXISTS agent_runs (
|
|
223
|
+
run_id TEXT PRIMARY KEY,
|
|
224
|
+
task_id TEXT,
|
|
225
|
+
agent_id TEXT NOT NULL,
|
|
226
|
+
parent_agent_id TEXT,
|
|
227
|
+
client TEXT NOT NULL,
|
|
228
|
+
conversation_id TEXT,
|
|
229
|
+
session_id TEXT,
|
|
230
|
+
role TEXT,
|
|
231
|
+
started_at TEXT NOT NULL,
|
|
232
|
+
updated_at TEXT NOT NULL
|
|
233
|
+
)`,
|
|
234
|
+
`CREATE INDEX IF NOT EXISTS idx_sessions_task_updated
|
|
235
|
+
ON sessions(task_id, updated_at DESC)`,
|
|
236
|
+
`CREATE INDEX IF NOT EXISTS idx_session_events_task_created
|
|
237
|
+
ON session_events(task_id, created_at DESC)`,
|
|
238
|
+
`CREATE INDEX IF NOT EXISTS idx_summary_cache_task_updated
|
|
239
|
+
ON summary_cache(task_id, updated_at DESC)`,
|
|
240
|
+
`CREATE INDEX IF NOT EXISTS idx_tasks_updated
|
|
241
|
+
ON tasks(updated_at DESC)`,
|
|
242
|
+
`CREATE INDEX IF NOT EXISTS idx_task_handoffs_task_created
|
|
243
|
+
ON task_handoffs(task_id, created_at DESC)`,
|
|
244
|
+
`CREATE INDEX IF NOT EXISTS idx_agent_runs_task_updated
|
|
245
|
+
ON agent_runs(task_id, updated_at DESC)`,
|
|
246
|
+
],
|
|
247
|
+
},
|
|
182
248
|
];
|
|
183
249
|
|
|
184
250
|
let sqliteModulePromise = null;
|
|
@@ -672,6 +738,60 @@ const parseJsonText = (value, fallback = {}) => {
|
|
|
672
738
|
const hashLegacyPayload = (prefix, payload) =>
|
|
673
739
|
`${prefix}:${createHash('sha1').update(payload).digest('hex')}`;
|
|
674
740
|
|
|
741
|
+
const normalizeTaskText = (value) =>
|
|
742
|
+
String(value ?? '')
|
|
743
|
+
.toLowerCase()
|
|
744
|
+
.replace(/\s+/g, ' ')
|
|
745
|
+
.trim();
|
|
746
|
+
|
|
747
|
+
const slugTaskText = (value) =>
|
|
748
|
+
normalizeTaskText(value)
|
|
749
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
750
|
+
.replace(/^-+|-+$/g, '')
|
|
751
|
+
.slice(0, 48) || 'task';
|
|
752
|
+
|
|
753
|
+
export const deriveTaskId = ({ goal, branchName = '', worktreePath = '' } = {}) => {
|
|
754
|
+
const canonicalGoal = String(goal ?? '').trim();
|
|
755
|
+
const slug = slugTaskText(canonicalGoal);
|
|
756
|
+
const identity = JSON.stringify({
|
|
757
|
+
goal: normalizeTaskText(canonicalGoal),
|
|
758
|
+
branchName: normalizeTaskText(branchName),
|
|
759
|
+
worktreePath: normalizeTaskText(worktreePath),
|
|
760
|
+
});
|
|
761
|
+
const digest = createHash('sha1').update(identity).digest('hex').slice(0, 10);
|
|
762
|
+
return `${slug}-${digest}`;
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
const deriveTaskFields = ({
|
|
766
|
+
taskId = null,
|
|
767
|
+
goal = '',
|
|
768
|
+
branchName = '',
|
|
769
|
+
worktreePath = projectRoot,
|
|
770
|
+
} = {}) => {
|
|
771
|
+
const canonicalGoal = typeof goal === 'string' ? goal.trim() : '';
|
|
772
|
+
const normalizedGoal = normalizeTaskText(canonicalGoal);
|
|
773
|
+
const resolvedBranchName = typeof branchName === 'string' && branchName.trim().length > 0
|
|
774
|
+
? branchName.trim()
|
|
775
|
+
: null;
|
|
776
|
+
const resolvedWorktreePath = typeof worktreePath === 'string' && worktreePath.trim().length > 0
|
|
777
|
+
? worktreePath.trim()
|
|
778
|
+
: projectRoot;
|
|
779
|
+
|
|
780
|
+
return {
|
|
781
|
+
taskId: typeof taskId === 'string' && taskId.trim().length > 0
|
|
782
|
+
? taskId.trim()
|
|
783
|
+
: deriveTaskId({
|
|
784
|
+
goal: canonicalGoal,
|
|
785
|
+
branchName: resolvedBranchName ?? '',
|
|
786
|
+
worktreePath: resolvedWorktreePath,
|
|
787
|
+
}),
|
|
788
|
+
canonicalGoal,
|
|
789
|
+
normalizedGoal,
|
|
790
|
+
branchName: resolvedBranchName,
|
|
791
|
+
worktreePath: resolvedWorktreePath,
|
|
792
|
+
};
|
|
793
|
+
};
|
|
794
|
+
|
|
675
795
|
const buildSessionRecord = (sessionId, data) => {
|
|
676
796
|
const completed = normalizeStringArray(data.completed);
|
|
677
797
|
const decisions = normalizeStringArray(data.decisions);
|
|
@@ -681,10 +801,20 @@ const buildSessionRecord = (sessionId, data) => {
|
|
|
681
801
|
const blockers = normalizeStringArray(data.blockers);
|
|
682
802
|
const updatedAt = toIsoString(data.updatedAt);
|
|
683
803
|
const createdAt = toIsoString(data.createdAt, updatedAt);
|
|
804
|
+
const taskFields = deriveTaskFields({
|
|
805
|
+
taskId: data.taskId,
|
|
806
|
+
goal: data.goal,
|
|
807
|
+
branchName: data.branchName,
|
|
808
|
+
worktreePath: data.worktreePath,
|
|
809
|
+
});
|
|
684
810
|
|
|
685
811
|
return {
|
|
686
812
|
sessionId,
|
|
687
813
|
goal: typeof data.goal === 'string' ? data.goal : '',
|
|
814
|
+
taskId: taskFields.taskId,
|
|
815
|
+
agentId: typeof data.agentId === 'string' && data.agentId.trim().length > 0 ? data.agentId : null,
|
|
816
|
+
branchName: taskFields.branchName,
|
|
817
|
+
worktreePath: taskFields.worktreePath,
|
|
688
818
|
status: normalizeStatus(data.status),
|
|
689
819
|
currentFocus: typeof data.currentFocus === 'string' ? data.currentFocus : '',
|
|
690
820
|
whyBlocked: typeof data.whyBlocked === 'string' ? data.whyBlocked : '',
|
|
@@ -710,6 +840,10 @@ const upsertSession = (db, record) => {
|
|
|
710
840
|
current_focus,
|
|
711
841
|
why_blocked,
|
|
712
842
|
next_step,
|
|
843
|
+
task_id,
|
|
844
|
+
agent_id,
|
|
845
|
+
branch_name,
|
|
846
|
+
worktree_path,
|
|
713
847
|
pinned_context_json,
|
|
714
848
|
unresolved_questions_json,
|
|
715
849
|
blockers_json,
|
|
@@ -720,13 +854,17 @@ const upsertSession = (db, record) => {
|
|
|
720
854
|
created_at,
|
|
721
855
|
updated_at
|
|
722
856
|
)
|
|
723
|
-
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
857
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
724
858
|
ON CONFLICT(session_id) DO UPDATE SET
|
|
725
859
|
goal = excluded.goal,
|
|
726
860
|
status = excluded.status,
|
|
727
861
|
current_focus = excluded.current_focus,
|
|
728
862
|
why_blocked = excluded.why_blocked,
|
|
729
863
|
next_step = excluded.next_step,
|
|
864
|
+
task_id = excluded.task_id,
|
|
865
|
+
agent_id = excluded.agent_id,
|
|
866
|
+
branch_name = excluded.branch_name,
|
|
867
|
+
worktree_path = excluded.worktree_path,
|
|
730
868
|
pinned_context_json = excluded.pinned_context_json,
|
|
731
869
|
unresolved_questions_json = excluded.unresolved_questions_json,
|
|
732
870
|
blockers_json = excluded.blockers_json,
|
|
@@ -743,6 +881,10 @@ const upsertSession = (db, record) => {
|
|
|
743
881
|
record.currentFocus,
|
|
744
882
|
record.whyBlocked,
|
|
745
883
|
record.nextStep,
|
|
884
|
+
record.taskId,
|
|
885
|
+
record.agentId,
|
|
886
|
+
record.branchName,
|
|
887
|
+
record.worktreePath,
|
|
746
888
|
toJsonText(record.pinnedContext, []),
|
|
747
889
|
toJsonText(record.unresolvedQuestions, []),
|
|
748
890
|
toJsonText(record.blockers, []),
|
|
@@ -761,15 +903,21 @@ const insertLegacySessionEvent = (db, record, sourceFile) => {
|
|
|
761
903
|
INSERT OR IGNORE INTO session_events(
|
|
762
904
|
session_id,
|
|
763
905
|
event_type,
|
|
906
|
+
task_id,
|
|
907
|
+
agent_id,
|
|
908
|
+
event_kind,
|
|
764
909
|
payload_json,
|
|
765
910
|
token_cost,
|
|
766
911
|
created_at,
|
|
767
912
|
legacy_key
|
|
768
913
|
)
|
|
769
|
-
VALUES(?, ?, ?, ?, ?, ?)
|
|
914
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
770
915
|
`).run(
|
|
771
916
|
record.sessionId,
|
|
772
917
|
'legacy_import',
|
|
918
|
+
record.taskId,
|
|
919
|
+
record.agentId,
|
|
920
|
+
'legacy_import',
|
|
773
921
|
JSON.stringify({ source: sourceFile, updatedAt: record.updatedAt }),
|
|
774
922
|
0,
|
|
775
923
|
record.updatedAt,
|
|
@@ -866,17 +1014,229 @@ const upsertActiveSession = (db, sessionId, updatedAt) => {
|
|
|
866
1014
|
`).run(ACTIVE_SESSION_SCOPE, sessionId, updatedAt);
|
|
867
1015
|
};
|
|
868
1016
|
|
|
1017
|
+
const upsertTaskRow = (db, {
|
|
1018
|
+
taskId,
|
|
1019
|
+
canonicalGoal,
|
|
1020
|
+
normalizedGoal,
|
|
1021
|
+
status = 'planning',
|
|
1022
|
+
branchName = null,
|
|
1023
|
+
worktreePath = projectRoot,
|
|
1024
|
+
lastSessionId = null,
|
|
1025
|
+
createdAt = new Date().toISOString(),
|
|
1026
|
+
updatedAt = createdAt,
|
|
1027
|
+
} = {}) => {
|
|
1028
|
+
db.prepare(`
|
|
1029
|
+
INSERT INTO tasks(
|
|
1030
|
+
task_id,
|
|
1031
|
+
project_scope,
|
|
1032
|
+
canonical_goal,
|
|
1033
|
+
normalized_goal,
|
|
1034
|
+
status,
|
|
1035
|
+
branch_name,
|
|
1036
|
+
worktree_path,
|
|
1037
|
+
last_session_id,
|
|
1038
|
+
created_at,
|
|
1039
|
+
updated_at
|
|
1040
|
+
)
|
|
1041
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1042
|
+
ON CONFLICT(task_id) DO UPDATE SET
|
|
1043
|
+
canonical_goal = excluded.canonical_goal,
|
|
1044
|
+
normalized_goal = excluded.normalized_goal,
|
|
1045
|
+
status = excluded.status,
|
|
1046
|
+
branch_name = excluded.branch_name,
|
|
1047
|
+
worktree_path = excluded.worktree_path,
|
|
1048
|
+
last_session_id = excluded.last_session_id,
|
|
1049
|
+
updated_at = excluded.updated_at,
|
|
1050
|
+
created_at = tasks.created_at
|
|
1051
|
+
`).run(
|
|
1052
|
+
taskId,
|
|
1053
|
+
projectRoot,
|
|
1054
|
+
canonicalGoal,
|
|
1055
|
+
normalizedGoal,
|
|
1056
|
+
normalizeStatus(status, 'planning'),
|
|
1057
|
+
branchName,
|
|
1058
|
+
worktreePath,
|
|
1059
|
+
lastSessionId,
|
|
1060
|
+
createdAt,
|
|
1061
|
+
updatedAt,
|
|
1062
|
+
);
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
const getTaskRow = (db, taskId) => db.prepare(`
|
|
1066
|
+
SELECT
|
|
1067
|
+
task_id,
|
|
1068
|
+
project_scope,
|
|
1069
|
+
canonical_goal,
|
|
1070
|
+
normalized_goal,
|
|
1071
|
+
status,
|
|
1072
|
+
branch_name,
|
|
1073
|
+
worktree_path,
|
|
1074
|
+
last_session_id,
|
|
1075
|
+
created_at,
|
|
1076
|
+
updated_at
|
|
1077
|
+
FROM tasks
|
|
1078
|
+
WHERE task_id = ?
|
|
1079
|
+
`).get(taskId);
|
|
1080
|
+
|
|
1081
|
+
const normalizeTaskRow = (row) => {
|
|
1082
|
+
if (!row) {
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
return {
|
|
1087
|
+
taskId: row.task_id,
|
|
1088
|
+
projectScope: row.project_scope,
|
|
1089
|
+
canonicalGoal: row.canonical_goal,
|
|
1090
|
+
normalizedGoal: row.normalized_goal,
|
|
1091
|
+
status: row.status,
|
|
1092
|
+
branchName: row.branch_name ?? null,
|
|
1093
|
+
worktreePath: row.worktree_path ?? null,
|
|
1094
|
+
lastSessionId: row.last_session_id ?? null,
|
|
1095
|
+
createdAt: row.created_at,
|
|
1096
|
+
updatedAt: row.updated_at,
|
|
1097
|
+
};
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
const insertTaskHandoffRow = (db, {
|
|
1101
|
+
taskId,
|
|
1102
|
+
sessionId = null,
|
|
1103
|
+
fromAgentId = null,
|
|
1104
|
+
toAgentId = null,
|
|
1105
|
+
trigger,
|
|
1106
|
+
summary = {},
|
|
1107
|
+
createdAt = new Date().toISOString(),
|
|
1108
|
+
} = {}) => {
|
|
1109
|
+
const result = db.prepare(`
|
|
1110
|
+
INSERT INTO task_handoffs(
|
|
1111
|
+
task_id,
|
|
1112
|
+
session_id,
|
|
1113
|
+
from_agent_id,
|
|
1114
|
+
to_agent_id,
|
|
1115
|
+
trigger,
|
|
1116
|
+
summary_json,
|
|
1117
|
+
created_at
|
|
1118
|
+
)
|
|
1119
|
+
VALUES(?, ?, ?, ?, ?, ?, ?)
|
|
1120
|
+
`).run(
|
|
1121
|
+
taskId,
|
|
1122
|
+
sessionId,
|
|
1123
|
+
fromAgentId,
|
|
1124
|
+
toAgentId,
|
|
1125
|
+
trigger,
|
|
1126
|
+
JSON.stringify(summary ?? {}),
|
|
1127
|
+
createdAt,
|
|
1128
|
+
);
|
|
1129
|
+
|
|
1130
|
+
return result.lastInsertRowid;
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
const getLatestTaskHandoffRow = (db, taskId) => db.prepare(`
|
|
1134
|
+
SELECT
|
|
1135
|
+
handoff_id,
|
|
1136
|
+
task_id,
|
|
1137
|
+
session_id,
|
|
1138
|
+
from_agent_id,
|
|
1139
|
+
to_agent_id,
|
|
1140
|
+
trigger,
|
|
1141
|
+
summary_json,
|
|
1142
|
+
created_at
|
|
1143
|
+
FROM task_handoffs
|
|
1144
|
+
WHERE task_id = ?
|
|
1145
|
+
ORDER BY datetime(created_at) DESC, handoff_id DESC
|
|
1146
|
+
LIMIT 1
|
|
1147
|
+
`).get(taskId);
|
|
1148
|
+
|
|
1149
|
+
const normalizeTaskHandoffRow = (row) => {
|
|
1150
|
+
if (!row) {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
return {
|
|
1155
|
+
handoffId: Number(row.handoff_id),
|
|
1156
|
+
taskId: row.task_id,
|
|
1157
|
+
sessionId: row.session_id ?? null,
|
|
1158
|
+
fromAgentId: row.from_agent_id ?? null,
|
|
1159
|
+
toAgentId: row.to_agent_id ?? null,
|
|
1160
|
+
trigger: row.trigger,
|
|
1161
|
+
summary: parseJsonText(row.summary_json, {}),
|
|
1162
|
+
createdAt: row.created_at,
|
|
1163
|
+
};
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
const upsertAgentRunRow = (db, {
|
|
1167
|
+
runId,
|
|
1168
|
+
taskId = null,
|
|
1169
|
+
agentId,
|
|
1170
|
+
parentAgentId = null,
|
|
1171
|
+
client,
|
|
1172
|
+
conversationId = null,
|
|
1173
|
+
sessionId = null,
|
|
1174
|
+
role = 'main',
|
|
1175
|
+
startedAt = new Date().toISOString(),
|
|
1176
|
+
updatedAt = startedAt,
|
|
1177
|
+
} = {}) => {
|
|
1178
|
+
db.prepare(`
|
|
1179
|
+
INSERT INTO agent_runs(
|
|
1180
|
+
run_id,
|
|
1181
|
+
task_id,
|
|
1182
|
+
agent_id,
|
|
1183
|
+
parent_agent_id,
|
|
1184
|
+
client,
|
|
1185
|
+
conversation_id,
|
|
1186
|
+
session_id,
|
|
1187
|
+
role,
|
|
1188
|
+
started_at,
|
|
1189
|
+
updated_at
|
|
1190
|
+
)
|
|
1191
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1192
|
+
ON CONFLICT(run_id) DO UPDATE SET
|
|
1193
|
+
task_id = excluded.task_id,
|
|
1194
|
+
agent_id = excluded.agent_id,
|
|
1195
|
+
parent_agent_id = excluded.parent_agent_id,
|
|
1196
|
+
client = excluded.client,
|
|
1197
|
+
conversation_id = excluded.conversation_id,
|
|
1198
|
+
session_id = excluded.session_id,
|
|
1199
|
+
role = excluded.role,
|
|
1200
|
+
updated_at = excluded.updated_at,
|
|
1201
|
+
started_at = agent_runs.started_at
|
|
1202
|
+
`).run(
|
|
1203
|
+
runId,
|
|
1204
|
+
taskId,
|
|
1205
|
+
agentId,
|
|
1206
|
+
parentAgentId,
|
|
1207
|
+
client,
|
|
1208
|
+
conversationId,
|
|
1209
|
+
sessionId,
|
|
1210
|
+
role,
|
|
1211
|
+
startedAt,
|
|
1212
|
+
updatedAt,
|
|
1213
|
+
);
|
|
1214
|
+
};
|
|
1215
|
+
|
|
869
1216
|
const buildHookTurnRecord = (hookKey, state = {}) => {
|
|
870
1217
|
const startedAt = toIsoString(state.startedAt);
|
|
871
1218
|
const updatedAt = toIsoString(state.updatedAt, startedAt);
|
|
1219
|
+
const conversationId = typeof state.conversationId === 'string' && state.conversationId.trim().length > 0
|
|
1220
|
+
? state.conversationId
|
|
1221
|
+
: typeof state.cursorConversationId === 'string' && state.cursorConversationId.trim().length > 0
|
|
1222
|
+
? state.cursorConversationId
|
|
1223
|
+
: typeof state.claudeSessionId === 'string' && state.claudeSessionId.trim().length > 0
|
|
1224
|
+
? state.claudeSessionId
|
|
1225
|
+
: '';
|
|
872
1226
|
|
|
873
1227
|
return {
|
|
874
1228
|
hookKey,
|
|
875
1229
|
client: typeof state.client === 'string' && state.client.trim().length > 0 ? state.client : 'claude',
|
|
876
|
-
claudeSessionId:
|
|
1230
|
+
claudeSessionId: conversationId,
|
|
1231
|
+
conversationId,
|
|
877
1232
|
projectSessionId: typeof state.projectSessionId === 'string' && state.projectSessionId.trim().length > 0
|
|
878
1233
|
? state.projectSessionId
|
|
879
1234
|
: null,
|
|
1235
|
+
taskId: typeof state.taskId === 'string' && state.taskId.trim().length > 0 ? state.taskId : null,
|
|
1236
|
+
agentId: typeof state.agentId === 'string' && state.agentId.trim().length > 0 ? state.agentId : null,
|
|
1237
|
+
parentAgentId: typeof state.parentAgentId === 'string' && state.parentAgentId.trim().length > 0
|
|
1238
|
+
? state.parentAgentId
|
|
1239
|
+
: null,
|
|
880
1240
|
turnId: typeof state.turnId === 'string' && state.turnId.trim().length > 0
|
|
881
1241
|
? state.turnId
|
|
882
1242
|
: `turn-${Date.now()}`,
|
|
@@ -906,7 +1266,11 @@ const normalizeHookTurnRow = (row) => {
|
|
|
906
1266
|
hookKey: row.hook_key,
|
|
907
1267
|
client: row.client,
|
|
908
1268
|
claudeSessionId: row.claude_session_id,
|
|
1269
|
+
conversationId: row.conversation_id ?? row.claude_session_id,
|
|
909
1270
|
projectSessionId: row.project_session_id ?? null,
|
|
1271
|
+
taskId: row.task_id ?? null,
|
|
1272
|
+
agentId: row.agent_id ?? null,
|
|
1273
|
+
parentAgentId: row.parent_agent_id ?? null,
|
|
910
1274
|
turnId: row.turn_id,
|
|
911
1275
|
promptPreview: row.prompt_preview,
|
|
912
1276
|
continuityState: row.continuity_state,
|
|
@@ -927,6 +1291,10 @@ const readHookTurnRow = (db, hookKey) => db.prepare(`
|
|
|
927
1291
|
client,
|
|
928
1292
|
claude_session_id,
|
|
929
1293
|
project_session_id,
|
|
1294
|
+
task_id,
|
|
1295
|
+
agent_id,
|
|
1296
|
+
parent_agent_id,
|
|
1297
|
+
conversation_id,
|
|
930
1298
|
turn_id,
|
|
931
1299
|
prompt_preview,
|
|
932
1300
|
continuity_state,
|
|
@@ -949,6 +1317,10 @@ const upsertHookTurnRow = (db, record) => {
|
|
|
949
1317
|
client,
|
|
950
1318
|
claude_session_id,
|
|
951
1319
|
project_session_id,
|
|
1320
|
+
task_id,
|
|
1321
|
+
agent_id,
|
|
1322
|
+
parent_agent_id,
|
|
1323
|
+
conversation_id,
|
|
952
1324
|
turn_id,
|
|
953
1325
|
prompt_preview,
|
|
954
1326
|
continuity_state,
|
|
@@ -961,11 +1333,15 @@ const upsertHookTurnRow = (db, record) => {
|
|
|
961
1333
|
started_at,
|
|
962
1334
|
updated_at
|
|
963
1335
|
)
|
|
964
|
-
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1336
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
965
1337
|
ON CONFLICT(hook_key) DO UPDATE SET
|
|
966
1338
|
client = excluded.client,
|
|
967
1339
|
claude_session_id = excluded.claude_session_id,
|
|
968
1340
|
project_session_id = excluded.project_session_id,
|
|
1341
|
+
task_id = excluded.task_id,
|
|
1342
|
+
agent_id = excluded.agent_id,
|
|
1343
|
+
parent_agent_id = excluded.parent_agent_id,
|
|
1344
|
+
conversation_id = excluded.conversation_id,
|
|
969
1345
|
turn_id = excluded.turn_id,
|
|
970
1346
|
prompt_preview = excluded.prompt_preview,
|
|
971
1347
|
continuity_state = excluded.continuity_state,
|
|
@@ -982,6 +1358,10 @@ const upsertHookTurnRow = (db, record) => {
|
|
|
982
1358
|
record.client,
|
|
983
1359
|
record.claudeSessionId,
|
|
984
1360
|
record.projectSessionId,
|
|
1361
|
+
record.taskId,
|
|
1362
|
+
record.agentId,
|
|
1363
|
+
record.parentAgentId,
|
|
1364
|
+
record.conversationId,
|
|
985
1365
|
record.turnId,
|
|
986
1366
|
record.promptPreview,
|
|
987
1367
|
record.continuityState,
|
|
@@ -1013,6 +1393,69 @@ export const deleteHookTurnState = async ({ filePath = getStateDbPath(), hookKey
|
|
|
1013
1393
|
return existing;
|
|
1014
1394
|
}, { filePath });
|
|
1015
1395
|
|
|
1396
|
+
export const getTask = async ({ filePath = getStateDbPath(), taskId, readOnly = false } = {}) => {
|
|
1397
|
+
const reader = readOnly ? withStateDbSnapshot : withStateDb;
|
|
1398
|
+
return reader((db) => normalizeTaskRow(getTaskRow(db, taskId)), { filePath });
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
export const persistTaskHandoff = async ({ filePath = getStateDbPath(), taskId, sessionId = null, fromAgentId = null, toAgentId = null, trigger, summary = {} } = {}) =>
|
|
1402
|
+
withStateDb((db) => {
|
|
1403
|
+
const createdAt = new Date().toISOString();
|
|
1404
|
+
const handoffId = insertTaskHandoffRow(db, {
|
|
1405
|
+
taskId,
|
|
1406
|
+
sessionId,
|
|
1407
|
+
fromAgentId,
|
|
1408
|
+
toAgentId,
|
|
1409
|
+
trigger,
|
|
1410
|
+
summary,
|
|
1411
|
+
createdAt,
|
|
1412
|
+
});
|
|
1413
|
+
return normalizeTaskHandoffRow(getLatestTaskHandoffRow(db, taskId)) ?? {
|
|
1414
|
+
handoffId,
|
|
1415
|
+
taskId,
|
|
1416
|
+
sessionId,
|
|
1417
|
+
fromAgentId,
|
|
1418
|
+
toAgentId,
|
|
1419
|
+
trigger,
|
|
1420
|
+
summary,
|
|
1421
|
+
createdAt,
|
|
1422
|
+
};
|
|
1423
|
+
}, { filePath });
|
|
1424
|
+
|
|
1425
|
+
export const getLatestTaskHandoff = async ({ filePath = getStateDbPath(), taskId, readOnly = false } = {}) => {
|
|
1426
|
+
const reader = readOnly ? withStateDbSnapshot : withStateDb;
|
|
1427
|
+
return reader((db) => normalizeTaskHandoffRow(getLatestTaskHandoffRow(db, taskId)), { filePath });
|
|
1428
|
+
};
|
|
1429
|
+
|
|
1430
|
+
export const upsertAgentRun = async ({ filePath = getStateDbPath(), runId, taskId = null, agentId, parentAgentId = null, client, conversationId = null, sessionId = null, role = 'main' } = {}) =>
|
|
1431
|
+
withStateDb((db) => {
|
|
1432
|
+
const now = new Date().toISOString();
|
|
1433
|
+
upsertAgentRunRow(db, {
|
|
1434
|
+
runId,
|
|
1435
|
+
taskId,
|
|
1436
|
+
agentId,
|
|
1437
|
+
parentAgentId,
|
|
1438
|
+
client,
|
|
1439
|
+
conversationId,
|
|
1440
|
+
sessionId,
|
|
1441
|
+
role,
|
|
1442
|
+
startedAt: now,
|
|
1443
|
+
updatedAt: now,
|
|
1444
|
+
});
|
|
1445
|
+
return {
|
|
1446
|
+
runId,
|
|
1447
|
+
taskId,
|
|
1448
|
+
agentId,
|
|
1449
|
+
parentAgentId,
|
|
1450
|
+
client,
|
|
1451
|
+
conversationId,
|
|
1452
|
+
sessionId,
|
|
1453
|
+
role,
|
|
1454
|
+
startedAt: now,
|
|
1455
|
+
updatedAt: now,
|
|
1456
|
+
};
|
|
1457
|
+
}, { filePath });
|
|
1458
|
+
|
|
1016
1459
|
const listLegacySessionFiles = (sessionsDir) => {
|
|
1017
1460
|
if (!fs.existsSync(sessionsDir)) {
|
|
1018
1461
|
return [];
|
|
@@ -1051,6 +1494,17 @@ export const importLegacyState = async ({
|
|
|
1051
1494
|
const record = buildSessionRecord(sessionId, payload);
|
|
1052
1495
|
|
|
1053
1496
|
upsertSession(db, record);
|
|
1497
|
+
upsertTaskRow(db, {
|
|
1498
|
+
taskId: record.taskId,
|
|
1499
|
+
canonicalGoal: record.goal,
|
|
1500
|
+
normalizedGoal: normalizeTaskText(record.goal),
|
|
1501
|
+
status: record.status,
|
|
1502
|
+
branchName: record.branchName,
|
|
1503
|
+
worktreePath: record.worktreePath,
|
|
1504
|
+
lastSessionId: sessionId,
|
|
1505
|
+
createdAt: record.createdAt,
|
|
1506
|
+
updatedAt: record.updatedAt,
|
|
1507
|
+
});
|
|
1054
1508
|
insertLegacySessionEvent(db, record, fileName);
|
|
1055
1509
|
report.sessions[exists ? 'skipped' : 'imported'] += 1;
|
|
1056
1510
|
}
|