umbrella-context 0.1.2 → 0.1.32
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/bin/um.js +2 -0
- package/dist/adapters/byterover-context-runtime-store.d.ts +15 -0
- package/dist/adapters/byterover-context-runtime-store.js +57 -0
- package/dist/adapters/byterover-runtime-bridge.d.ts +218 -0
- package/dist/adapters/byterover-runtime-bridge.js +343 -0
- package/dist/adapters/byterover-transport-task-store.d.ts +13 -0
- package/dist/adapters/byterover-transport-task-store.js +50 -0
- package/dist/adapters/umbrella-onboarding.d.ts +27 -0
- package/dist/adapters/umbrella-onboarding.js +79 -0
- package/dist/adapters/umbrella-provider-runtime.d.ts +38 -0
- package/dist/adapters/umbrella-provider-runtime.js +199 -0
- package/dist/adapters/vendor-byterover.d.ts +4 -0
- package/dist/adapters/vendor-byterover.js +19 -0
- package/dist/commands/activity.d.ts +2 -0
- package/dist/commands/activity.js +82 -0
- package/dist/commands/bridge.d.ts +2 -0
- package/dist/commands/bridge.js +40 -0
- package/dist/commands/catalog.d.ts +34 -0
- package/dist/commands/catalog.js +234 -0
- package/dist/commands/connect.js +14 -14
- package/dist/commands/connectors.d.ts +24 -0
- package/dist/commands/connectors.js +626 -0
- package/dist/commands/curate.d.ts +1 -0
- package/dist/commands/curate.js +48 -3
- package/dist/commands/debug.d.ts +2 -0
- package/dist/commands/debug.js +55 -0
- package/dist/commands/fix.js +54 -0
- package/dist/commands/hub.d.ts +22 -0
- package/dist/commands/hub.js +487 -0
- package/dist/commands/interactive.d.ts +2 -0
- package/dist/commands/interactive.js +970 -62
- package/dist/commands/locations.d.ts +1 -0
- package/dist/commands/locations.js +15 -12
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +34 -0
- package/dist/commands/model.d.ts +11 -0
- package/dist/commands/model.js +225 -0
- package/dist/commands/providers.d.ts +17 -0
- package/dist/commands/providers.js +379 -0
- package/dist/commands/pull.js +60 -1
- package/dist/commands/push.js +62 -2
- package/dist/commands/reset.d.ts +2 -0
- package/dist/commands/reset.js +35 -0
- package/dist/commands/restart.d.ts +2 -0
- package/dist/commands/restart.js +21 -0
- package/dist/commands/search.js +65 -1
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +241 -0
- package/dist/commands/setup.js +58 -56
- package/dist/commands/space.d.ts +12 -0
- package/dist/commands/space.js +138 -42
- package/dist/commands/status.d.ts +29 -0
- package/dist/commands/status.js +120 -19
- package/dist/commands/tasks.d.ts +2 -0
- package/dist/commands/tasks.js +95 -0
- package/dist/commands/transport.d.ts +2 -0
- package/dist/commands/transport.js +88 -0
- package/dist/commands/tree.d.ts +2 -0
- package/dist/commands/tree.js +98 -0
- package/dist/commands/tui.d.ts +2 -0
- package/dist/commands/tui.js +1273 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.js +69 -0
- package/dist/index.js +41 -5
- package/dist/repo-state.d.ts +227 -1
- package/dist/repo-state.js +920 -4
- package/dist/umbrella.js +29 -5
- package/package.json +11 -3
package/dist/repo-state.js
CHANGED
|
@@ -1,11 +1,86 @@
|
|
|
1
1
|
import { promises as fs } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { randomUUID } from "crypto";
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
import { configManager } from "./config.js";
|
|
4
6
|
const UM_DIR = ".um";
|
|
5
7
|
const CONTEXT_FILE = "context.json";
|
|
6
8
|
const PENDING_MEMORIES_FILE = "pending-memories.json";
|
|
7
9
|
const PULLED_MEMORIES_FILE = "pulled-memories.json";
|
|
8
10
|
const PULLED_FIXES_FILE = "pulled-fixes.json";
|
|
11
|
+
const CONNECTORS_FILE = "connectors.json";
|
|
12
|
+
const HUB_FILE = "hub.json";
|
|
13
|
+
const CONNECTOR_RUNS_FILE = "connector-runs.json";
|
|
14
|
+
const SESSION_FILE = "session.json";
|
|
15
|
+
const TASKS_FILE = "tasks.json";
|
|
16
|
+
const TRANSPORT_FILE = "transport.json";
|
|
17
|
+
const CONTEXT_TREE_STATE_FILE = "context-tree.json";
|
|
18
|
+
const HUB_ASSETS_DIR = "hub";
|
|
19
|
+
const CONNECTOR_ASSETS_DIR = "connectors";
|
|
20
|
+
const CONNECTOR_RUN_REPORTS_DIR = "connector-runs";
|
|
21
|
+
function normalizeSessionState(state) {
|
|
22
|
+
if (!state)
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
...state,
|
|
26
|
+
commandHistory: state.commandHistory ?? [],
|
|
27
|
+
recentQueries: state.recentQueries ?? [],
|
|
28
|
+
recentCurations: state.recentCurations ?? [],
|
|
29
|
+
lastSummary: state.lastSummary ?? null,
|
|
30
|
+
currentPanel: state.currentPanel ?? null,
|
|
31
|
+
currentFocus: state.currentFocus ?? null,
|
|
32
|
+
panelHistory: state.panelHistory ?? [],
|
|
33
|
+
recentProviderIds: state.recentProviderIds ?? [],
|
|
34
|
+
recentModels: state.recentModels ?? [],
|
|
35
|
+
recentHubSlugs: state.recentHubSlugs ?? [],
|
|
36
|
+
recentConnectorSources: state.recentConnectorSources ?? [],
|
|
37
|
+
events: state.events ?? [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function normalizeTransportState(state) {
|
|
41
|
+
if (!state)
|
|
42
|
+
return null;
|
|
43
|
+
return {
|
|
44
|
+
version: 1,
|
|
45
|
+
status: state.status ?? "idle",
|
|
46
|
+
activeTaskId: state.activeTaskId ?? null,
|
|
47
|
+
queue: state.queue ?? [],
|
|
48
|
+
stats: state.stats ?? { queued: 0, running: 0, completed: 0, failed: 0 },
|
|
49
|
+
subscriptions: state.subscriptions ?? ["task:*", "sync:*", "runtime:*", "context-tree:*"],
|
|
50
|
+
lastPushAt: state.lastPushAt ?? null,
|
|
51
|
+
lastPullAt: state.lastPullAt ?? null,
|
|
52
|
+
lastRuntimeCheckAt: state.lastRuntimeCheckAt ?? null,
|
|
53
|
+
taskKinds: state.taskKinds ?? {},
|
|
54
|
+
updatedAt: state.updatedAt ?? new Date().toISOString(),
|
|
55
|
+
events: state.events ?? [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildTransportTaskKinds(tasks) {
|
|
59
|
+
return tasks.reduce((acc, task) => {
|
|
60
|
+
acc[task.kind] = (acc[task.kind] ?? 0) + 1;
|
|
61
|
+
return acc;
|
|
62
|
+
}, {});
|
|
63
|
+
}
|
|
64
|
+
function buildTransportStats(tasks) {
|
|
65
|
+
return tasks.reduce((acc, task) => {
|
|
66
|
+
if (task.status === "running")
|
|
67
|
+
acc.running += 1;
|
|
68
|
+
else if (task.status === "completed")
|
|
69
|
+
acc.completed += 1;
|
|
70
|
+
else if (task.status === "failed")
|
|
71
|
+
acc.failed += 1;
|
|
72
|
+
else
|
|
73
|
+
acc.queued += 1;
|
|
74
|
+
return acc;
|
|
75
|
+
}, { queued: 0, running: 0, completed: 0, failed: 0 });
|
|
76
|
+
}
|
|
77
|
+
function transportStatusFromTasks(tasks) {
|
|
78
|
+
if (tasks.some((task) => task.status === "running"))
|
|
79
|
+
return "running";
|
|
80
|
+
if (tasks.some((task) => task.status === "failed"))
|
|
81
|
+
return "warning";
|
|
82
|
+
return "idle";
|
|
83
|
+
}
|
|
9
84
|
async function pathExists(targetPath) {
|
|
10
85
|
try {
|
|
11
86
|
await fs.access(targetPath);
|
|
@@ -15,19 +90,71 @@ async function pathExists(targetPath) {
|
|
|
15
90
|
return false;
|
|
16
91
|
}
|
|
17
92
|
}
|
|
93
|
+
async function findNearestPackageRoot(startDir = process.cwd()) {
|
|
94
|
+
let current = path.resolve(startDir);
|
|
95
|
+
while (true) {
|
|
96
|
+
if (await pathExists(path.join(current, "package.json")))
|
|
97
|
+
return current;
|
|
98
|
+
const parent = path.dirname(current);
|
|
99
|
+
if (parent === current)
|
|
100
|
+
return null;
|
|
101
|
+
current = parent;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
18
104
|
export async function findRepoRoot(startDir = process.cwd()) {
|
|
105
|
+
try {
|
|
106
|
+
const gitRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
107
|
+
cwd: startDir,
|
|
108
|
+
encoding: "utf8",
|
|
109
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
110
|
+
}).trim();
|
|
111
|
+
if (gitRoot)
|
|
112
|
+
return path.resolve(gitRoot);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Fall back to local heuristics when git is unavailable or the cwd is not inside a git repo.
|
|
116
|
+
}
|
|
19
117
|
let current = path.resolve(startDir);
|
|
118
|
+
let nearestPackageJsonDir = null;
|
|
20
119
|
while (true) {
|
|
21
120
|
if (await pathExists(path.join(current, ".git")))
|
|
22
121
|
return current;
|
|
23
|
-
if (await pathExists(path.join(current, "package.json")))
|
|
24
|
-
|
|
122
|
+
if (!nearestPackageJsonDir && await pathExists(path.join(current, "package.json"))) {
|
|
123
|
+
nearestPackageJsonDir = current;
|
|
124
|
+
}
|
|
25
125
|
const parent = path.dirname(current);
|
|
26
126
|
if (parent === current)
|
|
27
|
-
return path.resolve(startDir);
|
|
127
|
+
return nearestPackageJsonDir ?? path.resolve(startDir);
|
|
28
128
|
current = parent;
|
|
29
129
|
}
|
|
30
130
|
}
|
|
131
|
+
async function copyMissingFiles(sourceDir, targetDir) {
|
|
132
|
+
if (!(await pathExists(sourceDir)))
|
|
133
|
+
return;
|
|
134
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
135
|
+
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
if (!entry.isFile())
|
|
138
|
+
continue;
|
|
139
|
+
const source = path.join(sourceDir, entry.name);
|
|
140
|
+
const target = path.join(targetDir, entry.name);
|
|
141
|
+
if (await pathExists(target))
|
|
142
|
+
continue;
|
|
143
|
+
await fs.copyFile(source, target);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function uniqueBy(items, key) {
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
const result = [];
|
|
149
|
+
for (const item of items) {
|
|
150
|
+
const nextKey = key(item);
|
|
151
|
+
if (seen.has(nextKey))
|
|
152
|
+
continue;
|
|
153
|
+
seen.add(nextKey);
|
|
154
|
+
result.push(item);
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
31
158
|
function getUmDir(repoRoot) {
|
|
32
159
|
return path.join(repoRoot, UM_DIR);
|
|
33
160
|
}
|
|
@@ -43,9 +170,35 @@ function getPulledMemoriesFile(repoRoot) {
|
|
|
43
170
|
function getPulledFixesFile(repoRoot) {
|
|
44
171
|
return path.join(getUmDir(repoRoot), PULLED_FIXES_FILE);
|
|
45
172
|
}
|
|
173
|
+
function getConnectorsFile(repoRoot) {
|
|
174
|
+
return path.join(getUmDir(repoRoot), CONNECTORS_FILE);
|
|
175
|
+
}
|
|
176
|
+
function getHubFile(repoRoot) {
|
|
177
|
+
return path.join(getUmDir(repoRoot), HUB_FILE);
|
|
178
|
+
}
|
|
179
|
+
function getConnectorRunsFile(repoRoot) {
|
|
180
|
+
return path.join(getUmDir(repoRoot), CONNECTOR_RUNS_FILE);
|
|
181
|
+
}
|
|
182
|
+
function getSessionFile(repoRoot) {
|
|
183
|
+
return path.join(getUmDir(repoRoot), SESSION_FILE);
|
|
184
|
+
}
|
|
185
|
+
function getTasksFile(repoRoot) {
|
|
186
|
+
return path.join(getUmDir(repoRoot), TASKS_FILE);
|
|
187
|
+
}
|
|
188
|
+
function getTransportFile(repoRoot) {
|
|
189
|
+
return path.join(getUmDir(repoRoot), TRANSPORT_FILE);
|
|
190
|
+
}
|
|
191
|
+
function getContextTreeStateFile(repoRoot) {
|
|
192
|
+
return path.join(getUmDir(repoRoot), CONTEXT_TREE_STATE_FILE);
|
|
193
|
+
}
|
|
46
194
|
async function ensureUmDir(repoRoot) {
|
|
47
195
|
await fs.mkdir(getUmDir(repoRoot), { recursive: true });
|
|
48
196
|
}
|
|
197
|
+
async function ensureSubDir(repoRoot, name) {
|
|
198
|
+
const target = path.join(getUmDir(repoRoot), name);
|
|
199
|
+
await fs.mkdir(target, { recursive: true });
|
|
200
|
+
return target;
|
|
201
|
+
}
|
|
49
202
|
async function readJsonFile(filePath, fallback) {
|
|
50
203
|
try {
|
|
51
204
|
const raw = await fs.readFile(filePath, "utf8");
|
|
@@ -58,8 +211,47 @@ async function readJsonFile(filePath, fallback) {
|
|
|
58
211
|
async function writeJsonFile(filePath, data) {
|
|
59
212
|
await fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf8");
|
|
60
213
|
}
|
|
214
|
+
async function migrateLegacyUmDir(startDir, repoRoot) {
|
|
215
|
+
const legacyRoot = await findNearestPackageRoot(startDir);
|
|
216
|
+
if (!legacyRoot || legacyRoot === repoRoot)
|
|
217
|
+
return;
|
|
218
|
+
const legacyUmDir = getUmDir(legacyRoot);
|
|
219
|
+
if (!(await pathExists(legacyUmDir)))
|
|
220
|
+
return;
|
|
221
|
+
await ensureUmDir(repoRoot);
|
|
222
|
+
const mergeArrayFile = async (fileName, fallback, key) => {
|
|
223
|
+
const legacyPath = path.join(legacyUmDir, fileName);
|
|
224
|
+
if (!(await pathExists(legacyPath)))
|
|
225
|
+
return;
|
|
226
|
+
const rootPath = path.join(getUmDir(repoRoot), fileName);
|
|
227
|
+
const legacyItems = await readJsonFile(legacyPath, fallback);
|
|
228
|
+
const rootItems = await readJsonFile(rootPath, fallback);
|
|
229
|
+
const merged = uniqueBy([...rootItems, ...legacyItems], key);
|
|
230
|
+
await writeJsonFile(rootPath, merged);
|
|
231
|
+
};
|
|
232
|
+
const rootContextPath = getContextFile(repoRoot);
|
|
233
|
+
const legacyContextPath = getContextFile(legacyRoot);
|
|
234
|
+
if (!(await pathExists(rootContextPath)) && await pathExists(legacyContextPath)) {
|
|
235
|
+
await fs.copyFile(legacyContextPath, rootContextPath);
|
|
236
|
+
}
|
|
237
|
+
await mergeArrayFile(PENDING_MEMORIES_FILE, [], (item) => item.id);
|
|
238
|
+
await mergeArrayFile(PULLED_MEMORIES_FILE, [], (item) => item.id);
|
|
239
|
+
await mergeArrayFile(PULLED_FIXES_FILE, [], (item) => item.id);
|
|
240
|
+
await mergeArrayFile(CONNECTORS_FILE, [], (item) => item.source);
|
|
241
|
+
await mergeArrayFile(HUB_FILE, [], (item) => item.slug);
|
|
242
|
+
await mergeArrayFile(CONNECTOR_RUNS_FILE, [], (item) => item.id);
|
|
243
|
+
await copyMissingFiles(path.join(legacyUmDir, HUB_ASSETS_DIR), path.join(getUmDir(repoRoot), HUB_ASSETS_DIR));
|
|
244
|
+
await copyMissingFiles(path.join(legacyUmDir, CONNECTOR_ASSETS_DIR), path.join(getUmDir(repoRoot), CONNECTOR_ASSETS_DIR));
|
|
245
|
+
await copyMissingFiles(path.join(legacyUmDir, CONNECTOR_RUN_REPORTS_DIR), path.join(getUmDir(repoRoot), CONNECTOR_RUN_REPORTS_DIR));
|
|
246
|
+
const legacySessionPath = getSessionFile(legacyRoot);
|
|
247
|
+
const rootSessionPath = getSessionFile(repoRoot);
|
|
248
|
+
if (!(await pathExists(rootSessionPath)) && await pathExists(legacySessionPath)) {
|
|
249
|
+
await fs.copyFile(legacySessionPath, rootSessionPath);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
61
252
|
export async function ensureRepoContext(config, cwd = process.cwd()) {
|
|
62
253
|
const repoRoot = await findRepoRoot(cwd);
|
|
254
|
+
await migrateLegacyUmDir(cwd, repoRoot);
|
|
63
255
|
await ensureUmDir(repoRoot);
|
|
64
256
|
const now = new Date().toISOString();
|
|
65
257
|
const nextState = {
|
|
@@ -85,10 +277,13 @@ export async function ensureRepoContext(config, cwd = process.cwd()) {
|
|
|
85
277
|
...nextState,
|
|
86
278
|
initializedAt: existing?.initializedAt ?? now,
|
|
87
279
|
});
|
|
280
|
+
await ensureTransportState(cwd);
|
|
281
|
+
await rebuildContextTreeState(cwd);
|
|
88
282
|
return repoRoot;
|
|
89
283
|
}
|
|
90
284
|
export async function getRepoContext(cwd = process.cwd()) {
|
|
91
285
|
const repoRoot = await findRepoRoot(cwd);
|
|
286
|
+
await migrateLegacyUmDir(cwd, repoRoot);
|
|
92
287
|
const state = await readJsonFile(getContextFile(repoRoot), null);
|
|
93
288
|
return { repoRoot, state, umDir: getUmDir(repoRoot) };
|
|
94
289
|
}
|
|
@@ -115,6 +310,7 @@ export async function addPendingMemory(input, cwd = process.cwd()) {
|
|
|
115
310
|
...state,
|
|
116
311
|
updatedAt: new Date().toISOString(),
|
|
117
312
|
}), cwd);
|
|
313
|
+
await rebuildContextTreeState(cwd);
|
|
118
314
|
return entry;
|
|
119
315
|
}
|
|
120
316
|
export async function getPendingMemories(cwd = process.cwd()) {
|
|
@@ -124,6 +320,83 @@ export async function getPendingMemories(cwd = process.cwd()) {
|
|
|
124
320
|
export async function clearPendingMemories(cwd = process.cwd()) {
|
|
125
321
|
const { repoRoot } = await getRepoContext(cwd);
|
|
126
322
|
await writeJsonFile(getPendingMemoriesFile(repoRoot), []);
|
|
323
|
+
await rebuildContextTreeState(cwd);
|
|
324
|
+
}
|
|
325
|
+
export async function resetRepoState(cwd = process.cwd()) {
|
|
326
|
+
const { repoRoot, state } = await getRepoContext(cwd);
|
|
327
|
+
await ensureUmDir(repoRoot);
|
|
328
|
+
await writeJsonFile(getPendingMemoriesFile(repoRoot), []);
|
|
329
|
+
await writeJsonFile(getPulledMemoriesFile(repoRoot), []);
|
|
330
|
+
await writeJsonFile(getPulledFixesFile(repoRoot), []);
|
|
331
|
+
await writeJsonFile(getConnectorsFile(repoRoot), []);
|
|
332
|
+
await writeJsonFile(getHubFile(repoRoot), []);
|
|
333
|
+
await writeJsonFile(getConnectorRunsFile(repoRoot), []);
|
|
334
|
+
await writeJsonFile(getTasksFile(repoRoot), []);
|
|
335
|
+
await writeJsonFile(getTransportFile(repoRoot), {
|
|
336
|
+
version: 1,
|
|
337
|
+
status: "idle",
|
|
338
|
+
activeTaskId: null,
|
|
339
|
+
queue: [],
|
|
340
|
+
stats: { queued: 0, running: 0, completed: 0, failed: 0 },
|
|
341
|
+
subscriptions: ["task:*", "sync:*", "runtime:*", "context-tree:*"],
|
|
342
|
+
lastPushAt: null,
|
|
343
|
+
lastPullAt: null,
|
|
344
|
+
lastRuntimeCheckAt: null,
|
|
345
|
+
taskKinds: {},
|
|
346
|
+
updatedAt: new Date().toISOString(),
|
|
347
|
+
events: [],
|
|
348
|
+
});
|
|
349
|
+
const hubDir = path.join(getUmDir(repoRoot), HUB_ASSETS_DIR);
|
|
350
|
+
const connectorDir = path.join(getUmDir(repoRoot), CONNECTOR_ASSETS_DIR);
|
|
351
|
+
const connectorReportsDir = path.join(getUmDir(repoRoot), CONNECTOR_RUN_REPORTS_DIR);
|
|
352
|
+
await fs.rm(hubDir, { recursive: true, force: true });
|
|
353
|
+
await fs.rm(connectorDir, { recursive: true, force: true });
|
|
354
|
+
await fs.rm(connectorReportsDir, { recursive: true, force: true });
|
|
355
|
+
const now = new Date().toISOString();
|
|
356
|
+
if (state) {
|
|
357
|
+
await writeJsonFile(getContextFile(repoRoot), {
|
|
358
|
+
...state,
|
|
359
|
+
updatedAt: now,
|
|
360
|
+
lastPushAt: null,
|
|
361
|
+
lastPullAt: null,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
const nextSession = {
|
|
365
|
+
version: 1,
|
|
366
|
+
id: randomUUID(),
|
|
367
|
+
startedAt: now,
|
|
368
|
+
updatedAt: now,
|
|
369
|
+
commandHistory: [],
|
|
370
|
+
recentQueries: [],
|
|
371
|
+
recentCurations: [],
|
|
372
|
+
lastSummary: "Reset local Context state for this repo.",
|
|
373
|
+
currentPanel: "reset",
|
|
374
|
+
currentFocus: null,
|
|
375
|
+
panelHistory: ["reset"],
|
|
376
|
+
recentProviderIds: [],
|
|
377
|
+
recentModels: [],
|
|
378
|
+
recentHubSlugs: [],
|
|
379
|
+
recentConnectorSources: [],
|
|
380
|
+
events: [
|
|
381
|
+
{
|
|
382
|
+
id: randomUUID(),
|
|
383
|
+
at: now,
|
|
384
|
+
kind: "session",
|
|
385
|
+
title: "Reset local Context state",
|
|
386
|
+
detail: "Local .um content, hub installs, connectors, runs, and pulled snapshots were cleared for this repo.",
|
|
387
|
+
panel: "reset",
|
|
388
|
+
focus: null,
|
|
389
|
+
status: "warning",
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
await writeJsonFile(getSessionFile(repoRoot), nextSession);
|
|
394
|
+
await rebuildContextTreeState(cwd);
|
|
395
|
+
return {
|
|
396
|
+
repoRoot,
|
|
397
|
+
umDir: getUmDir(repoRoot),
|
|
398
|
+
resetAt: now,
|
|
399
|
+
};
|
|
127
400
|
}
|
|
128
401
|
export async function setPulledMemories(memories, cwd = process.cwd()) {
|
|
129
402
|
const { repoRoot } = await getRepoContext(cwd);
|
|
@@ -134,6 +407,15 @@ export async function setPulledMemories(memories, cwd = process.cwd()) {
|
|
|
134
407
|
updatedAt: new Date().toISOString(),
|
|
135
408
|
lastPullAt: new Date().toISOString(),
|
|
136
409
|
}), cwd);
|
|
410
|
+
await syncTransportFromTasks(cwd, { lastPullAt: new Date().toISOString() });
|
|
411
|
+
await recordTransportEvent({
|
|
412
|
+
kind: "sync-pull",
|
|
413
|
+
title: `Pulled ${memories.length} context entr${memories.length === 1 ? "y" : "ies"}`,
|
|
414
|
+
detail: "Updated the repo-local server snapshot.",
|
|
415
|
+
status: "success",
|
|
416
|
+
taskId: null,
|
|
417
|
+
}, cwd);
|
|
418
|
+
await rebuildContextTreeState(cwd);
|
|
137
419
|
}
|
|
138
420
|
export async function getPulledMemories(cwd = process.cwd()) {
|
|
139
421
|
const { repoRoot } = await getRepoContext(cwd);
|
|
@@ -148,17 +430,651 @@ export async function setPulledFixes(fixes, cwd = process.cwd()) {
|
|
|
148
430
|
updatedAt: new Date().toISOString(),
|
|
149
431
|
lastPullAt: new Date().toISOString(),
|
|
150
432
|
}), cwd);
|
|
433
|
+
await syncTransportFromTasks(cwd, { lastPullAt: new Date().toISOString() });
|
|
434
|
+
await rebuildContextTreeState(cwd);
|
|
151
435
|
}
|
|
152
436
|
export async function getPulledFixes(cwd = process.cwd()) {
|
|
153
437
|
const { repoRoot } = await getRepoContext(cwd);
|
|
154
438
|
return readJsonFile(getPulledFixesFile(repoRoot), []);
|
|
155
439
|
}
|
|
156
440
|
export async function markPushCompleted(cwd = process.cwd()) {
|
|
157
|
-
|
|
441
|
+
const next = await updateRepoContext((state) => ({
|
|
158
442
|
...state,
|
|
159
443
|
updatedAt: new Date().toISOString(),
|
|
160
444
|
lastPushAt: new Date().toISOString(),
|
|
161
445
|
}), cwd);
|
|
446
|
+
await syncTransportFromTasks(cwd, { lastPushAt: next.lastPushAt });
|
|
447
|
+
await recordTransportEvent({
|
|
448
|
+
kind: "sync-push",
|
|
449
|
+
title: "Completed Context push",
|
|
450
|
+
detail: "Local drafts were shared with the server and local push markers were updated.",
|
|
451
|
+
status: "success",
|
|
452
|
+
taskId: null,
|
|
453
|
+
}, cwd);
|
|
454
|
+
await rebuildContextTreeState(cwd);
|
|
455
|
+
return next;
|
|
456
|
+
}
|
|
457
|
+
export async function getInstalledConnectors(cwd = process.cwd()) {
|
|
458
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
459
|
+
const connectors = await readJsonFile(getConnectorsFile(repoRoot), []);
|
|
460
|
+
const deduped = uniqueBy(connectors, (item) => item.source);
|
|
461
|
+
if (deduped.length !== connectors.length) {
|
|
462
|
+
await writeJsonFile(getConnectorsFile(repoRoot), deduped);
|
|
463
|
+
}
|
|
464
|
+
return deduped;
|
|
465
|
+
}
|
|
466
|
+
export async function setInstalledConnectors(connectors, cwd = process.cwd()) {
|
|
467
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
468
|
+
await ensureUmDir(repoRoot);
|
|
469
|
+
await writeJsonFile(getConnectorsFile(repoRoot), uniqueBy(connectors, (item) => item.source));
|
|
470
|
+
await updateRepoContext((state) => ({
|
|
471
|
+
...state,
|
|
472
|
+
updatedAt: new Date().toISOString(),
|
|
473
|
+
}), cwd);
|
|
474
|
+
await rebuildContextTreeState(cwd);
|
|
475
|
+
}
|
|
476
|
+
export async function getInstalledHubEntries(cwd = process.cwd()) {
|
|
477
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
478
|
+
const entries = await readJsonFile(getHubFile(repoRoot), []);
|
|
479
|
+
const deduped = uniqueBy(entries, (item) => item.slug);
|
|
480
|
+
if (deduped.length !== entries.length) {
|
|
481
|
+
await writeJsonFile(getHubFile(repoRoot), deduped);
|
|
482
|
+
}
|
|
483
|
+
return deduped;
|
|
484
|
+
}
|
|
485
|
+
export async function setInstalledHubEntries(entries, cwd = process.cwd()) {
|
|
486
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
487
|
+
await ensureUmDir(repoRoot);
|
|
488
|
+
await writeJsonFile(getHubFile(repoRoot), uniqueBy(entries, (item) => item.slug));
|
|
489
|
+
await updateRepoContext((state) => ({
|
|
490
|
+
...state,
|
|
491
|
+
updatedAt: new Date().toISOString(),
|
|
492
|
+
}), cwd);
|
|
493
|
+
await rebuildContextTreeState(cwd);
|
|
494
|
+
}
|
|
495
|
+
export async function writeHubAsset(slug, content, cwd = process.cwd()) {
|
|
496
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
497
|
+
const dir = await ensureSubDir(repoRoot, HUB_ASSETS_DIR);
|
|
498
|
+
const filePath = path.join(dir, `${slug}.md`);
|
|
499
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
500
|
+
return filePath;
|
|
501
|
+
}
|
|
502
|
+
export async function writeHubAssetFiles(slug, files, cwd = process.cwd()) {
|
|
503
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
504
|
+
const rootDir = await ensureSubDir(repoRoot, HUB_ASSETS_DIR);
|
|
505
|
+
const dir = path.join(rootDir, slug);
|
|
506
|
+
await fs.mkdir(dir, { recursive: true });
|
|
507
|
+
const written = [];
|
|
508
|
+
for (const file of files) {
|
|
509
|
+
const filePath = path.join(dir, file.name);
|
|
510
|
+
await fs.writeFile(filePath, file.content, "utf8");
|
|
511
|
+
written.push(filePath);
|
|
512
|
+
}
|
|
513
|
+
return written;
|
|
514
|
+
}
|
|
515
|
+
export async function writeConnectorAsset(source, content, cwd = process.cwd()) {
|
|
516
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
517
|
+
const dir = await ensureSubDir(repoRoot, CONNECTOR_ASSETS_DIR);
|
|
518
|
+
const filePath = path.join(dir, `${source}.json`);
|
|
519
|
+
await writeJsonFile(filePath, content);
|
|
520
|
+
return filePath;
|
|
521
|
+
}
|
|
522
|
+
export async function writeConnectorAssetFiles(source, files, cwd = process.cwd()) {
|
|
523
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
524
|
+
const rootDir = await ensureSubDir(repoRoot, CONNECTOR_ASSETS_DIR);
|
|
525
|
+
const dir = path.join(rootDir, source);
|
|
526
|
+
await fs.mkdir(dir, { recursive: true });
|
|
527
|
+
const written = [];
|
|
528
|
+
for (const file of files) {
|
|
529
|
+
const filePath = path.join(dir, file.name);
|
|
530
|
+
await fs.writeFile(filePath, file.content, "utf8");
|
|
531
|
+
written.push(filePath);
|
|
532
|
+
}
|
|
533
|
+
return written;
|
|
534
|
+
}
|
|
535
|
+
export async function getConnectorRuns(cwd = process.cwd()) {
|
|
536
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
537
|
+
return readJsonFile(getConnectorRunsFile(repoRoot), []);
|
|
538
|
+
}
|
|
539
|
+
export async function getTasks(cwd = process.cwd()) {
|
|
540
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
541
|
+
return readJsonFile(getTasksFile(repoRoot), []);
|
|
542
|
+
}
|
|
543
|
+
export async function getTaskById(taskId, cwd = process.cwd()) {
|
|
544
|
+
const tasks = await getTasks(cwd);
|
|
545
|
+
return tasks.find((task) => task.id === taskId) ?? null;
|
|
546
|
+
}
|
|
547
|
+
export async function ensureTransportState(cwd = process.cwd()) {
|
|
548
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
549
|
+
await ensureUmDir(repoRoot);
|
|
550
|
+
const existing = normalizeTransportState(await readJsonFile(getTransportFile(repoRoot), null));
|
|
551
|
+
if (existing) {
|
|
552
|
+
await writeJsonFile(getTransportFile(repoRoot), existing);
|
|
553
|
+
return existing;
|
|
554
|
+
}
|
|
555
|
+
const next = {
|
|
556
|
+
version: 1,
|
|
557
|
+
status: "idle",
|
|
558
|
+
activeTaskId: null,
|
|
559
|
+
queue: [],
|
|
560
|
+
stats: { queued: 0, running: 0, completed: 0, failed: 0 },
|
|
561
|
+
subscriptions: ["task:*", "sync:*", "runtime:*", "context-tree:*"],
|
|
562
|
+
lastPushAt: null,
|
|
563
|
+
lastPullAt: null,
|
|
564
|
+
lastRuntimeCheckAt: null,
|
|
565
|
+
taskKinds: {},
|
|
566
|
+
updatedAt: new Date().toISOString(),
|
|
567
|
+
events: [],
|
|
568
|
+
};
|
|
569
|
+
await writeJsonFile(getTransportFile(repoRoot), next);
|
|
570
|
+
return next;
|
|
571
|
+
}
|
|
572
|
+
export async function getTransportState(cwd = process.cwd()) {
|
|
573
|
+
return ensureTransportState(cwd);
|
|
574
|
+
}
|
|
575
|
+
export async function updateTransportState(updater, cwd = process.cwd()) {
|
|
576
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
577
|
+
const current = normalizeTransportState(await ensureTransportState(cwd));
|
|
578
|
+
if (!current)
|
|
579
|
+
throw new Error("Could not initialize repo transport state.");
|
|
580
|
+
const next = normalizeTransportState(await updater(current));
|
|
581
|
+
if (!next)
|
|
582
|
+
throw new Error("Could not update repo transport state.");
|
|
583
|
+
await writeJsonFile(getTransportFile(repoRoot), next);
|
|
584
|
+
return next;
|
|
585
|
+
}
|
|
586
|
+
export async function recordTransportEvent(event, cwd = process.cwd()) {
|
|
587
|
+
return updateTransportState((state) => ({
|
|
588
|
+
...state,
|
|
589
|
+
updatedAt: new Date().toISOString(),
|
|
590
|
+
events: [
|
|
591
|
+
{
|
|
592
|
+
id: randomUUID(),
|
|
593
|
+
at: new Date().toISOString(),
|
|
594
|
+
...event,
|
|
595
|
+
},
|
|
596
|
+
...(state.events ?? []),
|
|
597
|
+
].slice(0, 50),
|
|
598
|
+
}), cwd);
|
|
599
|
+
}
|
|
600
|
+
async function syncTransportFromTasks(cwd = process.cwd(), markers) {
|
|
601
|
+
const tasks = await getTasks(cwd);
|
|
602
|
+
return updateTransportState((state) => ({
|
|
603
|
+
...state,
|
|
604
|
+
status: transportStatusFromTasks(tasks),
|
|
605
|
+
activeTaskId: tasks.find((task) => task.status === "running")?.id ?? null,
|
|
606
|
+
queue: tasks.slice(0, 25).map((task) => ({
|
|
607
|
+
id: task.id,
|
|
608
|
+
kind: task.kind,
|
|
609
|
+
title: task.title,
|
|
610
|
+
status: task.status,
|
|
611
|
+
})),
|
|
612
|
+
stats: buildTransportStats(tasks),
|
|
613
|
+
taskKinds: buildTransportTaskKinds(tasks),
|
|
614
|
+
lastPushAt: markers?.lastPushAt ?? state.lastPushAt,
|
|
615
|
+
lastPullAt: markers?.lastPullAt ?? state.lastPullAt,
|
|
616
|
+
lastRuntimeCheckAt: markers?.lastRuntimeCheckAt ?? state.lastRuntimeCheckAt,
|
|
617
|
+
updatedAt: new Date().toISOString(),
|
|
618
|
+
}), cwd);
|
|
619
|
+
}
|
|
620
|
+
export async function getContextTreeState(cwd = process.cwd()) {
|
|
621
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
622
|
+
const existing = await readJsonFile(getContextTreeStateFile(repoRoot), null);
|
|
623
|
+
if (existing)
|
|
624
|
+
return existing;
|
|
625
|
+
return rebuildContextTreeState(cwd);
|
|
626
|
+
}
|
|
627
|
+
export async function rebuildContextTreeState(cwd = process.cwd()) {
|
|
628
|
+
const { repoRoot, state } = await getRepoContext(cwd);
|
|
629
|
+
await ensureUmDir(repoRoot);
|
|
630
|
+
const activeProvider = configManager.providers.find((entry) => entry.id === configManager.config?.activeProvider) ?? null;
|
|
631
|
+
const [pending, pulled, fixes, hubEntries, connectors, connectorRuns, tasks, transport] = await Promise.all([
|
|
632
|
+
getPendingMemories(cwd),
|
|
633
|
+
getPulledMemories(cwd),
|
|
634
|
+
getPulledFixes(cwd),
|
|
635
|
+
getInstalledHubEntries(cwd),
|
|
636
|
+
getInstalledConnectors(cwd),
|
|
637
|
+
getConnectorRuns(cwd),
|
|
638
|
+
getTasks(cwd),
|
|
639
|
+
ensureTransportState(cwd),
|
|
640
|
+
]);
|
|
641
|
+
const companyId = state?.companyId ?? "company";
|
|
642
|
+
const spaceId = state?.projectId ?? "space";
|
|
643
|
+
const next = {
|
|
644
|
+
version: 1,
|
|
645
|
+
repoRoot,
|
|
646
|
+
umDir: getUmDir(repoRoot),
|
|
647
|
+
generatedAt: new Date().toISOString(),
|
|
648
|
+
summaryHandle: `${state?.companyName ?? "Unlinked"} / ${state?.projectName ?? "Unlinked"} has ${pending.length} pending drafts, ${pulled.length} pulled notes, ${fixes.length} fixes, ${tasks.length} tasks, and ${transport.status} transport status.`,
|
|
649
|
+
stats: {
|
|
650
|
+
pendingDrafts: pending.length,
|
|
651
|
+
pulledContext: pulled.length,
|
|
652
|
+
pulledFixes: fixes.length,
|
|
653
|
+
hubEntries: hubEntries.length,
|
|
654
|
+
connectors: connectors.length,
|
|
655
|
+
connectorRuns: connectorRuns.length,
|
|
656
|
+
tasks: tasks.length,
|
|
657
|
+
},
|
|
658
|
+
runtime: {
|
|
659
|
+
companyName: state?.companyName ?? null,
|
|
660
|
+
spaceName: state?.projectName ?? null,
|
|
661
|
+
provider: activeProvider ? `${activeProvider.name} (${activeProvider.kind})` : null,
|
|
662
|
+
model: configManager.config?.activeModel ?? null,
|
|
663
|
+
transportStatus: transport.status,
|
|
664
|
+
lastPushAt: state?.lastPushAt ?? null,
|
|
665
|
+
lastPullAt: state?.lastPullAt ?? null,
|
|
666
|
+
},
|
|
667
|
+
nodes: [
|
|
668
|
+
{ id: companyId, label: state?.companyName ?? "Unlinked company", kind: "company", parentId: null, detail: state ? "Current Umbrella company link" : "Repo not linked yet" },
|
|
669
|
+
{ id: spaceId, label: state?.projectName ?? "Unlinked space", kind: "space", parentId: companyId, detail: state?.workspaceName ?? null },
|
|
670
|
+
{ id: "repo-root", label: "Repo Root", kind: "folder", parentId: spaceId, detail: repoRoot },
|
|
671
|
+
{ id: "um-dir", label: ".um", kind: "folder", parentId: "repo-root", detail: getUmDir(repoRoot) },
|
|
672
|
+
{ id: "pending", label: "Local Drafts", kind: "collection", parentId: "um-dir", detail: "Repo-local curations waiting to push", count: pending.length },
|
|
673
|
+
{ id: "pulled", label: "Pulled Context", kind: "collection", parentId: "um-dir", detail: "Latest shared server snapshot", count: pulled.length },
|
|
674
|
+
{ id: "fixes", label: "Known Fixes", kind: "collection", parentId: "um-dir", detail: "Pulled fix knowledge", count: fixes.length },
|
|
675
|
+
{ id: "hub", label: "Hub Entries", kind: "collection", parentId: "um-dir", detail: "Installed reusable bundles", count: hubEntries.length },
|
|
676
|
+
{ id: "connectors", label: "Connectors", kind: "collection", parentId: "um-dir", detail: "Installed repo connectors", count: connectors.length },
|
|
677
|
+
{ id: "runs", label: "Connector Runs", kind: "collection", parentId: "um-dir", detail: "Connector execution reports", count: connectorRuns.length },
|
|
678
|
+
{ id: "tasks", label: "Tasks", kind: "collection", parentId: "um-dir", detail: "Local transport-aware task queue", count: tasks.length },
|
|
679
|
+
{
|
|
680
|
+
id: "runtime",
|
|
681
|
+
label: "Runtime",
|
|
682
|
+
kind: "runtime",
|
|
683
|
+
parentId: spaceId,
|
|
684
|
+
detail: `${transport.status} transport | provider ${activeProvider?.name ?? "none"} | model ${configManager.config?.activeModel ?? "none"}`,
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
id: "runtime-provider",
|
|
688
|
+
label: "Provider",
|
|
689
|
+
kind: "summary",
|
|
690
|
+
parentId: "runtime",
|
|
691
|
+
detail: activeProvider ? `${activeProvider.name} (${activeProvider.kind})` : "No active provider",
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
id: "runtime-model",
|
|
695
|
+
label: "Model",
|
|
696
|
+
kind: "summary",
|
|
697
|
+
parentId: "runtime",
|
|
698
|
+
detail: configManager.config?.activeModel ?? "No active model",
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
id: "runtime-transport",
|
|
702
|
+
label: "Transport Queue",
|
|
703
|
+
kind: "summary",
|
|
704
|
+
parentId: "runtime",
|
|
705
|
+
detail: `status=${transport.status} | active=${transport.activeTaskId ?? "none"} | queue=${transport.queue.length}`,
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
id: "runtime-transport-events",
|
|
709
|
+
label: "Transport Events",
|
|
710
|
+
kind: "summary",
|
|
711
|
+
parentId: "runtime-transport",
|
|
712
|
+
detail: `${transport.events.length} retained transport events`,
|
|
713
|
+
count: transport.events.length,
|
|
714
|
+
},
|
|
715
|
+
...Object.entries(transport.taskKinds ?? {}).map(([kind, count]) => ({
|
|
716
|
+
id: `runtime-task-kind-${kind}`,
|
|
717
|
+
label: `Task Kind: ${kind}`,
|
|
718
|
+
kind: "summary",
|
|
719
|
+
parentId: "tasks",
|
|
720
|
+
detail: `${count} recorded ${kind} task${count === 1 ? "" : "s"}`,
|
|
721
|
+
count,
|
|
722
|
+
})),
|
|
723
|
+
],
|
|
724
|
+
};
|
|
725
|
+
await writeJsonFile(getContextTreeStateFile(repoRoot), next);
|
|
726
|
+
return next;
|
|
727
|
+
}
|
|
728
|
+
export async function createTask(input, cwd = process.cwd()) {
|
|
729
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
730
|
+
await ensureUmDir(repoRoot);
|
|
731
|
+
const tasks = await readJsonFile(getTasksFile(repoRoot), []);
|
|
732
|
+
const now = new Date().toISOString();
|
|
733
|
+
const task = {
|
|
734
|
+
id: randomUUID(),
|
|
735
|
+
createdAt: now,
|
|
736
|
+
updatedAt: now,
|
|
737
|
+
finishedAt: null,
|
|
738
|
+
startedAt: input.status === "running" ? now : null,
|
|
739
|
+
input: input.focus ?? null,
|
|
740
|
+
result: null,
|
|
741
|
+
streamingContent: null,
|
|
742
|
+
reasoningContents: [],
|
|
743
|
+
toolCalls: [],
|
|
744
|
+
...input,
|
|
745
|
+
};
|
|
746
|
+
tasks.unshift(task);
|
|
747
|
+
await writeJsonFile(getTasksFile(repoRoot), tasks.slice(0, 100));
|
|
748
|
+
await syncTransportFromTasks(cwd);
|
|
749
|
+
await recordTransportEvent({
|
|
750
|
+
kind: "task-created",
|
|
751
|
+
title: task.title,
|
|
752
|
+
detail: task.detail,
|
|
753
|
+
status: task.status === "failed" ? "failure" : task.status === "completed" ? "success" : "info",
|
|
754
|
+
taskId: task.id,
|
|
755
|
+
}, cwd);
|
|
756
|
+
if (task.status === "running") {
|
|
757
|
+
await recordTransportEvent({
|
|
758
|
+
kind: "task-started",
|
|
759
|
+
title: task.title,
|
|
760
|
+
detail: task.detail,
|
|
761
|
+
status: "info",
|
|
762
|
+
taskId: task.id,
|
|
763
|
+
}, cwd);
|
|
764
|
+
}
|
|
765
|
+
await rebuildContextTreeState(cwd);
|
|
766
|
+
return task;
|
|
767
|
+
}
|
|
768
|
+
export async function updateTask(taskId, updater, cwd = process.cwd()) {
|
|
769
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
770
|
+
const tasks = await readJsonFile(getTasksFile(repoRoot), []);
|
|
771
|
+
const nextTasks = tasks.map((task) => task.id === taskId
|
|
772
|
+
? updater({
|
|
773
|
+
...task,
|
|
774
|
+
updatedAt: new Date().toISOString(),
|
|
775
|
+
})
|
|
776
|
+
: task);
|
|
777
|
+
await writeJsonFile(getTasksFile(repoRoot), nextTasks);
|
|
778
|
+
await syncTransportFromTasks(cwd);
|
|
779
|
+
await rebuildContextTreeState(cwd);
|
|
780
|
+
return nextTasks.find((task) => task.id === taskId) ?? null;
|
|
781
|
+
}
|
|
782
|
+
export async function patchTaskLifecycle(taskId, patch, cwd = process.cwd()) {
|
|
783
|
+
const nextTask = await updateTask(taskId, (task) => ({
|
|
784
|
+
...task,
|
|
785
|
+
status: patch.status,
|
|
786
|
+
detail: patch.detail ?? task.detail,
|
|
787
|
+
result: patch.result ?? task.result ?? null,
|
|
788
|
+
sessionId: patch.sessionId ?? task.sessionId ?? null,
|
|
789
|
+
startedAt: patch.status === "running" ? task.startedAt ?? new Date().toISOString() : task.startedAt ?? null,
|
|
790
|
+
streamingContent: patch.streamingContent ?? task.streamingContent ?? null,
|
|
791
|
+
finishedAt: patch.status === "completed" || patch.status === "failed"
|
|
792
|
+
? new Date().toISOString()
|
|
793
|
+
: task.finishedAt ?? null,
|
|
794
|
+
updatedAt: new Date().toISOString(),
|
|
795
|
+
}), cwd);
|
|
796
|
+
if (!nextTask)
|
|
797
|
+
return null;
|
|
798
|
+
const title = nextTask.title;
|
|
799
|
+
if (patch.status === "running") {
|
|
800
|
+
await recordTransportEvent({
|
|
801
|
+
kind: "task-started",
|
|
802
|
+
title,
|
|
803
|
+
detail: patch.reason ?? patch.detail ?? nextTask.detail ?? null,
|
|
804
|
+
status: "info",
|
|
805
|
+
taskId,
|
|
806
|
+
}, cwd);
|
|
807
|
+
}
|
|
808
|
+
else if (patch.status === "completed") {
|
|
809
|
+
await recordTransportEvent({
|
|
810
|
+
kind: "task-completed",
|
|
811
|
+
title,
|
|
812
|
+
detail: patch.reason ?? patch.result ?? nextTask.result ?? null,
|
|
813
|
+
status: "success",
|
|
814
|
+
taskId,
|
|
815
|
+
}, cwd);
|
|
816
|
+
}
|
|
817
|
+
else if (patch.status === "failed") {
|
|
818
|
+
await recordTransportEvent({
|
|
819
|
+
kind: "task-failed",
|
|
820
|
+
title,
|
|
821
|
+
detail: patch.reason ?? patch.detail ?? nextTask.detail ?? null,
|
|
822
|
+
status: "failure",
|
|
823
|
+
taskId,
|
|
824
|
+
}, cwd);
|
|
825
|
+
}
|
|
826
|
+
return nextTask;
|
|
827
|
+
}
|
|
828
|
+
export async function appendTaskStreamingContent(taskId, content, cwd = process.cwd()) {
|
|
829
|
+
return updateTask(taskId, (task) => ({
|
|
830
|
+
...task,
|
|
831
|
+
streamingContent: `${task.streamingContent ?? ""}${content}`,
|
|
832
|
+
updatedAt: new Date().toISOString(),
|
|
833
|
+
}), cwd);
|
|
834
|
+
}
|
|
835
|
+
export async function addTaskReasoningContent(taskId, content, isThinking = false, cwd = process.cwd()) {
|
|
836
|
+
return updateTask(taskId, (task) => ({
|
|
837
|
+
...task,
|
|
838
|
+
reasoningContents: [
|
|
839
|
+
...(task.reasoningContents ?? []),
|
|
840
|
+
{
|
|
841
|
+
content,
|
|
842
|
+
isThinking,
|
|
843
|
+
timestamp: new Date().toISOString(),
|
|
844
|
+
},
|
|
845
|
+
],
|
|
846
|
+
updatedAt: new Date().toISOString(),
|
|
847
|
+
}), cwd);
|
|
848
|
+
}
|
|
849
|
+
export async function addTaskToolCall(taskId, toolCall, cwd = process.cwd()) {
|
|
850
|
+
return updateTask(taskId, (task) => ({
|
|
851
|
+
...task,
|
|
852
|
+
toolCalls: [
|
|
853
|
+
...(task.toolCalls ?? []),
|
|
854
|
+
{
|
|
855
|
+
...toolCall,
|
|
856
|
+
timestamp: new Date().toISOString(),
|
|
857
|
+
},
|
|
858
|
+
],
|
|
859
|
+
updatedAt: new Date().toISOString(),
|
|
860
|
+
}), cwd);
|
|
861
|
+
}
|
|
862
|
+
export async function completeTask(taskId, status, detail, cwd = process.cwd()) {
|
|
863
|
+
return patchTaskLifecycle(taskId, {
|
|
864
|
+
status,
|
|
865
|
+
detail: detail ?? null,
|
|
866
|
+
reason: detail ?? null,
|
|
867
|
+
result: detail ?? null,
|
|
868
|
+
}, cwd);
|
|
869
|
+
}
|
|
870
|
+
export async function addConnectorRun(input, cwd = process.cwd()) {
|
|
871
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
872
|
+
await ensureUmDir(repoRoot);
|
|
873
|
+
const runs = await readJsonFile(getConnectorRunsFile(repoRoot), []);
|
|
874
|
+
const entry = {
|
|
875
|
+
id: randomUUID(),
|
|
876
|
+
ranAt: new Date().toISOString(),
|
|
877
|
+
...input,
|
|
878
|
+
};
|
|
879
|
+
runs.unshift(entry);
|
|
880
|
+
await writeJsonFile(getConnectorRunsFile(repoRoot), runs.slice(0, 25));
|
|
881
|
+
await updateRepoContext((state) => ({
|
|
882
|
+
...state,
|
|
883
|
+
updatedAt: new Date().toISOString(),
|
|
884
|
+
}), cwd);
|
|
885
|
+
await syncTransportFromTasks(cwd, { lastRuntimeCheckAt: new Date().toISOString() });
|
|
886
|
+
await recordTransportEvent({
|
|
887
|
+
kind: "runtime-check",
|
|
888
|
+
title: `Connector run: ${entry.connectorName}`,
|
|
889
|
+
detail: entry.summary,
|
|
890
|
+
status: entry.status === "success" ? "success" : "failure",
|
|
891
|
+
taskId: null,
|
|
892
|
+
}, cwd);
|
|
893
|
+
await rebuildContextTreeState(cwd);
|
|
894
|
+
return entry;
|
|
895
|
+
}
|
|
896
|
+
export async function writeConnectorRunReport(run, cwd = process.cwd()) {
|
|
897
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
898
|
+
const dir = await ensureSubDir(repoRoot, CONNECTOR_RUN_REPORTS_DIR);
|
|
899
|
+
const filePath = path.join(dir, `${run.id}.md`);
|
|
900
|
+
const markdown = [
|
|
901
|
+
`# ${run.connectorName}`,
|
|
902
|
+
"",
|
|
903
|
+
`- Status: ${run.status}`,
|
|
904
|
+
`- Ran At: ${run.ranAt}`,
|
|
905
|
+
`- Connector ID: ${run.connectorId}`,
|
|
906
|
+
"",
|
|
907
|
+
"## Summary",
|
|
908
|
+
"",
|
|
909
|
+
run.summary,
|
|
910
|
+
"",
|
|
911
|
+
].join("\n");
|
|
912
|
+
await fs.writeFile(filePath, markdown, "utf8");
|
|
913
|
+
return filePath;
|
|
914
|
+
}
|
|
915
|
+
export async function getSessionState(cwd = process.cwd()) {
|
|
916
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
917
|
+
return normalizeSessionState(await readJsonFile(getSessionFile(repoRoot), null));
|
|
918
|
+
}
|
|
919
|
+
export async function ensureSessionState(cwd = process.cwd()) {
|
|
920
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
921
|
+
await ensureUmDir(repoRoot);
|
|
922
|
+
const existing = normalizeSessionState(await readJsonFile(getSessionFile(repoRoot), null));
|
|
923
|
+
if (existing) {
|
|
924
|
+
await writeJsonFile(getSessionFile(repoRoot), existing);
|
|
925
|
+
return existing;
|
|
926
|
+
}
|
|
927
|
+
const now = new Date().toISOString();
|
|
928
|
+
const next = {
|
|
929
|
+
version: 1,
|
|
930
|
+
id: randomUUID(),
|
|
931
|
+
startedAt: now,
|
|
932
|
+
updatedAt: now,
|
|
933
|
+
commandHistory: [],
|
|
934
|
+
recentQueries: [],
|
|
935
|
+
recentCurations: [],
|
|
936
|
+
lastSummary: null,
|
|
937
|
+
currentPanel: null,
|
|
938
|
+
currentFocus: null,
|
|
939
|
+
panelHistory: [],
|
|
940
|
+
recentProviderIds: [],
|
|
941
|
+
recentModels: [],
|
|
942
|
+
recentHubSlugs: [],
|
|
943
|
+
recentConnectorSources: [],
|
|
944
|
+
events: [],
|
|
945
|
+
};
|
|
946
|
+
await writeJsonFile(getSessionFile(repoRoot), next);
|
|
947
|
+
return next;
|
|
948
|
+
}
|
|
949
|
+
export async function resetSessionState(cwd = process.cwd()) {
|
|
950
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
951
|
+
await ensureUmDir(repoRoot);
|
|
952
|
+
const now = new Date().toISOString();
|
|
953
|
+
const next = {
|
|
954
|
+
version: 1,
|
|
955
|
+
id: randomUUID(),
|
|
956
|
+
startedAt: now,
|
|
957
|
+
updatedAt: now,
|
|
958
|
+
commandHistory: [],
|
|
959
|
+
recentQueries: [],
|
|
960
|
+
recentCurations: [],
|
|
961
|
+
lastSummary: "Started a fresh Context session.",
|
|
962
|
+
currentPanel: null,
|
|
963
|
+
currentFocus: null,
|
|
964
|
+
panelHistory: [],
|
|
965
|
+
recentProviderIds: [],
|
|
966
|
+
recentModels: [],
|
|
967
|
+
recentHubSlugs: [],
|
|
968
|
+
recentConnectorSources: [],
|
|
969
|
+
events: [
|
|
970
|
+
{
|
|
971
|
+
id: randomUUID(),
|
|
972
|
+
at: now,
|
|
973
|
+
kind: "session",
|
|
974
|
+
title: "Started a fresh Context session",
|
|
975
|
+
detail: "Local .um data stayed in place, but the live shell history was reset.",
|
|
976
|
+
panel: "session",
|
|
977
|
+
focus: null,
|
|
978
|
+
status: "info",
|
|
979
|
+
},
|
|
980
|
+
],
|
|
981
|
+
};
|
|
982
|
+
await writeJsonFile(getSessionFile(repoRoot), next);
|
|
983
|
+
return next;
|
|
984
|
+
}
|
|
985
|
+
export async function updateSessionState(updater, cwd = process.cwd()) {
|
|
986
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
987
|
+
const current = normalizeSessionState(await ensureSessionState(cwd));
|
|
988
|
+
if (!current) {
|
|
989
|
+
throw new Error("Could not initialize repo session state.");
|
|
990
|
+
}
|
|
991
|
+
const next = normalizeSessionState(await updater(current));
|
|
992
|
+
if (!next) {
|
|
993
|
+
throw new Error("Could not update repo session state.");
|
|
994
|
+
}
|
|
995
|
+
await writeJsonFile(getSessionFile(repoRoot), next);
|
|
996
|
+
return next;
|
|
997
|
+
}
|
|
998
|
+
export async function recordSessionCommand(command, cwd = process.cwd()) {
|
|
999
|
+
return updateSessionState((state) => ({
|
|
1000
|
+
...state,
|
|
1001
|
+
updatedAt: new Date().toISOString(),
|
|
1002
|
+
commandHistory: [...(state.commandHistory ?? []), command].slice(-50),
|
|
1003
|
+
}), cwd);
|
|
1004
|
+
}
|
|
1005
|
+
export async function recordSessionQuery(query, cwd = process.cwd()) {
|
|
1006
|
+
return updateSessionState((state) => ({
|
|
1007
|
+
...state,
|
|
1008
|
+
updatedAt: new Date().toISOString(),
|
|
1009
|
+
recentQueries: [query, ...(state.recentQueries ?? []).filter((entry) => entry !== query)].slice(0, 10),
|
|
1010
|
+
}), cwd);
|
|
1011
|
+
}
|
|
1012
|
+
export async function recordSessionCuration(content, cwd = process.cwd()) {
|
|
1013
|
+
return updateSessionState((state) => ({
|
|
1014
|
+
...state,
|
|
1015
|
+
updatedAt: new Date().toISOString(),
|
|
1016
|
+
recentCurations: [content, ...(state.recentCurations ?? []).filter((entry) => entry !== content)].slice(0, 10),
|
|
1017
|
+
}), cwd);
|
|
1018
|
+
}
|
|
1019
|
+
export async function setSessionSummary(summary, cwd = process.cwd()) {
|
|
1020
|
+
return updateSessionState((state) => ({
|
|
1021
|
+
...state,
|
|
1022
|
+
updatedAt: new Date().toISOString(),
|
|
1023
|
+
lastSummary: summary,
|
|
1024
|
+
}), cwd);
|
|
1025
|
+
}
|
|
1026
|
+
export async function setSessionPanel(panel, focus, cwd = process.cwd()) {
|
|
1027
|
+
return updateSessionState((state) => ({
|
|
1028
|
+
...state,
|
|
1029
|
+
updatedAt: new Date().toISOString(),
|
|
1030
|
+
currentPanel: panel,
|
|
1031
|
+
currentFocus: focus ?? null,
|
|
1032
|
+
panelHistory: panel
|
|
1033
|
+
? [panel, ...(state.panelHistory ?? []).filter((entry) => entry !== panel)].slice(0, 10)
|
|
1034
|
+
: (state.panelHistory ?? []),
|
|
1035
|
+
}), cwd);
|
|
1036
|
+
}
|
|
1037
|
+
export async function recordSessionProvider(providerId, cwd = process.cwd()) {
|
|
1038
|
+
return updateSessionState((state) => ({
|
|
1039
|
+
...state,
|
|
1040
|
+
updatedAt: new Date().toISOString(),
|
|
1041
|
+
recentProviderIds: [providerId, ...(state.recentProviderIds ?? []).filter((entry) => entry !== providerId)].slice(0, 5),
|
|
1042
|
+
}), cwd);
|
|
1043
|
+
}
|
|
1044
|
+
export async function recordSessionModel(modelId, cwd = process.cwd()) {
|
|
1045
|
+
return updateSessionState((state) => ({
|
|
1046
|
+
...state,
|
|
1047
|
+
updatedAt: new Date().toISOString(),
|
|
1048
|
+
recentModels: [modelId, ...(state.recentModels ?? []).filter((entry) => entry !== modelId)].slice(0, 5),
|
|
1049
|
+
}), cwd);
|
|
1050
|
+
}
|
|
1051
|
+
export async function recordSessionHubEntry(slug, cwd = process.cwd()) {
|
|
1052
|
+
return updateSessionState((state) => ({
|
|
1053
|
+
...state,
|
|
1054
|
+
updatedAt: new Date().toISOString(),
|
|
1055
|
+
recentHubSlugs: [slug, ...(state.recentHubSlugs ?? []).filter((entry) => entry !== slug)].slice(0, 5),
|
|
1056
|
+
}), cwd);
|
|
1057
|
+
}
|
|
1058
|
+
export async function recordSessionConnector(source, cwd = process.cwd()) {
|
|
1059
|
+
return updateSessionState((state) => ({
|
|
1060
|
+
...state,
|
|
1061
|
+
updatedAt: new Date().toISOString(),
|
|
1062
|
+
recentConnectorSources: [source, ...(state.recentConnectorSources ?? []).filter((entry) => entry !== source)].slice(0, 5),
|
|
1063
|
+
}), cwd);
|
|
1064
|
+
}
|
|
1065
|
+
export async function recordSessionEvent(event, cwd = process.cwd()) {
|
|
1066
|
+
return updateSessionState((state) => ({
|
|
1067
|
+
...state,
|
|
1068
|
+
updatedAt: new Date().toISOString(),
|
|
1069
|
+
events: [
|
|
1070
|
+
{
|
|
1071
|
+
id: randomUUID(),
|
|
1072
|
+
at: new Date().toISOString(),
|
|
1073
|
+
...event,
|
|
1074
|
+
},
|
|
1075
|
+
...(state.events ?? []),
|
|
1076
|
+
].slice(0, 30),
|
|
1077
|
+
}), cwd);
|
|
162
1078
|
}
|
|
163
1079
|
export function summarizeLocalMemoryMatches(entries, query) {
|
|
164
1080
|
const needle = query.trim().toLowerCase();
|