flyspace-cli 0.1.15 → 0.1.16
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/dist/{chunk-KFT2YP5W.js → chunk-2OGLCT7I.js} +236 -63
- package/dist/cli.js +637 -271
- package/dist/index.js +1 -1
- package/package.json +6 -4
|
@@ -263,7 +263,7 @@ function stopAutomationRegistryPolling() {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
// src/channels.ts
|
|
266
|
-
import { existsSync as existsSync2, mkdirSync, statSync } from "fs";
|
|
266
|
+
import { existsSync as existsSync2, mkdirSync, realpathSync, statSync } from "fs";
|
|
267
267
|
import { homedir } from "os";
|
|
268
268
|
import { isAbsolute, resolve as resolve2, sep } from "path";
|
|
269
269
|
function isLogicalCwd(p) {
|
|
@@ -272,11 +272,22 @@ function isLogicalCwd(p) {
|
|
|
272
272
|
function logicalRoot() {
|
|
273
273
|
return process.env["FLYSPACE_ROOT"] || resolve2(homedir(), "Flyspace");
|
|
274
274
|
}
|
|
275
|
+
var workspaceRoots = /* @__PURE__ */ new Map();
|
|
276
|
+
async function refreshWorkspaceRoots() {
|
|
277
|
+
const { data, error } = await getSupabaseClient().from("workspaces").select("slug, root_path");
|
|
278
|
+
if (error) return;
|
|
279
|
+
workspaceRoots = new Map(
|
|
280
|
+
(data ?? []).filter((w) => w.root_path).map((w) => [w.slug, expandPath(w.root_path)])
|
|
281
|
+
);
|
|
282
|
+
}
|
|
275
283
|
function resolveLogicalCwd(p) {
|
|
276
|
-
|
|
284
|
+
const [slug, name] = p.split("/");
|
|
285
|
+
const root = workspaceRoots.get(slug);
|
|
286
|
+
return root ? resolve2(root, name) : resolve2(logicalRoot(), p);
|
|
277
287
|
}
|
|
278
288
|
function ensureLogicalCwdDir(cwd) {
|
|
279
|
-
|
|
289
|
+
const roots = [logicalRoot(), ...workspaceRoots.values()];
|
|
290
|
+
if (!roots.some((r) => cwd.startsWith(r + sep)) || existsSync2(cwd)) return;
|
|
280
291
|
mkdirSync(cwd, { recursive: true });
|
|
281
292
|
}
|
|
282
293
|
function expandPath(p) {
|
|
@@ -358,6 +369,9 @@ function indexRegistry(list) {
|
|
|
358
369
|
);
|
|
359
370
|
}
|
|
360
371
|
async function refreshChannelRegistry() {
|
|
372
|
+
const rootsBefore = JSON.stringify([...workspaceRoots]);
|
|
373
|
+
await refreshWorkspaceRoots();
|
|
374
|
+
if (JSON.stringify([...workspaceRoots]) !== rootsBefore) lastSnapshot2 = void 0;
|
|
361
375
|
const { data, error } = await getSupabaseClient().from("channels").select("*").order("position", { ascending: true });
|
|
362
376
|
if (error) {
|
|
363
377
|
console.error("[channels] refreshChannelRegistry failed:", error.message);
|
|
@@ -522,7 +536,44 @@ function toRow(input) {
|
|
|
522
536
|
if (input.archivedAt !== void 0) row["archived_at"] = input.archivedAt;
|
|
523
537
|
return row;
|
|
524
538
|
}
|
|
539
|
+
var ChannelCwdError = class extends Error {
|
|
540
|
+
constructor(message) {
|
|
541
|
+
super(message);
|
|
542
|
+
this.name = "ChannelCwdError";
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
function realpathOrSelf(p) {
|
|
546
|
+
try {
|
|
547
|
+
return realpathSync(p);
|
|
548
|
+
} catch {
|
|
549
|
+
return p;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async function assertCwdInWorkspace(profile, cwd) {
|
|
553
|
+
if (isLogicalCwd(cwd)) {
|
|
554
|
+
if (cwd.split("/")[0] !== profile) {
|
|
555
|
+
throw new ChannelCwdError(`Folder "${cwd}" belongs to another workspace.`);
|
|
556
|
+
}
|
|
557
|
+
return cwd;
|
|
558
|
+
}
|
|
559
|
+
const { data, error } = await getSupabaseClient().from("workspaces").select("slug, root_path");
|
|
560
|
+
if (error) throw new Error(error.message);
|
|
561
|
+
const workspaces = data ?? [];
|
|
562
|
+
const ownRoot = workspaces.find((w) => w.slug === profile)?.root_path;
|
|
563
|
+
const home = ownRoot?.match(/^(\/Users\/[^/]+|\/home\/[^/]+|\/root)(?=\/|$)/)?.[1];
|
|
564
|
+
const absolute = home && (cwd === "~" || cwd.startsWith("~/")) ? resolve2(home, cwd.slice(2)) : expandPath(cwd);
|
|
565
|
+
const path = realpathOrSelf(absolute);
|
|
566
|
+
const roots = workspaces.filter((w) => w.root_path).map((w) => ({ slug: w.slug, root: realpathOrSelf(expandPath(w.root_path)) }));
|
|
567
|
+
const owner = roots.filter(({ root }) => path === root || path.startsWith(root + sep)).sort((a, b) => b.root.length - a.root.length)[0];
|
|
568
|
+
if (owner ? owner.slug !== profile : roots.some((r) => r.slug === profile)) {
|
|
569
|
+
throw new ChannelCwdError(
|
|
570
|
+
owner ? `Folder "${cwd}" belongs to the "${owner.slug}" workspace.` : `Folder "${cwd}" is outside this workspace's folder.`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
return absolute;
|
|
574
|
+
}
|
|
525
575
|
async function createChannel(input) {
|
|
576
|
+
input = { ...input, cwd: await assertCwdInWorkspace(getProfile(), input.cwd) };
|
|
526
577
|
const { data, error } = await getSupabaseClient().from("channels").insert({ ...toRow(input), profile: getProfile() }).select().single();
|
|
527
578
|
if (error) throw new Error(error.message);
|
|
528
579
|
const channel = rowToChannel(data);
|
|
@@ -547,6 +598,9 @@ async function updateChannelById(id, patch) {
|
|
|
547
598
|
if (existing?.isDefault && (patch.visibility === "private" || patch.archivedAt || patch.isDefault === false || patch.name !== void 0 && patch.name.trim() !== existing.name)) {
|
|
548
599
|
throw new DefaultChannelError();
|
|
549
600
|
}
|
|
601
|
+
if (patch.cwd !== void 0) {
|
|
602
|
+
patch = { ...patch, cwd: await assertCwdInWorkspace(existing?.profile ?? getProfile(), patch.cwd) };
|
|
603
|
+
}
|
|
550
604
|
const { data, error } = await getSupabaseClient().from("channels").update({ ...toRow(patch), updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id).select().single();
|
|
551
605
|
if (error) throw new Error(error.message);
|
|
552
606
|
await refreshChannelRegistry();
|
|
@@ -1543,9 +1597,9 @@ function isAuthError(text) {
|
|
|
1543
1597
|
}
|
|
1544
1598
|
function authErrorMessage(raw) {
|
|
1545
1599
|
return `The agent can't sign in to Claude right now (the Host's login expired and could not be refreshed).
|
|
1546
|
-
|
|
1600
|
+
To fix it, a workspace admin can open Settings \u2192 Agent \u2192 Providers and click Sign in next to Claude Code.
|
|
1547
1601
|
|
|
1548
|
-
Original error: ${raw}`;
|
|
1602
|
+
> Original error: ${raw}`;
|
|
1549
1603
|
}
|
|
1550
1604
|
function isTransientError(text, apiErrorStatus) {
|
|
1551
1605
|
if (apiErrorStatus == null) {
|
|
@@ -2111,7 +2165,7 @@ var featureFlags = {
|
|
|
2111
2165
|
dictation: {
|
|
2112
2166
|
title: "Dictation",
|
|
2113
2167
|
description: "A mic button in the message composer that turns speech into text as you talk. Each phrase uses speech-to-text.",
|
|
2114
|
-
stage: "
|
|
2168
|
+
stage: "beta"
|
|
2115
2169
|
},
|
|
2116
2170
|
"agent-memory": {
|
|
2117
2171
|
title: "Agent memory",
|
|
@@ -2161,6 +2215,7 @@ async function disableFeatureFlag(workspaceId, flagId) {
|
|
|
2161
2215
|
|
|
2162
2216
|
// src/store/memory-store.ts
|
|
2163
2217
|
var MEMORY_TYPES = ["preference", "decision", "fact", "reference"];
|
|
2218
|
+
var channelTarget = (channelId) => ({ scope: "channel", channelId });
|
|
2164
2219
|
var NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2165
2220
|
var DESCRIPTION_MAX = 200;
|
|
2166
2221
|
var BODY_MAX = 16e3;
|
|
@@ -2184,7 +2239,10 @@ function validateMemoryInput(input) {
|
|
|
2184
2239
|
function rowToMemory(row) {
|
|
2185
2240
|
return {
|
|
2186
2241
|
id: row["id"],
|
|
2187
|
-
|
|
2242
|
+
scope: row["scope"] ?? "channel",
|
|
2243
|
+
workspaceId: row["workspace_id"],
|
|
2244
|
+
channelId: row["channel_id"] ?? null,
|
|
2245
|
+
userId: row["user_id"] ?? null,
|
|
2188
2246
|
name: row["name"],
|
|
2189
2247
|
description: row["description"],
|
|
2190
2248
|
type: row["type"],
|
|
@@ -2192,23 +2250,40 @@ function rowToMemory(row) {
|
|
|
2192
2250
|
sourceThreadId: row["source_thread_id"] ?? null,
|
|
2193
2251
|
createdBy: row["created_by"],
|
|
2194
2252
|
createdAt: row["created_at"],
|
|
2195
|
-
updatedAt: row["updated_at"]
|
|
2253
|
+
updatedAt: row["updated_at"],
|
|
2254
|
+
lastUsedAt: row["last_used_at"] ?? null
|
|
2196
2255
|
};
|
|
2197
2256
|
}
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2257
|
+
function targetRow(target) {
|
|
2258
|
+
switch (target.scope) {
|
|
2259
|
+
case "channel":
|
|
2260
|
+
return { scope: "channel", channel_id: target.channelId };
|
|
2261
|
+
case "workspace":
|
|
2262
|
+
return { scope: "workspace", workspace_id: target.workspaceId };
|
|
2263
|
+
case "user":
|
|
2264
|
+
return { scope: "user", workspace_id: target.workspaceId, user_id: target.userId };
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
function inTarget(query, target) {
|
|
2268
|
+
let q = query;
|
|
2269
|
+
for (const [column, value] of Object.entries(targetRow(target))) q = q.eq(column, value);
|
|
2270
|
+
return q;
|
|
2271
|
+
}
|
|
2272
|
+
var describeTarget = (t) => t.scope === "channel" ? t.channelId : t.scope === "workspace" ? `workspace ${t.workspaceId}` : `user ${t.userId}`;
|
|
2273
|
+
async function listMemories(target) {
|
|
2274
|
+
const { data, error } = await inTarget(getSupabaseClient().from("memories").select("*"), target).order("updated_at", { ascending: false });
|
|
2275
|
+
if (error) throw new Error(`listMemories(${describeTarget(target)}): ${error.message}`);
|
|
2201
2276
|
return (data ?? []).map(rowToMemory);
|
|
2202
2277
|
}
|
|
2203
|
-
async function
|
|
2204
|
-
const { data, error } = await getSupabaseClient().from("memories").select("*")
|
|
2205
|
-
if (error) throw new Error(`
|
|
2278
|
+
async function getMemory(target, name) {
|
|
2279
|
+
const { data, error } = await inTarget(getSupabaseClient().from("memories").select("*"), target).eq("name", name).maybeSingle();
|
|
2280
|
+
if (error) throw new Error(`getMemory(${describeTarget(target)}, ${name}): ${error.message}`);
|
|
2206
2281
|
return data ? rowToMemory(data) : null;
|
|
2207
2282
|
}
|
|
2208
|
-
async function
|
|
2283
|
+
async function saveMemory(target, input, actor) {
|
|
2209
2284
|
validateMemoryInput(input);
|
|
2210
2285
|
const supabase = getSupabaseClient();
|
|
2211
|
-
const existing = await
|
|
2286
|
+
const existing = await getMemory(target, input.name);
|
|
2212
2287
|
const fields = {
|
|
2213
2288
|
description: input.description.trim(),
|
|
2214
2289
|
type: input.type,
|
|
@@ -2219,9 +2294,9 @@ async function saveChannelMemory(input, actor) {
|
|
|
2219
2294
|
if (existing && sameContent(existing, fields)) {
|
|
2220
2295
|
return { memory: existing, created: false, changed: false };
|
|
2221
2296
|
}
|
|
2222
|
-
const { data, error } = existing ? await supabase.from("memories").update({ ...fields, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", existing.id).select("*").single() : await supabase.from("memories").insert({ ...fields,
|
|
2297
|
+
const { data, error } = existing ? await supabase.from("memories").update({ ...fields, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", existing.id).select("*").single() : await supabase.from("memories").insert({ ...fields, ...targetRow(target), name: input.name }).select("*").single();
|
|
2223
2298
|
if (error)
|
|
2224
|
-
throw new Error(`
|
|
2299
|
+
throw new Error(`saveMemory(${describeTarget(target)}, ${input.name}): ${error.message}`);
|
|
2225
2300
|
const memory = rowToMemory(data);
|
|
2226
2301
|
await logMemoryEvent({
|
|
2227
2302
|
memory,
|
|
@@ -2233,12 +2308,12 @@ async function saveChannelMemory(input, actor) {
|
|
|
2233
2308
|
});
|
|
2234
2309
|
return { memory, created: !existing, changed: true };
|
|
2235
2310
|
}
|
|
2236
|
-
async function
|
|
2237
|
-
const existing = (await
|
|
2311
|
+
async function updateMemory(target, memoryId, patch, actor) {
|
|
2312
|
+
const existing = (await listMemories(target)).find((m) => m.id === memoryId);
|
|
2238
2313
|
if (!existing) return null;
|
|
2239
|
-
const { memory } = await
|
|
2314
|
+
const { memory } = await saveMemory(
|
|
2315
|
+
target,
|
|
2240
2316
|
{
|
|
2241
|
-
channelId,
|
|
2242
2317
|
name: existing.name,
|
|
2243
2318
|
description: patch.description ?? existing.description,
|
|
2244
2319
|
type: patch.type ?? existing.type,
|
|
@@ -2249,20 +2324,28 @@ async function updateChannelMemory(channelId, memoryId, patch, actor) {
|
|
|
2249
2324
|
);
|
|
2250
2325
|
return memory;
|
|
2251
2326
|
}
|
|
2252
|
-
async function
|
|
2327
|
+
async function searchMemories(target, query) {
|
|
2253
2328
|
const words = query.replaceAll(/[,()*%\\]/g, " ").split(/\s+/).filter(Boolean);
|
|
2254
2329
|
if (!words.length) return [];
|
|
2255
|
-
let request = getSupabaseClient().from("memories").select("*")
|
|
2330
|
+
let request = inTarget(getSupabaseClient().from("memories").select("*"), target);
|
|
2256
2331
|
for (const word of words) {
|
|
2257
2332
|
request = request.or(`name.ilike.*${word}*,description.ilike.*${word}*,body.ilike.*${word}*`);
|
|
2258
2333
|
}
|
|
2259
2334
|
const { data, error } = await request.order("updated_at", { ascending: false }).limit(20);
|
|
2260
|
-
if (error) throw new Error(`
|
|
2335
|
+
if (error) throw new Error(`searchMemories(${describeTarget(target)}): ${error.message}`);
|
|
2261
2336
|
return (data ?? []).map(rowToMemory);
|
|
2262
2337
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2338
|
+
var USED_GRANULARITY_MS = 24 * 60 * 6e4;
|
|
2339
|
+
async function markMemoryUsed(memory) {
|
|
2340
|
+
const cutoff = new Date(Date.now() - USED_GRANULARITY_MS).toISOString();
|
|
2341
|
+
if (memory.lastUsedAt && memory.lastUsedAt > cutoff) return;
|
|
2342
|
+
const { error } = await getSupabaseClient().from("memories").update({ last_used_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", memory.id).or(`last_used_at.is.null,last_used_at.lt.${cutoff}`);
|
|
2343
|
+
if (error) console.error(`[memory] markMemoryUsed(${memory.id}) failed:`, error.message);
|
|
2344
|
+
}
|
|
2345
|
+
async function deleteMemory(target, idOrName, actor, threadId) {
|
|
2346
|
+
const { data, error } = await inTarget(getSupabaseClient().from("memories").delete(), target).eq(isUuid(idOrName) ? "id" : "name", idOrName).select("*");
|
|
2347
|
+
if (error)
|
|
2348
|
+
throw new Error(`deleteMemory(${describeTarget(target)}, ${idOrName}): ${error.message}`);
|
|
2266
2349
|
const deleted = (data ?? []).map(rowToMemory);
|
|
2267
2350
|
for (const memory of deleted) {
|
|
2268
2351
|
await logMemoryEvent({
|
|
@@ -2276,6 +2359,25 @@ async function deleteChannelMemory(channelId, idOrName, actor, threadId) {
|
|
|
2276
2359
|
}
|
|
2277
2360
|
return deleted.length > 0;
|
|
2278
2361
|
}
|
|
2362
|
+
async function promoteChannelMemory(channelId, memoryId) {
|
|
2363
|
+
const supabase = getSupabaseClient();
|
|
2364
|
+
const existing = (await listChannelMemories(channelId)).find((m) => m.id === memoryId);
|
|
2365
|
+
if (!existing) return { ok: false, reason: "not-found" };
|
|
2366
|
+
if (await getMemory({ scope: "workspace", workspaceId: existing.workspaceId }, existing.name)) {
|
|
2367
|
+
return { ok: false, reason: "name-taken" };
|
|
2368
|
+
}
|
|
2369
|
+
const { data, error } = await supabase.from("memories").update({ scope: "workspace", channel_id: null, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", memoryId).eq("scope", "channel").select("*").maybeSingle();
|
|
2370
|
+
if (error) throw new Error(`promoteChannelMemory(${memoryId}): ${error.message}`);
|
|
2371
|
+
if (!data) return { ok: false, reason: "not-found" };
|
|
2372
|
+
const memory = rowToMemory(data);
|
|
2373
|
+
await logMemoryEvent({ memory, action: "create", before: null, after: snapshot(memory), actor: "user", threadId: null });
|
|
2374
|
+
return { ok: true, memory };
|
|
2375
|
+
}
|
|
2376
|
+
var listChannelMemories = (channelId) => listMemories(channelTarget(channelId));
|
|
2377
|
+
var getChannelMemory = (channelId, name) => getMemory(channelTarget(channelId), name);
|
|
2378
|
+
var saveChannelMemory = ({ channelId, ...fields }, actor) => saveMemory(channelTarget(channelId), fields, actor);
|
|
2379
|
+
var updateChannelMemory = (channelId, memoryId, patch, actor) => updateMemory(channelTarget(channelId), memoryId, patch, actor);
|
|
2380
|
+
var deleteChannelMemory = (channelId, idOrName, actor, threadId) => deleteMemory(channelTarget(channelId), idOrName, actor, threadId);
|
|
2279
2381
|
function unchangedSince(current, after) {
|
|
2280
2382
|
return current && after ? sameContent(current, after) : !current && !after;
|
|
2281
2383
|
}
|
|
@@ -2294,6 +2396,10 @@ function sameContent(m, other) {
|
|
|
2294
2396
|
async function logMemoryEvent(e) {
|
|
2295
2397
|
const { error } = await getSupabaseClient().from("memory_events").insert({
|
|
2296
2398
|
channel_id: e.memory.channelId,
|
|
2399
|
+
// A channel event's workspace comes from its channel (0100 trigger).
|
|
2400
|
+
...e.memory.channelId ? {} : { workspace_id: e.memory.workspaceId },
|
|
2401
|
+
scope: e.memory.scope,
|
|
2402
|
+
user_id: e.memory.userId,
|
|
2297
2403
|
memory_id: e.action === "delete" ? null : e.memory.id,
|
|
2298
2404
|
thread_id: e.threadId,
|
|
2299
2405
|
name: e.memory.name,
|
|
@@ -2319,15 +2425,18 @@ function rowToEvent(row) {
|
|
|
2319
2425
|
undoneAt: row["undone_at"] ?? null
|
|
2320
2426
|
};
|
|
2321
2427
|
}
|
|
2322
|
-
async function
|
|
2428
|
+
async function listMemoryEvents(channelId, filter = {}) {
|
|
2323
2429
|
const supabase = getSupabaseClient();
|
|
2324
|
-
|
|
2325
|
-
if (
|
|
2430
|
+
let query = supabase.from("memory_events").select("*").eq("channel_id", channelId);
|
|
2431
|
+
if (filter.threadId) query = query.eq("thread_id", filter.threadId);
|
|
2432
|
+
if (filter.actor) query = query.eq("actor", filter.actor);
|
|
2433
|
+
const { data, error } = await query.order("created_at", { ascending: false }).limit(filter.limit ?? 50);
|
|
2434
|
+
if (error) throw new Error(`listMemoryEvents(${channelId}): ${error.message}`);
|
|
2326
2435
|
const events = (data ?? []).map(rowToEvent);
|
|
2327
2436
|
if (!events.length) return [];
|
|
2328
2437
|
const names = [...new Set(events.map((e) => e.name))];
|
|
2329
2438
|
const live = await supabase.from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId).in("name", names);
|
|
2330
|
-
if (live.error) throw new Error(`
|
|
2439
|
+
if (live.error) throw new Error(`listMemoryEvents(${channelId}): ${live.error.message}`);
|
|
2331
2440
|
const byName = new Map(
|
|
2332
2441
|
(live.data ?? []).map((r) => [
|
|
2333
2442
|
r["name"],
|
|
@@ -2344,6 +2453,7 @@ async function listThreadMemoryEvents(channelId, threadId) {
|
|
|
2344
2453
|
};
|
|
2345
2454
|
});
|
|
2346
2455
|
}
|
|
2456
|
+
var listThreadMemoryEvents = (channelId, threadId) => listMemoryEvents(channelId, { threadId, actor: "agent" });
|
|
2347
2457
|
async function undoMemoryEvent(channelId, eventId) {
|
|
2348
2458
|
const supabase = getSupabaseClient();
|
|
2349
2459
|
const { data, error } = await supabase.from("memory_events").select("*").eq("id", eventId).eq("channel_id", channelId).maybeSingle();
|
|
@@ -2374,21 +2484,29 @@ async function undoMemoryEvent(channelId, eventId) {
|
|
|
2374
2484
|
function isUuid(value) {
|
|
2375
2485
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
2376
2486
|
}
|
|
2377
|
-
function formatMemoryPrompt(channelId, memories) {
|
|
2378
|
-
const lines = [];
|
|
2487
|
+
function formatMemoryPrompt(channelId, memories, extras = {}) {
|
|
2379
2488
|
let used = 0;
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2489
|
+
const indexOf = (list, searchHint) => {
|
|
2490
|
+
const lines = [];
|
|
2491
|
+
let omitted = 0;
|
|
2492
|
+
for (const m of list) {
|
|
2493
|
+
const line = `- \`${m.name}\` (${m.type}): ${m.description}`;
|
|
2494
|
+
if (used + line.length > MEMORY_INDEX_MAX_CHARS) {
|
|
2495
|
+
omitted++;
|
|
2496
|
+
continue;
|
|
2497
|
+
}
|
|
2498
|
+
lines.push(line);
|
|
2499
|
+
used += line.length + 1;
|
|
2386
2500
|
}
|
|
2387
|
-
lines.
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2501
|
+
if (!lines.length && !omitted) return "(none yet)";
|
|
2502
|
+
return lines.join("\n") + (omitted ? `
|
|
2503
|
+
- (${omitted} older memories not listed; find them with \`${searchHint}\`)` : "");
|
|
2504
|
+
};
|
|
2505
|
+
const user = extras.user;
|
|
2506
|
+
const userFlags2 = user ? `--scope user --user ${user.userId}` : "";
|
|
2507
|
+
const userIndex = user ? indexOf(user.memories, `memory search --channel ${channelId} ${userFlags2}`) : "";
|
|
2508
|
+
const channelIndex = indexOf(memories, "memory search");
|
|
2509
|
+
const workspaceIndex = extras.workspace?.length ? indexOf(extras.workspace, `memory search --channel ${channelId} --scope workspace`) : "";
|
|
2392
2510
|
return [
|
|
2393
2511
|
`## Channel memory`,
|
|
2394
2512
|
``,
|
|
@@ -2400,26 +2518,22 @@ function formatMemoryPrompt(channelId, memories) {
|
|
|
2400
2518
|
`- Delete one that turned out wrong: \`flyspace memory delete --channel ${channelId} <name> [--thread <thread-id>]\``,
|
|
2401
2519
|
``,
|
|
2402
2520
|
`Save when someone asks you to remember something, corrects you, or a decision is made that later threads will need. Save what is not derivable from the code, git history or docs: decisions and why, preferences, corrections, where external things live. Do not save one-off details of this conversation. Check the index first and update an existing memory rather than adding a near-duplicate. Write memories in English. Pass \`--thread\` with the thread id from the \`[context: ...]\` header, so the thread shows what changed and it can be undone there.`,
|
|
2521
|
+
...user ? [
|
|
2522
|
+
``,
|
|
2523
|
+
`A personal preference of @${user.handle} (language, tone, reply length, time zone, how they like to work) belongs in their personal memory instead, which only their own turns see: add \`${userFlags2}\` to the same commands. Never save someone else's preferences there, and never put project knowledge there.`
|
|
2524
|
+
] : [],
|
|
2525
|
+
...workspaceIndex ? [
|
|
2526
|
+
``,
|
|
2527
|
+
`Workspace memories below apply to every channel. They are read-only to you: read one with \`--scope workspace\` on \`get\`; a Workspace Admin manages them.`
|
|
2528
|
+
] : [],
|
|
2403
2529
|
``,
|
|
2530
|
+
...user ? [`Personal memory of @${user.handle}:`, userIndex, ``] : [],
|
|
2404
2531
|
`Index:`,
|
|
2405
|
-
|
|
2532
|
+
channelIndex,
|
|
2533
|
+
...workspaceIndex ? [``, `Workspace memory:`, workspaceIndex] : []
|
|
2406
2534
|
].join("\n");
|
|
2407
2535
|
}
|
|
2408
2536
|
|
|
2409
|
-
// src/store/memory-context.ts
|
|
2410
|
-
async function loadAgentMemory(profile, channelId, opts = {}) {
|
|
2411
|
-
const enabled = await listEnabledFeatureFlagsBySlug(profile).then((ids) => resolveFeatureFlags(ids, false)["agent-memory"]).catch((err) => {
|
|
2412
|
-
console.error("[memory] feature flag lookup failed:", err.message);
|
|
2413
|
-
return false;
|
|
2414
|
-
});
|
|
2415
|
-
if (!enabled || opts.readable === false || !channelId || !isUuid(channelId)) return { enabled };
|
|
2416
|
-
const memoryPrompt = await listChannelMemories(channelId).then((memories) => formatMemoryPrompt(channelId, memories)).catch((err) => {
|
|
2417
|
-
console.error("[memory] memory lookup failed:", err.message);
|
|
2418
|
-
return void 0;
|
|
2419
|
-
});
|
|
2420
|
-
return memoryPrompt ? { enabled, memoryPrompt } : { enabled };
|
|
2421
|
-
}
|
|
2422
|
-
|
|
2423
2537
|
// src/store/workspace-store.ts
|
|
2424
2538
|
function rowToMembership(row) {
|
|
2425
2539
|
return {
|
|
@@ -2431,7 +2545,8 @@ function rowToMembership(row) {
|
|
|
2431
2545
|
role: row["role"],
|
|
2432
2546
|
status: row["status"],
|
|
2433
2547
|
invitedAt: row["invited_at"],
|
|
2434
|
-
acceptedAt: row["accepted_at"] ?? null
|
|
2548
|
+
acceptedAt: row["accepted_at"] ?? null,
|
|
2549
|
+
memoryAutoPaused: row["memory_auto_paused"] === true
|
|
2435
2550
|
};
|
|
2436
2551
|
}
|
|
2437
2552
|
var WORKSPACE_CACHE_TTL_MS = 6e4;
|
|
@@ -2587,6 +2702,10 @@ async function listMembershipsByEmail(email) {
|
|
|
2587
2702
|
if (error) throw new Error(`listMembershipsByEmail: ${error.message}`);
|
|
2588
2703
|
return (data ?? []).map(rowToMembership);
|
|
2589
2704
|
}
|
|
2705
|
+
async function setMemoryAutoPaused(workspaceId, userId, paused) {
|
|
2706
|
+
const { error } = await getSupabaseClient().from("memberships").update({ memory_auto_paused: paused }).eq("workspace_id", workspaceId).eq("user_id", userId);
|
|
2707
|
+
if (error) throw new Error(`setMemoryAutoPaused(${workspaceId}): ${error.message}`);
|
|
2708
|
+
}
|
|
2590
2709
|
async function listMemberships(workspaceId) {
|
|
2591
2710
|
const { data, error } = await getSupabaseClient().from("memberships").select("*").eq("workspace_id", workspaceId).order("invited_at", { ascending: true });
|
|
2592
2711
|
if (error) throw new Error(`listMemberships(${workspaceId}): ${error.message}`);
|
|
@@ -2710,6 +2829,37 @@ async function setAgentPaused(workspaceId, paused) {
|
|
|
2710
2829
|
if (error) throw new Error(`setAgentPaused(${workspaceId}): ${error.message}`);
|
|
2711
2830
|
}
|
|
2712
2831
|
|
|
2832
|
+
// src/store/memory-context.ts
|
|
2833
|
+
async function loadAgentMemory(profile, channelId, opts = {}) {
|
|
2834
|
+
const enabled = await listEnabledFeatureFlagsBySlug(profile).then((ids) => resolveFeatureFlags(ids, false)["agent-memory"]).catch((err) => {
|
|
2835
|
+
console.error("[memory] feature flag lookup failed:", err.message);
|
|
2836
|
+
return false;
|
|
2837
|
+
});
|
|
2838
|
+
if (!enabled || opts.readable === false || !channelId || !isUuid(channelId)) return { enabled };
|
|
2839
|
+
const memoryPrompt = await buildPrompt(profile, channelId, opts).catch((err) => {
|
|
2840
|
+
console.error("[memory] memory lookup failed:", err.message);
|
|
2841
|
+
return void 0;
|
|
2842
|
+
});
|
|
2843
|
+
return memoryPrompt ? { enabled, memoryPrompt } : { enabled };
|
|
2844
|
+
}
|
|
2845
|
+
async function buildPrompt(profile, channelId, opts) {
|
|
2846
|
+
const workspace = await getWorkspaceBySlug(profile);
|
|
2847
|
+
const handle = opts.senderHandle?.toLowerCase();
|
|
2848
|
+
const sender = workspace && handle ? (await listMemberships(workspace.id)).find(
|
|
2849
|
+
(m) => m.userId && m.handle.toLowerCase() === handle
|
|
2850
|
+
) : void 0;
|
|
2851
|
+
const [channel, shared, own] = await Promise.all([
|
|
2852
|
+
listMemories({ scope: "channel", channelId }),
|
|
2853
|
+
workspace ? listMemories({ scope: "workspace", workspaceId: workspace.id }) : [],
|
|
2854
|
+
workspace && sender ? listMemories({ scope: "user", workspaceId: workspace.id, userId: sender.userId }) : []
|
|
2855
|
+
]);
|
|
2856
|
+
const extras = {
|
|
2857
|
+
workspace: shared,
|
|
2858
|
+
...sender ? { user: { userId: sender.userId, handle: sender.handle, memories: own } } : {}
|
|
2859
|
+
};
|
|
2860
|
+
return formatMemoryPrompt(channelId, channel, extras);
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2713
2863
|
// src/dashboard/settings.ts
|
|
2714
2864
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
|
|
2715
2865
|
import { dirname, resolve as resolve5 } from "path";
|
|
@@ -4427,6 +4577,11 @@ import { CronExpressionParser } from "cron-parser";
|
|
|
4427
4577
|
var Scheduler = class {
|
|
4428
4578
|
jobs = /* @__PURE__ */ new Map();
|
|
4429
4579
|
caughtUp = /* @__PURE__ */ new Set();
|
|
4580
|
+
// A run is only recorded when it finishes, so while one is in flight the
|
|
4581
|
+
// store still shows the previous tick. Without this guard, a registry
|
|
4582
|
+
// reload during a long run re-registers the schedule and catchup starts a
|
|
4583
|
+
// second, overlapping copy (sah-budget-fetch ran twice a day this way).
|
|
4584
|
+
running = /* @__PURE__ */ new Set();
|
|
4430
4585
|
store;
|
|
4431
4586
|
constructor(store) {
|
|
4432
4587
|
this.store = store;
|
|
@@ -4478,6 +4633,11 @@ var Scheduler = class {
|
|
|
4478
4633
|
wrappedRun();
|
|
4479
4634
|
}
|
|
4480
4635
|
async runAndRecord(name, runFn) {
|
|
4636
|
+
if (this.running.has(name)) {
|
|
4637
|
+
console.log(`[scheduler] ${name} is still running, skipping overlapping run`);
|
|
4638
|
+
return;
|
|
4639
|
+
}
|
|
4640
|
+
this.running.add(name);
|
|
4481
4641
|
const startedAt = /* @__PURE__ */ new Date();
|
|
4482
4642
|
const elapsed = () => Date.now() - startedAt.getTime();
|
|
4483
4643
|
try {
|
|
@@ -4486,6 +4646,8 @@ var Scheduler = class {
|
|
|
4486
4646
|
} catch (err) {
|
|
4487
4647
|
console.error(`[scheduler] Error in ${name}:`, err);
|
|
4488
4648
|
this.store?.recordRun(name, "error", startedAt, elapsed());
|
|
4649
|
+
} finally {
|
|
4650
|
+
this.running.delete(name);
|
|
4489
4651
|
}
|
|
4490
4652
|
}
|
|
4491
4653
|
unregister(name) {
|
|
@@ -8718,6 +8880,7 @@ export {
|
|
|
8718
8880
|
warnOnBrokenChannelCwds,
|
|
8719
8881
|
resolveCwd,
|
|
8720
8882
|
resolveChannelPrompt,
|
|
8883
|
+
ChannelCwdError,
|
|
8721
8884
|
createChannel,
|
|
8722
8885
|
DefaultChannelError,
|
|
8723
8886
|
updateChannelById,
|
|
@@ -8745,6 +8908,7 @@ export {
|
|
|
8745
8908
|
modelLabel,
|
|
8746
8909
|
usageLimitedUntil,
|
|
8747
8910
|
usageLimitedModels,
|
|
8911
|
+
clearClaudeAuthFailed,
|
|
8748
8912
|
claudeAuthFailed,
|
|
8749
8913
|
buildSystemPrompt,
|
|
8750
8914
|
docsAccessPromptNote,
|
|
@@ -8761,16 +8925,23 @@ export {
|
|
|
8761
8925
|
MEMORY_TYPES,
|
|
8762
8926
|
isMemoryType,
|
|
8763
8927
|
validateMemoryInput,
|
|
8928
|
+
listMemories,
|
|
8929
|
+
getMemory,
|
|
8930
|
+
saveMemory,
|
|
8931
|
+
updateMemory,
|
|
8932
|
+
searchMemories,
|
|
8933
|
+
markMemoryUsed,
|
|
8934
|
+
deleteMemory,
|
|
8935
|
+
promoteChannelMemory,
|
|
8764
8936
|
listChannelMemories,
|
|
8765
8937
|
getChannelMemory,
|
|
8766
8938
|
saveChannelMemory,
|
|
8767
8939
|
updateChannelMemory,
|
|
8768
|
-
searchChannelMemories,
|
|
8769
8940
|
deleteChannelMemory,
|
|
8941
|
+
listMemoryEvents,
|
|
8770
8942
|
listThreadMemoryEvents,
|
|
8771
8943
|
undoMemoryEvent,
|
|
8772
8944
|
isUuid,
|
|
8773
|
-
loadAgentMemory,
|
|
8774
8945
|
getWorkspaceBySlug,
|
|
8775
8946
|
getWorkspaceById,
|
|
8776
8947
|
listWorkspaces,
|
|
@@ -8790,6 +8961,7 @@ export {
|
|
|
8790
8961
|
getMembershipByEmail,
|
|
8791
8962
|
getMembershipByUserId,
|
|
8792
8963
|
listMembershipsByEmail,
|
|
8964
|
+
setMemoryAutoPaused,
|
|
8793
8965
|
listMemberships,
|
|
8794
8966
|
createMembership,
|
|
8795
8967
|
HANDLE_PATTERN,
|
|
@@ -8806,6 +8978,7 @@ export {
|
|
|
8806
8978
|
setAllowGuestAccess,
|
|
8807
8979
|
getAgentPaused,
|
|
8808
8980
|
setAgentPaused,
|
|
8981
|
+
loadAgentMemory,
|
|
8809
8982
|
DEFAULT_TITLE_RULES,
|
|
8810
8983
|
extractCategories,
|
|
8811
8984
|
buildAutoTitle,
|