flyspace-cli 0.1.13 → 0.1.15
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-VKTJBYA3.js → chunk-KFT2YP5W.js} +386 -13
- package/dist/cli.js +925 -961
- package/dist/index.js +1 -1
- package/package.json +3 -1
- package/templates/general/01-welcome.md +20 -0
- package/templates/general/02-working-with-the-agent.md +21 -0
- package/templates/general/03-channels-and-folders.md +19 -0
- package/templates/general/04-docs.md +14 -0
- package/templates/general/05-inviting-your-team.md +17 -0
- package/templates/general/06-automations.md +21 -0
|
@@ -163,9 +163,9 @@ async function refreshAutomationRegistry() {
|
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
165
|
const next = data ?? [];
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
168
|
-
lastSnapshot =
|
|
166
|
+
const snapshot2 = JSON.stringify(next, (key, value) => key === "updated_at" ? void 0 : value);
|
|
167
|
+
if (snapshot2 === lastSnapshot) return;
|
|
168
|
+
lastSnapshot = snapshot2;
|
|
169
169
|
rows = next;
|
|
170
170
|
for (const cb of changeListeners) {
|
|
171
171
|
try {
|
|
@@ -364,12 +364,12 @@ async function refreshChannelRegistry() {
|
|
|
364
364
|
return;
|
|
365
365
|
}
|
|
366
366
|
const rows2 = data ?? [];
|
|
367
|
-
const
|
|
367
|
+
const snapshot2 = JSON.stringify(
|
|
368
368
|
rows2,
|
|
369
369
|
(key, value) => key === "updated_at" || key === "created_at" ? void 0 : value
|
|
370
370
|
);
|
|
371
|
-
if (
|
|
372
|
-
lastSnapshot2 =
|
|
371
|
+
if (snapshot2 === lastSnapshot2) return;
|
|
372
|
+
lastSnapshot2 = snapshot2;
|
|
373
373
|
indexRegistry(rows2.map(rowToChannel));
|
|
374
374
|
for (const cb of changeListeners2) {
|
|
375
375
|
try {
|
|
@@ -2086,6 +2086,340 @@ Fix the channel's working directory in its settings (or recreate the directory),
|
|
|
2086
2086
|
}
|
|
2087
2087
|
}
|
|
2088
2088
|
|
|
2089
|
+
// src/feature-flags.ts
|
|
2090
|
+
var featureFlags = {
|
|
2091
|
+
"ai-cost": {
|
|
2092
|
+
title: "AI cost",
|
|
2093
|
+
description: "Shows admins what the agent has spent: a Cost section in a channel's AI overview and a per-thread cost in the thread menu. Estimated at API list rates, so on a Claude subscription it will not match your bill.",
|
|
2094
|
+
stage: "beta"
|
|
2095
|
+
},
|
|
2096
|
+
"read-aloud": {
|
|
2097
|
+
title: "Read aloud",
|
|
2098
|
+
description: "Listen to messages, threads and docs as audio. Still rough in places, and each playback uses text-to-speech.",
|
|
2099
|
+
stage: "beta"
|
|
2100
|
+
},
|
|
2101
|
+
excalidraw: {
|
|
2102
|
+
title: "Excalidraw",
|
|
2103
|
+
description: "Create and edit whiteboard drawings (.excalidraw files) in the Docs pane. Existing drawings stay viewable with this off.",
|
|
2104
|
+
stage: "beta"
|
|
2105
|
+
},
|
|
2106
|
+
sandbox: {
|
|
2107
|
+
title: "Sandbox runtime",
|
|
2108
|
+
description: "Run a channel agent in an isolated sandbox without Docker. Channels already on Sandbox keep working with this off.",
|
|
2109
|
+
stage: "beta"
|
|
2110
|
+
},
|
|
2111
|
+
dictation: {
|
|
2112
|
+
title: "Dictation",
|
|
2113
|
+
description: "A mic button in the message composer that turns speech into text as you talk. Each phrase uses speech-to-text.",
|
|
2114
|
+
stage: "dev"
|
|
2115
|
+
},
|
|
2116
|
+
"agent-memory": {
|
|
2117
|
+
title: "Agent memory",
|
|
2118
|
+
description: "Channel memory the agent reads every turn, saved by the agent and picked up from idle threads, with a Memory tab and an undo chip. While on, Claude's built-in auto memory is switched off so the two can be compared; turning this off keeps every memory.",
|
|
2119
|
+
stage: "beta"
|
|
2120
|
+
}
|
|
2121
|
+
};
|
|
2122
|
+
var featureFlagIds = Object.keys(featureFlags);
|
|
2123
|
+
function isFeatureFlagId(id) {
|
|
2124
|
+
return Object.hasOwn(featureFlags, id);
|
|
2125
|
+
}
|
|
2126
|
+
function isFeatureVisible(stage, devHost) {
|
|
2127
|
+
return stage === "beta" || stage === "dev" && devHost;
|
|
2128
|
+
}
|
|
2129
|
+
function resolveFeatureFlags(enabledIds, devHost) {
|
|
2130
|
+
const enabled = new Set(enabledIds);
|
|
2131
|
+
const out = {};
|
|
2132
|
+
for (const id of featureFlagIds) {
|
|
2133
|
+
const stage = featureFlags[id].stage;
|
|
2134
|
+
out[id] = stage === "ga" || isFeatureVisible(stage, devHost) && enabled.has(id);
|
|
2135
|
+
}
|
|
2136
|
+
return out;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
// src/store/feature-flag-store.ts
|
|
2140
|
+
async function listEnabledFeatureFlags(workspaceId) {
|
|
2141
|
+
const { data, error } = await getSupabaseClient().from("feature_flags").select("flag_id").eq("workspace_id", workspaceId);
|
|
2142
|
+
if (error) throw new Error(`listEnabledFeatureFlags(${workspaceId}): ${error.message}`);
|
|
2143
|
+
return (data ?? []).map((r) => r.flag_id);
|
|
2144
|
+
}
|
|
2145
|
+
async function listEnabledFeatureFlagsBySlug(slug) {
|
|
2146
|
+
const { data, error } = await getSupabaseClient().from("workspaces").select("id").eq("slug", slug).maybeSingle();
|
|
2147
|
+
if (error) throw new Error(`listEnabledFeatureFlagsBySlug(${slug}): ${error.message}`);
|
|
2148
|
+
return data ? listEnabledFeatureFlags(data.id) : [];
|
|
2149
|
+
}
|
|
2150
|
+
async function enableFeatureFlag(workspaceId, flagId, userId) {
|
|
2151
|
+
const { error } = await getSupabaseClient().from("feature_flags").upsert(
|
|
2152
|
+
{ workspace_id: workspaceId, flag_id: flagId, enabled_by: userId },
|
|
2153
|
+
{ onConflict: "workspace_id,flag_id" }
|
|
2154
|
+
);
|
|
2155
|
+
if (error) throw new Error(`enableFeatureFlag(${workspaceId}, ${flagId}): ${error.message}`);
|
|
2156
|
+
}
|
|
2157
|
+
async function disableFeatureFlag(workspaceId, flagId) {
|
|
2158
|
+
const { error } = await getSupabaseClient().from("feature_flags").delete().eq("workspace_id", workspaceId).eq("flag_id", flagId);
|
|
2159
|
+
if (error) throw new Error(`disableFeatureFlag(${workspaceId}, ${flagId}): ${error.message}`);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// src/store/memory-store.ts
|
|
2163
|
+
var MEMORY_TYPES = ["preference", "decision", "fact", "reference"];
|
|
2164
|
+
var NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2165
|
+
var DESCRIPTION_MAX = 200;
|
|
2166
|
+
var BODY_MAX = 16e3;
|
|
2167
|
+
var MEMORY_INDEX_MAX_CHARS = 8e3;
|
|
2168
|
+
function isMemoryType(value) {
|
|
2169
|
+
return MEMORY_TYPES.includes(value);
|
|
2170
|
+
}
|
|
2171
|
+
function validateMemoryInput(input) {
|
|
2172
|
+
if (!NAME_RE.test(input.name))
|
|
2173
|
+
throw new Error("name must be a kebab-case slug, e.g. deploy-flow");
|
|
2174
|
+
if (!isMemoryType(input.type)) throw new Error(`type must be one of ${MEMORY_TYPES.join(", ")}`);
|
|
2175
|
+
const description = input.description.trim();
|
|
2176
|
+
if (!description || description.includes("\n") || description.length > DESCRIPTION_MAX) {
|
|
2177
|
+
throw new Error(`description must be one line of at most ${DESCRIPTION_MAX} characters`);
|
|
2178
|
+
}
|
|
2179
|
+
const body = input.body.trim();
|
|
2180
|
+
if (!body || body.length > BODY_MAX) {
|
|
2181
|
+
throw new Error(`body must be 1 to ${BODY_MAX} characters`);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
function rowToMemory(row) {
|
|
2185
|
+
return {
|
|
2186
|
+
id: row["id"],
|
|
2187
|
+
channelId: row["channel_id"],
|
|
2188
|
+
name: row["name"],
|
|
2189
|
+
description: row["description"],
|
|
2190
|
+
type: row["type"],
|
|
2191
|
+
body: row["body"],
|
|
2192
|
+
sourceThreadId: row["source_thread_id"] ?? null,
|
|
2193
|
+
createdBy: row["created_by"],
|
|
2194
|
+
createdAt: row["created_at"],
|
|
2195
|
+
updatedAt: row["updated_at"]
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
async function listChannelMemories(channelId) {
|
|
2199
|
+
const { data, error } = await getSupabaseClient().from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId).order("updated_at", { ascending: false });
|
|
2200
|
+
if (error) throw new Error(`listChannelMemories(${channelId}): ${error.message}`);
|
|
2201
|
+
return (data ?? []).map(rowToMemory);
|
|
2202
|
+
}
|
|
2203
|
+
async function getChannelMemory(channelId, name) {
|
|
2204
|
+
const { data, error } = await getSupabaseClient().from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId).eq("name", name).maybeSingle();
|
|
2205
|
+
if (error) throw new Error(`getChannelMemory(${channelId}, ${name}): ${error.message}`);
|
|
2206
|
+
return data ? rowToMemory(data) : null;
|
|
2207
|
+
}
|
|
2208
|
+
async function saveChannelMemory(input, actor) {
|
|
2209
|
+
validateMemoryInput(input);
|
|
2210
|
+
const supabase = getSupabaseClient();
|
|
2211
|
+
const existing = await getChannelMemory(input.channelId, input.name);
|
|
2212
|
+
const fields = {
|
|
2213
|
+
description: input.description.trim(),
|
|
2214
|
+
type: input.type,
|
|
2215
|
+
body: input.body.trim(),
|
|
2216
|
+
source_thread_id: input.sourceThreadId ?? existing?.sourceThreadId ?? null,
|
|
2217
|
+
created_by: input.createdBy
|
|
2218
|
+
};
|
|
2219
|
+
if (existing && sameContent(existing, fields)) {
|
|
2220
|
+
return { memory: existing, created: false, changed: false };
|
|
2221
|
+
}
|
|
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, scope: "channel", channel_id: input.channelId, name: input.name }).select("*").single();
|
|
2223
|
+
if (error)
|
|
2224
|
+
throw new Error(`saveChannelMemory(${input.channelId}, ${input.name}): ${error.message}`);
|
|
2225
|
+
const memory = rowToMemory(data);
|
|
2226
|
+
await logMemoryEvent({
|
|
2227
|
+
memory,
|
|
2228
|
+
action: existing ? "update" : "create",
|
|
2229
|
+
before: existing ? snapshot(existing) : null,
|
|
2230
|
+
after: snapshot(memory),
|
|
2231
|
+
actor,
|
|
2232
|
+
threadId: input.sourceThreadId ?? null
|
|
2233
|
+
});
|
|
2234
|
+
return { memory, created: !existing, changed: true };
|
|
2235
|
+
}
|
|
2236
|
+
async function updateChannelMemory(channelId, memoryId, patch, actor) {
|
|
2237
|
+
const existing = (await listChannelMemories(channelId)).find((m) => m.id === memoryId);
|
|
2238
|
+
if (!existing) return null;
|
|
2239
|
+
const { memory } = await saveChannelMemory(
|
|
2240
|
+
{
|
|
2241
|
+
channelId,
|
|
2242
|
+
name: existing.name,
|
|
2243
|
+
description: patch.description ?? existing.description,
|
|
2244
|
+
type: patch.type ?? existing.type,
|
|
2245
|
+
body: patch.body ?? existing.body,
|
|
2246
|
+
createdBy: existing.createdBy
|
|
2247
|
+
},
|
|
2248
|
+
actor
|
|
2249
|
+
);
|
|
2250
|
+
return memory;
|
|
2251
|
+
}
|
|
2252
|
+
async function searchChannelMemories(channelId, query) {
|
|
2253
|
+
const words = query.replaceAll(/[,()*%\\]/g, " ").split(/\s+/).filter(Boolean);
|
|
2254
|
+
if (!words.length) return [];
|
|
2255
|
+
let request = getSupabaseClient().from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId);
|
|
2256
|
+
for (const word of words) {
|
|
2257
|
+
request = request.or(`name.ilike.*${word}*,description.ilike.*${word}*,body.ilike.*${word}*`);
|
|
2258
|
+
}
|
|
2259
|
+
const { data, error } = await request.order("updated_at", { ascending: false }).limit(20);
|
|
2260
|
+
if (error) throw new Error(`searchChannelMemories(${channelId}): ${error.message}`);
|
|
2261
|
+
return (data ?? []).map(rowToMemory);
|
|
2262
|
+
}
|
|
2263
|
+
async function deleteChannelMemory(channelId, idOrName, actor, threadId) {
|
|
2264
|
+
const { data, error } = await getSupabaseClient().from("memories").delete().eq("scope", "channel").eq("channel_id", channelId).eq(isUuid(idOrName) ? "id" : "name", idOrName).select("*");
|
|
2265
|
+
if (error) throw new Error(`deleteChannelMemory(${channelId}, ${idOrName}): ${error.message}`);
|
|
2266
|
+
const deleted = (data ?? []).map(rowToMemory);
|
|
2267
|
+
for (const memory of deleted) {
|
|
2268
|
+
await logMemoryEvent({
|
|
2269
|
+
memory,
|
|
2270
|
+
action: "delete",
|
|
2271
|
+
before: snapshot(memory),
|
|
2272
|
+
after: null,
|
|
2273
|
+
actor,
|
|
2274
|
+
threadId: threadId ?? null
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
return deleted.length > 0;
|
|
2278
|
+
}
|
|
2279
|
+
function unchangedSince(current, after) {
|
|
2280
|
+
return current && after ? sameContent(current, after) : !current && !after;
|
|
2281
|
+
}
|
|
2282
|
+
function snapshot(m) {
|
|
2283
|
+
return {
|
|
2284
|
+
description: m.description,
|
|
2285
|
+
type: m.type,
|
|
2286
|
+
body: m.body,
|
|
2287
|
+
sourceThreadId: m.sourceThreadId,
|
|
2288
|
+
createdBy: m.createdBy
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
function sameContent(m, other) {
|
|
2292
|
+
return m.description === other.description && m.type === other.type && m.body === other.body;
|
|
2293
|
+
}
|
|
2294
|
+
async function logMemoryEvent(e) {
|
|
2295
|
+
const { error } = await getSupabaseClient().from("memory_events").insert({
|
|
2296
|
+
channel_id: e.memory.channelId,
|
|
2297
|
+
memory_id: e.action === "delete" ? null : e.memory.id,
|
|
2298
|
+
thread_id: e.threadId,
|
|
2299
|
+
name: e.memory.name,
|
|
2300
|
+
action: e.action,
|
|
2301
|
+
before: e.before,
|
|
2302
|
+
after: e.after,
|
|
2303
|
+
actor: e.actor
|
|
2304
|
+
});
|
|
2305
|
+
if (error) console.error(`[memory] event log failed for ${e.memory.name}:`, error.message);
|
|
2306
|
+
}
|
|
2307
|
+
function rowToEvent(row) {
|
|
2308
|
+
return {
|
|
2309
|
+
id: row["id"],
|
|
2310
|
+
channelId: row["channel_id"],
|
|
2311
|
+
memoryId: row["memory_id"] ?? null,
|
|
2312
|
+
threadId: row["thread_id"] ?? null,
|
|
2313
|
+
name: row["name"],
|
|
2314
|
+
action: row["action"],
|
|
2315
|
+
before: row["before"] ?? null,
|
|
2316
|
+
after: row["after"] ?? null,
|
|
2317
|
+
actor: row["actor"],
|
|
2318
|
+
createdAt: row["created_at"],
|
|
2319
|
+
undoneAt: row["undone_at"] ?? null
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
async function listThreadMemoryEvents(channelId, threadId) {
|
|
2323
|
+
const supabase = getSupabaseClient();
|
|
2324
|
+
const { data, error } = await supabase.from("memory_events").select("*").eq("channel_id", channelId).eq("thread_id", threadId).order("created_at", { ascending: false }).limit(50);
|
|
2325
|
+
if (error) throw new Error(`listThreadMemoryEvents(${threadId}): ${error.message}`);
|
|
2326
|
+
const events = (data ?? []).map(rowToEvent);
|
|
2327
|
+
if (!events.length) return [];
|
|
2328
|
+
const names = [...new Set(events.map((e) => e.name))];
|
|
2329
|
+
const live = await supabase.from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId).in("name", names);
|
|
2330
|
+
if (live.error) throw new Error(`listThreadMemoryEvents(${threadId}): ${live.error.message}`);
|
|
2331
|
+
const byName = new Map(
|
|
2332
|
+
(live.data ?? []).map((r) => [
|
|
2333
|
+
r["name"],
|
|
2334
|
+
rowToMemory(r)
|
|
2335
|
+
])
|
|
2336
|
+
);
|
|
2337
|
+
return events.map((e) => {
|
|
2338
|
+
const current = byName.get(e.name) ?? null;
|
|
2339
|
+
const unchanged = unchangedSince(current, e.after);
|
|
2340
|
+
return {
|
|
2341
|
+
...e,
|
|
2342
|
+
currentState: unchanged ? "current" : current ? "changed" : "deleted",
|
|
2343
|
+
undoable: !e.undoneAt && unchanged
|
|
2344
|
+
};
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
async function undoMemoryEvent(channelId, eventId) {
|
|
2348
|
+
const supabase = getSupabaseClient();
|
|
2349
|
+
const { data, error } = await supabase.from("memory_events").select("*").eq("id", eventId).eq("channel_id", channelId).maybeSingle();
|
|
2350
|
+
if (error) throw new Error(`undoMemoryEvent(${eventId}): ${error.message}`);
|
|
2351
|
+
if (!data) return "not-found";
|
|
2352
|
+
const event = rowToEvent(data);
|
|
2353
|
+
if (event.undoneAt) return "already-undone";
|
|
2354
|
+
const current = await getChannelMemory(channelId, event.name);
|
|
2355
|
+
if (!unchangedSince(current, event.after)) return "conflict";
|
|
2356
|
+
const claimed = await supabase.from("memory_events").update({ undone_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", eventId).is("undone_at", null).select("id");
|
|
2357
|
+
if (claimed.error) throw new Error(`undoMemoryEvent(${eventId}): ${claimed.error.message}`);
|
|
2358
|
+
if (!(claimed.data ?? []).length) return "already-undone";
|
|
2359
|
+
const before = event.before;
|
|
2360
|
+
const restore = before ? {
|
|
2361
|
+
description: before.description,
|
|
2362
|
+
type: before.type,
|
|
2363
|
+
body: before.body,
|
|
2364
|
+
source_thread_id: before.sourceThreadId,
|
|
2365
|
+
created_by: before.createdBy
|
|
2366
|
+
} : null;
|
|
2367
|
+
const { error: writeError } = !restore ? await supabase.from("memories").delete().eq("id", current.id) : current ? await supabase.from("memories").update({ ...restore, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", current.id) : await supabase.from("memories").insert({ ...restore, scope: "channel", channel_id: channelId, name: event.name });
|
|
2368
|
+
if (writeError) {
|
|
2369
|
+
await supabase.from("memory_events").update({ undone_at: null }).eq("id", eventId);
|
|
2370
|
+
throw new Error(`undoMemoryEvent(${eventId}): ${writeError.message}`);
|
|
2371
|
+
}
|
|
2372
|
+
return "undone";
|
|
2373
|
+
}
|
|
2374
|
+
function isUuid(value) {
|
|
2375
|
+
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
|
+
}
|
|
2377
|
+
function formatMemoryPrompt(channelId, memories) {
|
|
2378
|
+
const lines = [];
|
|
2379
|
+
let used = 0;
|
|
2380
|
+
let omitted = 0;
|
|
2381
|
+
for (const m of memories) {
|
|
2382
|
+
const line = `- \`${m.name}\` (${m.type}): ${m.description}`;
|
|
2383
|
+
if (used + line.length > MEMORY_INDEX_MAX_CHARS) {
|
|
2384
|
+
omitted++;
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
lines.push(line);
|
|
2388
|
+
used += line.length + 1;
|
|
2389
|
+
}
|
|
2390
|
+
const index = lines.length ? lines.join("\n") + (omitted ? `
|
|
2391
|
+
- (${omitted} older memories not listed; find them with \`memory search\`)` : "") : "(none yet)";
|
|
2392
|
+
return [
|
|
2393
|
+
`## Channel memory`,
|
|
2394
|
+
``,
|
|
2395
|
+
`This channel has a persistent memory shared by every thread in it and by every agent runner. Use it instead of any built-in memory directory of your own CLI.`,
|
|
2396
|
+
``,
|
|
2397
|
+
`- Read a memory: \`flyspace memory get --channel ${channelId} <name>\``,
|
|
2398
|
+
`- Search: \`flyspace memory search --channel ${channelId} <words>\``,
|
|
2399
|
+
`- Save or overwrite: \`flyspace memory save --channel ${channelId} --name <kebab-slug> --type <${MEMORY_TYPES.join("|")}> --description "<one line>" --body "<text>" [--thread <thread-id>]\``,
|
|
2400
|
+
`- Delete one that turned out wrong: \`flyspace memory delete --channel ${channelId} <name> [--thread <thread-id>]\``,
|
|
2401
|
+
``,
|
|
2402
|
+
`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.`,
|
|
2403
|
+
``,
|
|
2404
|
+
`Index:`,
|
|
2405
|
+
index
|
|
2406
|
+
].join("\n");
|
|
2407
|
+
}
|
|
2408
|
+
|
|
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
|
+
|
|
2089
2423
|
// src/store/workspace-store.ts
|
|
2090
2424
|
function rowToMembership(row) {
|
|
2091
2425
|
return {
|
|
@@ -2163,6 +2497,7 @@ async function deleteWorkspace(workspace) {
|
|
|
2163
2497
|
}
|
|
2164
2498
|
async function setWorkspaceStripeSubscription(workspaceId, fields) {
|
|
2165
2499
|
const { error } = await getSupabaseClient().from("workspaces").update({
|
|
2500
|
+
plan: fields.plan,
|
|
2166
2501
|
stripe_customer_id: fields.stripeCustomerId,
|
|
2167
2502
|
stripe_subscription_id: fields.stripeSubscriptionId,
|
|
2168
2503
|
plan_status: fields.planStatus
|
|
@@ -2175,6 +2510,14 @@ async function markWorkspaceAgentConnected(workspaceId) {
|
|
|
2175
2510
|
async function markWorkspaceProjectsSetup(workspaceId) {
|
|
2176
2511
|
await stampOnce(workspaceId, "projects_setup_at");
|
|
2177
2512
|
}
|
|
2513
|
+
async function markWorkspaceGreeted(workspaceId) {
|
|
2514
|
+
await stampOnce(workspaceId, "greeted_at");
|
|
2515
|
+
}
|
|
2516
|
+
async function getWorkspaceGreetedAt(workspaceId) {
|
|
2517
|
+
const { data, error } = await getSupabaseClient().from("workspaces").select("greeted_at").eq("id", workspaceId).maybeSingle();
|
|
2518
|
+
if (error) throw new Error(`getWorkspaceGreetedAt(${workspaceId}): ${error.message}`);
|
|
2519
|
+
return data?.greeted_at ?? null;
|
|
2520
|
+
}
|
|
2178
2521
|
async function stampOnce(workspaceId, column) {
|
|
2179
2522
|
const { error } = await getSupabaseClient().from("workspaces").update({ [column]: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", workspaceId).is(column, null);
|
|
2180
2523
|
if (error) throw new Error(`stampOnce(${workspaceId}, ${column}): ${error.message}`);
|
|
@@ -2190,11 +2533,12 @@ async function getWorkspaceByStripeCustomerId(stripeCustomerId) {
|
|
|
2190
2533
|
return data;
|
|
2191
2534
|
}
|
|
2192
2535
|
async function getWorkspaceBilling(workspaceId) {
|
|
2193
|
-
const { data, error } = await getSupabaseClient().from("workspaces").select("plan_status, stripe_customer_id, stripe_subscription_id").eq("id", workspaceId).maybeSingle();
|
|
2536
|
+
const { data, error } = await getSupabaseClient().from("workspaces").select("plan, plan_status, stripe_customer_id, stripe_subscription_id").eq("id", workspaceId).maybeSingle();
|
|
2194
2537
|
if (error) throw new Error(`getWorkspaceBilling(${workspaceId}): ${error.message}`);
|
|
2195
2538
|
const row = data ?? {};
|
|
2196
2539
|
return {
|
|
2197
2540
|
planStatus: row["plan_status"] ?? null,
|
|
2541
|
+
plan: row["plan"] ?? null,
|
|
2198
2542
|
stripeCustomerId: row["stripe_customer_id"] ?? null,
|
|
2199
2543
|
stripeSubscriptionId: row["stripe_subscription_id"] ?? null
|
|
2200
2544
|
};
|
|
@@ -3731,17 +4075,21 @@ async function runAutomation(opts) {
|
|
|
3731
4075
|
|
|
3732
4076
|
`;
|
|
3733
4077
|
try {
|
|
4078
|
+
const memory = await loadAgentMemory(getProfile(), channel && findChannel(channel)?.id);
|
|
3734
4079
|
const useCodex = (explicitModel ? runnerForModel(explicitModel) === "codex" : runnerForModel(loadDashboardSettings().agent) === "codex") && !requiresClaudeAutomationRunner(prompt);
|
|
3735
4080
|
const response = useCodex ? await askCodex(automationPrefix + prompt, cwd, {
|
|
3736
4081
|
...explicitModel && runnerForModel(explicitModel) === "codex" ? { model: explicitModel } : codexModel?.trim() ? { model: codexModel.trim() } : {},
|
|
3737
4082
|
// Same reason as the interactive paths: no --append-system-prompt on
|
|
3738
4083
|
// Codex, so the persona and channel instructions go in the prompt.
|
|
3739
|
-
systemPrompt:
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
4084
|
+
systemPrompt: [
|
|
4085
|
+
buildSystemPrompt(
|
|
4086
|
+
botName,
|
|
4087
|
+
void 0,
|
|
4088
|
+
resolveChannelPrompt(channel),
|
|
4089
|
+
resolveSelectedOutputStyle(claudeConfigDir)?.body
|
|
4090
|
+
),
|
|
4091
|
+
memory.memoryPrompt
|
|
4092
|
+
].filter(Boolean).join("\n\n"),
|
|
3745
4093
|
// Same attribution as the Claude branch below — no userId, the
|
|
3746
4094
|
// schedule's name is the label.
|
|
3747
4095
|
usageContext: { source: "automation", channelId: channel, label: name }
|
|
@@ -3755,6 +4103,8 @@ async function runAutomation(opts) {
|
|
|
3755
4103
|
model,
|
|
3756
4104
|
maxBudgetUsd,
|
|
3757
4105
|
channelPrompt: resolveChannelPrompt(channel),
|
|
4106
|
+
...memory.memoryPrompt ? { memoryPrompt: memory.memoryPrompt } : {},
|
|
4107
|
+
...memory.enabled ? { disableAutoMemory: true } : {},
|
|
3758
4108
|
// No userId: nobody triggered this. The schedule's name is the
|
|
3759
4109
|
// label, which is what makes "which automation is burning tokens"
|
|
3760
4110
|
// answerable without joining anything.
|
|
@@ -8400,6 +8750,27 @@ export {
|
|
|
8400
8750
|
docsAccessPromptNote,
|
|
8401
8751
|
pinnedClaudeConfigDir,
|
|
8402
8752
|
askClaude,
|
|
8753
|
+
featureFlags,
|
|
8754
|
+
featureFlagIds,
|
|
8755
|
+
isFeatureFlagId,
|
|
8756
|
+
isFeatureVisible,
|
|
8757
|
+
resolveFeatureFlags,
|
|
8758
|
+
listEnabledFeatureFlags,
|
|
8759
|
+
enableFeatureFlag,
|
|
8760
|
+
disableFeatureFlag,
|
|
8761
|
+
MEMORY_TYPES,
|
|
8762
|
+
isMemoryType,
|
|
8763
|
+
validateMemoryInput,
|
|
8764
|
+
listChannelMemories,
|
|
8765
|
+
getChannelMemory,
|
|
8766
|
+
saveChannelMemory,
|
|
8767
|
+
updateChannelMemory,
|
|
8768
|
+
searchChannelMemories,
|
|
8769
|
+
deleteChannelMemory,
|
|
8770
|
+
listThreadMemoryEvents,
|
|
8771
|
+
undoMemoryEvent,
|
|
8772
|
+
isUuid,
|
|
8773
|
+
loadAgentMemory,
|
|
8403
8774
|
getWorkspaceBySlug,
|
|
8404
8775
|
getWorkspaceById,
|
|
8405
8776
|
listWorkspaces,
|
|
@@ -8409,6 +8780,8 @@ export {
|
|
|
8409
8780
|
setWorkspaceStripeSubscription,
|
|
8410
8781
|
markWorkspaceAgentConnected,
|
|
8411
8782
|
markWorkspaceProjectsSetup,
|
|
8783
|
+
markWorkspaceGreeted,
|
|
8784
|
+
getWorkspaceGreetedAt,
|
|
8412
8785
|
getWorkspaceByStripeCustomerId,
|
|
8413
8786
|
getWorkspaceBilling,
|
|
8414
8787
|
updateWorkspacePlanStatus,
|