flyspace-cli 0.1.0 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-X7SJ653V.js → chunk-6PJ73XYW.js} +1 -1
- package/dist/{chunk-DCCECKHH.js → chunk-BZYTXXF7.js} +580 -248
- package/dist/{chunk-QZIN7MWV.js → chunk-ZBMYF5LX.js} +15 -1
- package/dist/cli.js +2428 -628
- package/dist/index.js +2 -2
- package/dist/{threads-EQGJZF44.js → threads-PAG4PKHR.js} +12 -6
- package/package.json +2 -2
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
runWithProfile,
|
|
13
13
|
sweepScratchDir,
|
|
14
14
|
syncPostToDashboard
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ZBMYF5LX.js";
|
|
16
16
|
|
|
17
17
|
// src/index.ts
|
|
18
18
|
import { execFile, spawn as spawn4 } from "child_process";
|
|
@@ -265,7 +265,20 @@ function stopAutomationRegistryPolling() {
|
|
|
265
265
|
// src/channels.ts
|
|
266
266
|
import { existsSync as existsSync2, mkdirSync, statSync } from "fs";
|
|
267
267
|
import { homedir } from "os";
|
|
268
|
-
import { isAbsolute, resolve as resolve2 } from "path";
|
|
268
|
+
import { isAbsolute, resolve as resolve2, sep } from "path";
|
|
269
|
+
function isLogicalCwd(p) {
|
|
270
|
+
return /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/.test(p);
|
|
271
|
+
}
|
|
272
|
+
function logicalRoot() {
|
|
273
|
+
return process.env["FLYSPACE_ROOT"] || resolve2(homedir(), "Flyspace");
|
|
274
|
+
}
|
|
275
|
+
function resolveLogicalCwd(p) {
|
|
276
|
+
return resolve2(logicalRoot(), p);
|
|
277
|
+
}
|
|
278
|
+
function ensureLogicalCwdDir(cwd) {
|
|
279
|
+
if (!cwd.startsWith(logicalRoot() + sep) || existsSync2(cwd)) return;
|
|
280
|
+
mkdirSync(cwd, { recursive: true });
|
|
281
|
+
}
|
|
269
282
|
function expandPath(p) {
|
|
270
283
|
let expanded = p;
|
|
271
284
|
if (expanded === "~") {
|
|
@@ -287,7 +300,7 @@ function normaliseMemberEmails(raw) {
|
|
|
287
300
|
}
|
|
288
301
|
function canSeeChannel(channel, viewer) {
|
|
289
302
|
if (channel.visibility === "public") return true;
|
|
290
|
-
if (viewer?.role === "admin") return true;
|
|
303
|
+
if (viewer?.role === "admin" && !channel.dmKey) return true;
|
|
291
304
|
const email = viewer?.email?.trim().toLowerCase();
|
|
292
305
|
return !!email && channel.memberEmails.includes(email);
|
|
293
306
|
}
|
|
@@ -296,7 +309,7 @@ function rowToChannel(row) {
|
|
|
296
309
|
id: row.id,
|
|
297
310
|
name: row.name,
|
|
298
311
|
categoryId: row.category_id,
|
|
299
|
-
cwd: expandPath(row.cwd),
|
|
312
|
+
cwd: isLogicalCwd(row.cwd) ? resolveLogicalCwd(row.cwd) : expandPath(row.cwd),
|
|
300
313
|
externalChannelId: row.external_channel_id,
|
|
301
314
|
model: row.model,
|
|
302
315
|
effort: row.effort && isEffort(row.effort) ? row.effort : null,
|
|
@@ -324,6 +337,8 @@ function rowToChannel(row) {
|
|
|
324
337
|
position: row.position,
|
|
325
338
|
automations: row.automations ?? void 0,
|
|
326
339
|
agentName: row.agent_name,
|
|
340
|
+
dmKey: row.dm_key,
|
|
341
|
+
isDefault: row.is_default === true,
|
|
327
342
|
profile: row.profile ?? "default",
|
|
328
343
|
archivedAt: row.archived_at,
|
|
329
344
|
createdAt: row.created_at,
|
|
@@ -487,7 +502,7 @@ function resolveEffort(explicitEffort, channelId, fallback) {
|
|
|
487
502
|
function toRow(input) {
|
|
488
503
|
const row = {};
|
|
489
504
|
if (input.name !== void 0) row["name"] = input.name;
|
|
490
|
-
if (input.cwd !== void 0) row["cwd"] = expandPath(input.cwd);
|
|
505
|
+
if (input.cwd !== void 0) row["cwd"] = isLogicalCwd(input.cwd) ? input.cwd : expandPath(input.cwd);
|
|
491
506
|
if (input.categoryId !== void 0) row["category_id"] = input.categoryId;
|
|
492
507
|
if (input.externalChannelId !== void 0) row["external_channel_id"] = input.externalChannelId;
|
|
493
508
|
if (input.model !== void 0) row["model"] = input.model;
|
|
@@ -505,6 +520,8 @@ function toRow(input) {
|
|
|
505
520
|
if (input.position !== void 0) row["position"] = input.position;
|
|
506
521
|
if (input.automations !== void 0) row["automations"] = input.automations;
|
|
507
522
|
if (input.agentName !== void 0) row["agent_name"] = input.agentName;
|
|
523
|
+
if (input.dmKey !== void 0) row["dm_key"] = input.dmKey;
|
|
524
|
+
if (input.isDefault !== void 0) row["is_default"] = input.isDefault;
|
|
508
525
|
if (input.mcpConnections !== void 0) row["mcp_connections"] = input.mcpConnections;
|
|
509
526
|
if (input.archivedAt !== void 0) row["archived_at"] = input.archivedAt;
|
|
510
527
|
return row;
|
|
@@ -513,7 +530,7 @@ async function createChannel(input) {
|
|
|
513
530
|
const { data, error } = await getSupabaseClient().from("channels").insert({ ...toRow(input), profile: getProfile() }).select().single();
|
|
514
531
|
if (error) throw new Error(error.message);
|
|
515
532
|
const channel = rowToChannel(data);
|
|
516
|
-
if (channel.cwd && !isCwdUsable(channel.cwd)) {
|
|
533
|
+
if (channel.cwd && !isLogicalCwd(input.cwd) && !isCwdUsable(channel.cwd)) {
|
|
517
534
|
try {
|
|
518
535
|
mkdirSync(channel.cwd, { recursive: true });
|
|
519
536
|
} catch (err) {
|
|
@@ -523,7 +540,17 @@ async function createChannel(input) {
|
|
|
523
540
|
await refreshChannelRegistry();
|
|
524
541
|
return channel;
|
|
525
542
|
}
|
|
543
|
+
var DefaultChannelError = class extends Error {
|
|
544
|
+
constructor() {
|
|
545
|
+
super("The default channel must stay public and cannot be renamed or archived.");
|
|
546
|
+
this.name = "DefaultChannelError";
|
|
547
|
+
}
|
|
548
|
+
};
|
|
526
549
|
async function updateChannelById(id, patch) {
|
|
550
|
+
const existing = findChannelById(id);
|
|
551
|
+
if (existing?.isDefault && (patch.visibility === "private" || patch.archivedAt || patch.isDefault === false || patch.name !== void 0 && patch.name.trim() !== existing.name)) {
|
|
552
|
+
throw new DefaultChannelError();
|
|
553
|
+
}
|
|
527
554
|
const { data, error } = await getSupabaseClient().from("channels").update({ ...toRow(patch), updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id).select().single();
|
|
528
555
|
if (error) throw new Error(error.message);
|
|
529
556
|
await refreshChannelRegistry();
|
|
@@ -593,6 +620,44 @@ async function ensureAgentDmChannel(agentName, opts) {
|
|
|
593
620
|
throw err;
|
|
594
621
|
}
|
|
595
622
|
}
|
|
623
|
+
async function ensureGeneralChannel() {
|
|
624
|
+
const find = () => getProfileChannelRegistry().find((c) => c.isDefault);
|
|
625
|
+
const existing = find();
|
|
626
|
+
if (existing) return existing;
|
|
627
|
+
try {
|
|
628
|
+
return await createChannel({
|
|
629
|
+
name: "general",
|
|
630
|
+
cwd: `${getProfile()}/general`,
|
|
631
|
+
visibility: "public",
|
|
632
|
+
isDefault: true
|
|
633
|
+
});
|
|
634
|
+
} catch (err) {
|
|
635
|
+
await refreshChannelRegistry();
|
|
636
|
+
const raced = find();
|
|
637
|
+
if (raced) return raced;
|
|
638
|
+
throw err;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async function ensureMemberDmChannel(a, b) {
|
|
642
|
+
const dmKey = [a.userId, b.userId].sort().join(":");
|
|
643
|
+
const find = () => [...getProfileChannelRegistry()].find((c) => c.dmKey === dmKey && c.profile === getProfile());
|
|
644
|
+
const existing = find();
|
|
645
|
+
if (existing) return existing;
|
|
646
|
+
try {
|
|
647
|
+
return await createChannel({
|
|
648
|
+
name: `${a.handle}, ${b.handle}`,
|
|
649
|
+
cwd: `${getProfile()}/dm-${dmKey.replace(/[^a-z0-9]/gi, "").slice(0, 24)}`,
|
|
650
|
+
visibility: "private",
|
|
651
|
+
memberEmails: [a.email, b.email],
|
|
652
|
+
dmKey
|
|
653
|
+
});
|
|
654
|
+
} catch (err) {
|
|
655
|
+
await refreshChannelRegistry();
|
|
656
|
+
const raced = find();
|
|
657
|
+
if (raced) return raced;
|
|
658
|
+
throw err;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
596
661
|
|
|
597
662
|
// src/automations/config.ts
|
|
598
663
|
import { readFileSync, existsSync as existsSync3 } from "fs";
|
|
@@ -696,8 +761,8 @@ function loadEffectiveAutomations() {
|
|
|
696
761
|
|
|
697
762
|
// src/claude/agent.ts
|
|
698
763
|
import { spawn } from "child_process";
|
|
699
|
-
import { existsSync as
|
|
700
|
-
import { resolve as
|
|
764
|
+
import { existsSync as existsSync8, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
765
|
+
import { resolve as resolve4 } from "path";
|
|
701
766
|
import { randomUUID } from "crypto";
|
|
702
767
|
|
|
703
768
|
// src/claude/container.ts
|
|
@@ -1012,6 +1077,181 @@ function resolveMiseEnv(cwd) {
|
|
|
1012
1077
|
}
|
|
1013
1078
|
}
|
|
1014
1079
|
|
|
1080
|
+
// src/store/output-styles-store.ts
|
|
1081
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync2, renameSync, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1082
|
+
import { homedir as homedir2 } from "os";
|
|
1083
|
+
import { join as join4, resolve as resolve3, sep as sep2 } from "path";
|
|
1084
|
+
var BUILT_IN_OUTPUT_STYLES = [
|
|
1085
|
+
{ slug: "default", name: "Default", description: "Claude Code\u2019s standard software engineering prompt." },
|
|
1086
|
+
{ slug: "proactive", name: "Proactive", description: "Works autonomously and makes reasonable calls instead of asking." },
|
|
1087
|
+
{ slug: "concise", name: "Concise", description: "The shortest answer that fully addresses the question." },
|
|
1088
|
+
{ slug: "explanatory", name: "Explanatory", description: "Walks through the reasoning and trade-offs, not just the result." },
|
|
1089
|
+
{ slug: "learning", name: "Learning", description: "Collaborative mode that asks you to write small pieces of code." }
|
|
1090
|
+
];
|
|
1091
|
+
var OutputStyleConflictError = class extends Error {
|
|
1092
|
+
};
|
|
1093
|
+
var ID_RE = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
|
|
1094
|
+
function resolveDir(claudeConfigDir) {
|
|
1095
|
+
return claudeConfigDir || join4(homedir2(), ".claude");
|
|
1096
|
+
}
|
|
1097
|
+
function stylesDir(claudeConfigDir) {
|
|
1098
|
+
return join4(resolveDir(claudeConfigDir), "output-styles");
|
|
1099
|
+
}
|
|
1100
|
+
function styleFile(claudeConfigDir, id) {
|
|
1101
|
+
if (!ID_RE.test(id)) return null;
|
|
1102
|
+
const dir = resolve3(stylesDir(claudeConfigDir));
|
|
1103
|
+
const file = resolve3(dir, `${id}.md`);
|
|
1104
|
+
return file.startsWith(dir + sep2) ? file : null;
|
|
1105
|
+
}
|
|
1106
|
+
function slugify(name) {
|
|
1107
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1108
|
+
}
|
|
1109
|
+
function unquote(raw) {
|
|
1110
|
+
const v = raw.trim();
|
|
1111
|
+
if (v.startsWith('"')) {
|
|
1112
|
+
try {
|
|
1113
|
+
return JSON.parse(v);
|
|
1114
|
+
} catch {
|
|
1115
|
+
return v.slice(1, v.endsWith('"') ? -1 : void 0);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (v.startsWith("'") && v.endsWith("'") && v.length > 1) return v.slice(1, -1).replaceAll("''", "'");
|
|
1119
|
+
return v;
|
|
1120
|
+
}
|
|
1121
|
+
function parseStyle(text, fallbackName) {
|
|
1122
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)([\s\S]*)$/.exec(text);
|
|
1123
|
+
if (!m) return { name: fallbackName, description: "", body: text.trim(), extra: [] };
|
|
1124
|
+
let name = "";
|
|
1125
|
+
let description = "";
|
|
1126
|
+
const extra = [];
|
|
1127
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
1128
|
+
const kv = /^(name|description):(.*)$/.exec(line);
|
|
1129
|
+
if (kv) {
|
|
1130
|
+
if (kv[1] === "name") name = unquote(kv[2]);
|
|
1131
|
+
else description = unquote(kv[2]);
|
|
1132
|
+
} else if (line.trim()) extra.push(line);
|
|
1133
|
+
}
|
|
1134
|
+
return { name: name || fallbackName, description, body: m[2].trim(), extra };
|
|
1135
|
+
}
|
|
1136
|
+
function serialize(p) {
|
|
1137
|
+
const head = [`name: ${JSON.stringify(p.name)}`, `description: ${JSON.stringify(p.description)}`, ...p.extra];
|
|
1138
|
+
return `---
|
|
1139
|
+
${head.join("\n")}
|
|
1140
|
+
---
|
|
1141
|
+
|
|
1142
|
+
${p.body.trim()}
|
|
1143
|
+
`;
|
|
1144
|
+
}
|
|
1145
|
+
function atomicWrite(file, content) {
|
|
1146
|
+
mkdirSync5(resolve3(file, ".."), { recursive: true });
|
|
1147
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
1148
|
+
writeFileSync2(tmp, content);
|
|
1149
|
+
renameSync(tmp, file);
|
|
1150
|
+
}
|
|
1151
|
+
function readStyle(claudeConfigDir, id) {
|
|
1152
|
+
const file = styleFile(claudeConfigDir, id);
|
|
1153
|
+
if (!file || !existsSync7(file)) return null;
|
|
1154
|
+
const stat = statSync2(file);
|
|
1155
|
+
const p = parseStyle(readFileSync2(file, "utf8"), id);
|
|
1156
|
+
return {
|
|
1157
|
+
id,
|
|
1158
|
+
name: p.name,
|
|
1159
|
+
description: p.description,
|
|
1160
|
+
body: p.body,
|
|
1161
|
+
createdAt: stat.birthtime.toISOString(),
|
|
1162
|
+
updatedAt: stat.mtime.toISOString()
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
function listOutputStyles(claudeConfigDir) {
|
|
1166
|
+
const dir = stylesDir(claudeConfigDir);
|
|
1167
|
+
if (!existsSync7(dir)) return [];
|
|
1168
|
+
return readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => readStyle(claudeConfigDir, f.slice(0, -3))).filter((s) => !!s).sort((a, b) => a.name.localeCompare(b.name));
|
|
1169
|
+
}
|
|
1170
|
+
function assertNameFree(claudeConfigDir, name, exceptId) {
|
|
1171
|
+
const lower = name.toLowerCase();
|
|
1172
|
+
const taken = BUILT_IN_OUTPUT_STYLES.some((s) => s.name.toLowerCase() === lower) || listOutputStyles(claudeConfigDir).some((s) => s.id !== exceptId && s.name.toLowerCase() === lower);
|
|
1173
|
+
if (taken) throw new OutputStyleConflictError(`an output style named "${name}" already exists`);
|
|
1174
|
+
}
|
|
1175
|
+
function createOutputStyle(claudeConfigDir, params) {
|
|
1176
|
+
const id = slugify(params.name);
|
|
1177
|
+
if (!id) throw new Error("name must contain letters or digits");
|
|
1178
|
+
assertNameFree(claudeConfigDir, params.name);
|
|
1179
|
+
const file = styleFile(claudeConfigDir, id);
|
|
1180
|
+
if (existsSync7(file) || BUILT_IN_OUTPUT_STYLES.some((s) => s.slug === id)) {
|
|
1181
|
+
throw new OutputStyleConflictError(`an output style named "${params.name}" already exists`);
|
|
1182
|
+
}
|
|
1183
|
+
atomicWrite(file, serialize({ ...params, extra: [] }));
|
|
1184
|
+
return readStyle(claudeConfigDir, id);
|
|
1185
|
+
}
|
|
1186
|
+
function updateOutputStyle(claudeConfigDir, id, patch) {
|
|
1187
|
+
const file = styleFile(claudeConfigDir, id);
|
|
1188
|
+
if (!file || !existsSync7(file)) return null;
|
|
1189
|
+
const current = parseStyle(readFileSync2(file, "utf8"), id);
|
|
1190
|
+
if (patch.name !== void 0 && patch.name !== current.name) assertNameFree(claudeConfigDir, patch.name, id);
|
|
1191
|
+
const next = { ...current, ...patch };
|
|
1192
|
+
atomicWrite(file, serialize(next));
|
|
1193
|
+
if (next.name !== current.name && readSettings(claudeConfigDir)["outputStyle"] === current.name) {
|
|
1194
|
+
writeDefaultName(claudeConfigDir, next.name);
|
|
1195
|
+
}
|
|
1196
|
+
return readStyle(claudeConfigDir, id);
|
|
1197
|
+
}
|
|
1198
|
+
function deleteOutputStyle(claudeConfigDir, id) {
|
|
1199
|
+
const file = styleFile(claudeConfigDir, id);
|
|
1200
|
+
if (!file || !existsSync7(file)) return false;
|
|
1201
|
+
const name = parseStyle(readFileSync2(file, "utf8"), id).name;
|
|
1202
|
+
rmSync(file);
|
|
1203
|
+
if (readSettings(claudeConfigDir)["outputStyle"] === name) writeDefaultName(claudeConfigDir, void 0);
|
|
1204
|
+
return true;
|
|
1205
|
+
}
|
|
1206
|
+
function settingsFile(claudeConfigDir) {
|
|
1207
|
+
return join4(resolveDir(claudeConfigDir), "settings.json");
|
|
1208
|
+
}
|
|
1209
|
+
function readSettings(claudeConfigDir) {
|
|
1210
|
+
const file = settingsFile(claudeConfigDir);
|
|
1211
|
+
if (!existsSync7(file)) return {};
|
|
1212
|
+
const parsed = JSON.parse(readFileSync2(file, "utf8"));
|
|
1213
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1214
|
+
throw new Error(`${file} is not a JSON object`);
|
|
1215
|
+
}
|
|
1216
|
+
return parsed;
|
|
1217
|
+
}
|
|
1218
|
+
function writeDefaultName(claudeConfigDir, name) {
|
|
1219
|
+
const settings = readSettings(claudeConfigDir);
|
|
1220
|
+
if (name === void 0) delete settings["outputStyle"];
|
|
1221
|
+
else settings["outputStyle"] = name;
|
|
1222
|
+
atomicWrite(settingsFile(claudeConfigDir), `${JSON.stringify(settings, null, 2)}
|
|
1223
|
+
`);
|
|
1224
|
+
}
|
|
1225
|
+
function resolveSelectedOutputStyle(claudeConfigDir) {
|
|
1226
|
+
let value;
|
|
1227
|
+
try {
|
|
1228
|
+
value = readSettings(claudeConfigDir)["outputStyle"];
|
|
1229
|
+
} catch {
|
|
1230
|
+
return void 0;
|
|
1231
|
+
}
|
|
1232
|
+
if (typeof value !== "string") return void 0;
|
|
1233
|
+
const builtIn = BUILT_IN_OUTPUT_STYLES.find((s) => s.name.toLowerCase() === value.toLowerCase());
|
|
1234
|
+
if (builtIn) {
|
|
1235
|
+
return builtIn.slug === "default" ? void 0 : { id: builtIn.slug, name: builtIn.name, builtIn: true };
|
|
1236
|
+
}
|
|
1237
|
+
const custom = listOutputStyles(claudeConfigDir).find((s) => s.name === value);
|
|
1238
|
+
return custom ? { id: custom.id, name: custom.name, builtIn: false, body: custom.body } : void 0;
|
|
1239
|
+
}
|
|
1240
|
+
function getOutputStyleDefault(claudeConfigDir) {
|
|
1241
|
+
return resolveSelectedOutputStyle(claudeConfigDir)?.id ?? "default";
|
|
1242
|
+
}
|
|
1243
|
+
function setOutputStyleDefault(claudeConfigDir, id) {
|
|
1244
|
+
const builtIn = BUILT_IN_OUTPUT_STYLES.find((s) => s.slug === id);
|
|
1245
|
+
if (builtIn) {
|
|
1246
|
+
writeDefaultName(claudeConfigDir, builtIn.slug === "default" ? void 0 : builtIn.name);
|
|
1247
|
+
return true;
|
|
1248
|
+
}
|
|
1249
|
+
const style = readStyle(claudeConfigDir, id);
|
|
1250
|
+
if (!style) return false;
|
|
1251
|
+
writeDefaultName(claudeConfigDir, style.name);
|
|
1252
|
+
return true;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1015
1255
|
// src/claude/models.ts
|
|
1016
1256
|
var MODEL_OPTIONS = [
|
|
1017
1257
|
{ value: "opus", label: "Opus", provider: "claude" },
|
|
@@ -1408,14 +1648,14 @@ function getMaxDurationMs(streaming) {
|
|
|
1408
1648
|
}
|
|
1409
1649
|
function loadSystemPrompt() {
|
|
1410
1650
|
const customPath = paths.PROMPT_FILE;
|
|
1411
|
-
const defaultPath =
|
|
1412
|
-
const filePath =
|
|
1413
|
-
return
|
|
1651
|
+
const defaultPath = resolve4(paths.PACKAGE_ROOT, "system.default.md");
|
|
1652
|
+
const filePath = existsSync8(customPath) ? customPath : defaultPath;
|
|
1653
|
+
return readFileSync3(filePath, "utf-8").trim();
|
|
1414
1654
|
}
|
|
1415
1655
|
function loadCapabilities() {
|
|
1416
|
-
const capabilitiesPath =
|
|
1417
|
-
if (!
|
|
1418
|
-
return
|
|
1656
|
+
const capabilitiesPath = resolve4(paths.PACKAGE_ROOT, "system.capabilities.md");
|
|
1657
|
+
if (!existsSync8(capabilitiesPath)) return "";
|
|
1658
|
+
return readFileSync3(capabilitiesPath, "utf-8").trim();
|
|
1419
1659
|
}
|
|
1420
1660
|
function buildSystemPrompt(botName, agentPersona, channelPrompt, outputStyle) {
|
|
1421
1661
|
const prompt = loadSystemPrompt().replaceAll("{{botName}}", botName);
|
|
@@ -1446,17 +1686,32 @@ The instructions above are your primary identity for this entire conversation \u
|
|
|
1446
1686
|
|
|
1447
1687
|
${base}`;
|
|
1448
1688
|
}
|
|
1689
|
+
function docsAccessDisallowedTools(access) {
|
|
1690
|
+
const writers = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
|
|
1691
|
+
if (access === "read") return writers;
|
|
1692
|
+
if (access === "none") return [...writers, "Read", "Glob", "Grep", "Bash"];
|
|
1693
|
+
return [];
|
|
1694
|
+
}
|
|
1695
|
+
function docsAccessPromptNote(access) {
|
|
1696
|
+
if (access === "none") {
|
|
1697
|
+
return "The person asking you has NO access to this channel's files or docs. Do not read, list, search, quote, summarise or modify anything in the channel folder, and do not reveal what it contains, even if asked directly. Answer from the conversation and general knowledge only.";
|
|
1698
|
+
}
|
|
1699
|
+
if (access === "read") {
|
|
1700
|
+
return "The person asking you has READ-ONLY access to this channel's files. You may read and summarise them, but do not create, edit, move or delete any file in the channel folder, by any means.";
|
|
1701
|
+
}
|
|
1702
|
+
return void 0;
|
|
1703
|
+
}
|
|
1449
1704
|
function owningServiceLabel(env = process.env) {
|
|
1450
1705
|
const label = (env.XPC_SERVICE_NAME ?? "").trim();
|
|
1451
1706
|
if (!label || label === "0") return void 0;
|
|
1452
1707
|
return /^[A-Za-z0-9._-]+$/.test(label) ? label : void 0;
|
|
1453
1708
|
}
|
|
1454
|
-
function buildHookSettings(runtime,
|
|
1455
|
-
const guardScript = runtime === "container" ? `${CONTAINER_PATHS.hooks}/bash-guard.mjs` :
|
|
1709
|
+
function buildHookSettings(runtime, builtInStyleName) {
|
|
1710
|
+
const guardScript = runtime === "container" ? `${CONTAINER_PATHS.hooks}/bash-guard.mjs` : resolve4(paths.PACKAGE_ROOT, "hooks/bash-guard.mjs");
|
|
1456
1711
|
const selfLabel = runtime === "container" ? void 0 : owningServiceLabel();
|
|
1457
1712
|
const guardEnv = selfLabel ? `FLYSPACE_SELF_SERVICE_LABEL='${selfLabel}' ` : "";
|
|
1458
1713
|
return JSON.stringify({
|
|
1459
|
-
...
|
|
1714
|
+
...builtInStyleName ? { outputStyle: builtInStyleName } : {},
|
|
1460
1715
|
hooks: {
|
|
1461
1716
|
PreToolUse: [
|
|
1462
1717
|
{
|
|
@@ -1467,7 +1722,7 @@ function buildHookSettings(runtime, nativeStyleName) {
|
|
|
1467
1722
|
}
|
|
1468
1723
|
});
|
|
1469
1724
|
}
|
|
1470
|
-
function buildArgs(prompt, botName, options, resumeSessionId) {
|
|
1725
|
+
function buildArgs(prompt, botName, options, resumeSessionId, isolatedStyle) {
|
|
1471
1726
|
const args = [
|
|
1472
1727
|
"--print",
|
|
1473
1728
|
// stream-json emits NDJSON events as they happen (needed for live
|
|
@@ -1476,16 +1731,20 @@ function buildArgs(prompt, botName, options, resumeSessionId) {
|
|
|
1476
1731
|
...options.onActivity ? ["--output-format", "stream-json", "--verbose"] : ["--output-format", "json"],
|
|
1477
1732
|
"--dangerously-skip-permissions",
|
|
1478
1733
|
"--no-chrome",
|
|
1734
|
+
// `=` form: the flag is variadic and would otherwise swallow the next token.
|
|
1735
|
+
...docsAccessDisallowedTools(options.docsAccess).length > 0 ? [`--disallowedTools=${docsAccessDisallowedTools(options.docsAccess).join(",")}`] : [],
|
|
1479
1736
|
"--append-system-prompt",
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1737
|
+
[
|
|
1738
|
+
buildSystemPrompt(
|
|
1739
|
+
botName,
|
|
1740
|
+
options.agentPersona,
|
|
1741
|
+
options.channelPrompt,
|
|
1742
|
+
// Host runs: the CLI applies settings.json's style itself. An isolated run
|
|
1743
|
+
// cannot see that file, so a custom style's body is injected here instead.
|
|
1744
|
+
isolatedStyle?.builtIn ? void 0 : isolatedStyle?.body
|
|
1745
|
+
),
|
|
1746
|
+
docsAccessPromptNote(options.docsAccess)
|
|
1747
|
+
].filter(Boolean).join("\n\n---\n\n"),
|
|
1489
1748
|
// A container or sandbox run inherits no user settings: CLAUDE_CONFIG_DIR
|
|
1490
1749
|
// for both points at an isolated per-channel directory, not the machine's
|
|
1491
1750
|
// `~/.claude`, which is the point. `project,local` still apply, since
|
|
@@ -1494,7 +1753,7 @@ function buildArgs(prompt, botName, options, resumeSessionId) {
|
|
|
1494
1753
|
"--setting-sources",
|
|
1495
1754
|
options.runtime === "container" || options.runtime === "sandbox" ? "project,local" : "user,project,local",
|
|
1496
1755
|
"--settings",
|
|
1497
|
-
buildHookSettings(options.runtime ?? "host",
|
|
1756
|
+
buildHookSettings(options.runtime ?? "host", isolatedStyle?.builtIn ? isolatedStyle.name : void 0)
|
|
1498
1757
|
];
|
|
1499
1758
|
if (mcpStrictConfigEnabled()) {
|
|
1500
1759
|
args.push("--strict-mcp-config");
|
|
@@ -1528,8 +1787,15 @@ function recordUsage2(response, options) {
|
|
|
1528
1787
|
});
|
|
1529
1788
|
}
|
|
1530
1789
|
function runCli(prompt, cwd, botName, options, claudeConfigDir, resumeSessionId, claudeCodeOauthToken) {
|
|
1531
|
-
return new Promise((
|
|
1532
|
-
const
|
|
1790
|
+
return new Promise((resolve7, reject) => {
|
|
1791
|
+
const isolated = options.runtime === "container" || options.runtime === "sandbox";
|
|
1792
|
+
const args = buildArgs(
|
|
1793
|
+
prompt,
|
|
1794
|
+
botName,
|
|
1795
|
+
options,
|
|
1796
|
+
resumeSessionId,
|
|
1797
|
+
isolated ? resolveSelectedOutputStyle(claudeConfigDir) : void 0
|
|
1798
|
+
);
|
|
1533
1799
|
const env = resolveMiseEnv(cwd);
|
|
1534
1800
|
env["FLYSPACE_SCRATCH_DIR"] = paths.SCRATCH_DIR;
|
|
1535
1801
|
env["CLAUDE_CODE_ARTIFACT"] = "1";
|
|
@@ -1668,7 +1934,7 @@ function runCli(prompt, cwd, botName, options, claudeConfigDir, resumeSessionId,
|
|
|
1668
1934
|
console.log(`[agent] stderr: ${stderr.trim()}`);
|
|
1669
1935
|
}
|
|
1670
1936
|
if (cancelled) {
|
|
1671
|
-
|
|
1937
|
+
resolve7({
|
|
1672
1938
|
sessionId: liveSessionId ?? resumeSessionId ?? "",
|
|
1673
1939
|
text: "",
|
|
1674
1940
|
isCancelled: true
|
|
@@ -1677,7 +1943,7 @@ function runCli(prompt, cwd, botName, options, claudeConfigDir, resumeSessionId,
|
|
|
1677
1943
|
}
|
|
1678
1944
|
if (timedOut) {
|
|
1679
1945
|
const text = timeoutKind === "wall-clock" && streaming ? `This task ran for over ${Math.round(maxDurationMs / 6e4)} minutes in total, so I was terminated as a runaway backstop. I was still making progress \u2014 the task is just too big for one turn. Consider splitting it into smaller steps, or continue and I'll pick up where I left off.` : `I got stuck for over ${Math.round((timeoutKind === "idle" ? idleTimeoutMs : maxDurationMs) / 6e4)} minutes with no activity and had to be terminated. Most likely cause: a Bash tool call to a long-running process (dev server, watcher, \`tail -f\`, etc.) that never exits. If you wanted to start a server, ask me to run it in the background (\`nohup ... > /tmp/log 2>&1 & disown\`) and I'll come back with the PID.`;
|
|
1680
|
-
|
|
1946
|
+
resolve7({
|
|
1681
1947
|
sessionId: liveSessionId ?? resumeSessionId ?? "",
|
|
1682
1948
|
text,
|
|
1683
1949
|
isError: true,
|
|
@@ -1705,16 +1971,16 @@ function runCli(prompt, cwd, botName, options, claudeConfigDir, resumeSessionId,
|
|
|
1705
1971
|
options.model
|
|
1706
1972
|
);
|
|
1707
1973
|
recordUsage2(response, options);
|
|
1708
|
-
|
|
1974
|
+
resolve7(response);
|
|
1709
1975
|
} catch {
|
|
1710
1976
|
const fallback = onLine ? "" : stdout.trim();
|
|
1711
|
-
|
|
1977
|
+
resolve7({ sessionId: resumeSessionId ?? "", text: fallback || "No response" });
|
|
1712
1978
|
}
|
|
1713
1979
|
});
|
|
1714
1980
|
});
|
|
1715
1981
|
}
|
|
1716
1982
|
async function askClaude(prompt, cwd, botName, options, claudeConfigDir, resumeSessionId, claudeCodeOauthToken) {
|
|
1717
|
-
if (!
|
|
1983
|
+
if (!existsSync8(cwd) || !statSync3(cwd).isDirectory()) {
|
|
1718
1984
|
console.error(`[agent] working directory does not exist: ${cwd}`);
|
|
1719
1985
|
return {
|
|
1720
1986
|
sessionId: resumeSessionId ?? "",
|
|
@@ -1803,9 +2069,14 @@ var workspaceCache = /* @__PURE__ */ new Map();
|
|
|
1803
2069
|
async function getWorkspaceBySlug(slug) {
|
|
1804
2070
|
const cached = workspaceCache.get(slug);
|
|
1805
2071
|
if (cached && cached.expiresAt > Date.now()) return cached.workspace;
|
|
1806
|
-
const { data, error } = await getSupabaseClient().from("workspaces").select("id, slug, agent_connected_at").eq("slug", slug).maybeSingle();
|
|
2072
|
+
const { data, error } = await getSupabaseClient().from("workspaces").select("id, slug, agent_connected_at, projects_setup_at").eq("slug", slug).maybeSingle();
|
|
1807
2073
|
if (error) throw new Error(`getWorkspaceBySlug(${slug}): ${error.message}`);
|
|
1808
|
-
const workspace = data ? {
|
|
2074
|
+
const workspace = data ? {
|
|
2075
|
+
id: data.id,
|
|
2076
|
+
slug: data.slug,
|
|
2077
|
+
agentConnectedAt: data.agent_connected_at,
|
|
2078
|
+
projectsSetupAt: data.projects_setup_at
|
|
2079
|
+
} : null;
|
|
1809
2080
|
workspaceCache.set(slug, { workspace, expiresAt: Date.now() + WORKSPACE_CACHE_TTL_MS });
|
|
1810
2081
|
return workspace;
|
|
1811
2082
|
}
|
|
@@ -1863,8 +2134,17 @@ async function setWorkspaceStripeSubscription(workspaceId, fields) {
|
|
|
1863
2134
|
if (error) throw new Error(`setWorkspaceStripeSubscription(${workspaceId}): ${error.message}`);
|
|
1864
2135
|
}
|
|
1865
2136
|
async function markWorkspaceAgentConnected(workspaceId) {
|
|
1866
|
-
|
|
1867
|
-
|
|
2137
|
+
await stampOnce(workspaceId, "agent_connected_at");
|
|
2138
|
+
}
|
|
2139
|
+
async function markWorkspaceProjectsSetup(workspaceId) {
|
|
2140
|
+
await stampOnce(workspaceId, "projects_setup_at");
|
|
2141
|
+
}
|
|
2142
|
+
async function stampOnce(workspaceId, column) {
|
|
2143
|
+
const { error } = await getSupabaseClient().from("workspaces").update({ [column]: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", workspaceId).is(column, null);
|
|
2144
|
+
if (error) throw new Error(`stampOnce(${workspaceId}, ${column}): ${error.message}`);
|
|
2145
|
+
for (const [slug, entry] of workspaceCache) {
|
|
2146
|
+
if (entry.workspace?.id === workspaceId) workspaceCache.delete(slug);
|
|
2147
|
+
}
|
|
1868
2148
|
}
|
|
1869
2149
|
async function getWorkspaceByStripeCustomerId(stripeCustomerId) {
|
|
1870
2150
|
const { data, error } = await getSupabaseClient().from("workspaces").select("id, slug").eq("stripe_customer_id", stripeCustomerId).maybeSingle();
|
|
@@ -1963,6 +2243,21 @@ async function createMembership(params) {
|
|
|
1963
2243
|
if (error) throw new Error(`createMembership(${params.email}): ${error.message}`);
|
|
1964
2244
|
return rowToMembership(data);
|
|
1965
2245
|
}
|
|
2246
|
+
var HANDLE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,31}$/;
|
|
2247
|
+
var HandleTakenError = class extends Error {
|
|
2248
|
+
constructor() {
|
|
2249
|
+
super("that handle is already taken");
|
|
2250
|
+
this.name = "HandleTakenError";
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
async function setMembershipHandle(workspaceId, userId, handle) {
|
|
2254
|
+
const { data, error } = await getSupabaseClient().from("memberships").update({ handle }).eq("workspace_id", workspaceId).eq("user_id", userId).select("*").maybeSingle();
|
|
2255
|
+
if (error) {
|
|
2256
|
+
if (error.code === "23505") throw new HandleTakenError();
|
|
2257
|
+
throw new Error(`setMembershipHandle(${workspaceId}, ${userId}): ${error.message}`);
|
|
2258
|
+
}
|
|
2259
|
+
return data ? rowToMembership(data) : null;
|
|
2260
|
+
}
|
|
1966
2261
|
var LastAdminError = class extends Error {
|
|
1967
2262
|
constructor() {
|
|
1968
2263
|
super("workspace must have at least one admin");
|
|
@@ -2035,98 +2330,97 @@ async function setAgentPaused(workspaceId, paused) {
|
|
|
2035
2330
|
if (error) throw new Error(`setAgentPaused(${workspaceId}): ${error.message}`);
|
|
2036
2331
|
}
|
|
2037
2332
|
|
|
2038
|
-
// src/
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
var
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
if (error) throw new Error(`Failed to list output styles: ${error.message}`);
|
|
2062
|
-
return (data ?? []).map(rowToOutputStyle);
|
|
2063
|
-
}
|
|
2064
|
-
async function getOutputStyle(workspaceId, id) {
|
|
2065
|
-
const { data, error } = await getSupabaseClient().from("output_styles").select("*").eq("workspace_id", workspaceId).eq("id", id).maybeSingle();
|
|
2066
|
-
if (error) throw new Error(`Failed to load output style ${id}: ${error.message}`);
|
|
2067
|
-
return data ? rowToOutputStyle(data) : null;
|
|
2068
|
-
}
|
|
2069
|
-
async function insertOutputStyle(params) {
|
|
2070
|
-
const { data, error } = await getSupabaseClient().from("output_styles").insert({
|
|
2071
|
-
workspace_id: params.workspaceId,
|
|
2072
|
-
name: params.name,
|
|
2073
|
-
description: params.description,
|
|
2074
|
-
body: params.body
|
|
2075
|
-
}).select("*").single();
|
|
2076
|
-
if (error) throw new Error(`Failed to create output style: ${error.message}`);
|
|
2077
|
-
return rowToOutputStyle(data);
|
|
2078
|
-
}
|
|
2079
|
-
async function updateOutputStyle(workspaceId, id, patch) {
|
|
2080
|
-
const update = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2081
|
-
if (patch.name !== void 0) update["name"] = patch.name;
|
|
2082
|
-
if (patch.description !== void 0) update["description"] = patch.description;
|
|
2083
|
-
if (patch.body !== void 0) update["body"] = patch.body;
|
|
2084
|
-
const { data, error } = await getSupabaseClient().from("output_styles").update(update).eq("id", id).eq("workspace_id", workspaceId).select("*").maybeSingle();
|
|
2085
|
-
if (error) throw new Error(`Failed to update output style: ${error.message}`);
|
|
2086
|
-
return data ? rowToOutputStyle(data) : null;
|
|
2087
|
-
}
|
|
2088
|
-
async function deleteOutputStyle(workspaceId, id) {
|
|
2089
|
-
const { data, error } = await getSupabaseClient().from("output_styles").delete().eq("id", id).eq("workspace_id", workspaceId).select("id").maybeSingle();
|
|
2090
|
-
if (error) throw new Error(`Failed to delete output style: ${error.message}`);
|
|
2091
|
-
return !!data;
|
|
2092
|
-
}
|
|
2093
|
-
async function getWorkspaceOutputStyleDefault(workspaceId) {
|
|
2094
|
-
const { data, error } = await getSupabaseClient().from("workspaces").select("output_style_default").eq("id", workspaceId).maybeSingle();
|
|
2095
|
-
if (error) {
|
|
2096
|
-
throw new Error(`Failed to load the workspace's default output style: ${error.message}`);
|
|
2097
|
-
}
|
|
2098
|
-
return data?.["output_style_default"] ?? "default";
|
|
2099
|
-
}
|
|
2100
|
-
async function setWorkspaceOutputStyleDefault(workspaceId, id) {
|
|
2101
|
-
const { error } = await getSupabaseClient().from("workspaces").update({ output_style_default: id }).eq("id", workspaceId);
|
|
2102
|
-
if (error) {
|
|
2103
|
-
throw new Error(`Failed to set the workspace's default output style: ${error.message}`);
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
async function getEffectiveOutputStyle(workspaceId) {
|
|
2107
|
-
const defaultId = await getWorkspaceOutputStyleDefault(workspaceId);
|
|
2108
|
-
if (defaultId === "default") return void 0;
|
|
2109
|
-
const nativeStyleName = NATIVE_OUTPUT_STYLE_NAMES[defaultId];
|
|
2110
|
-
if (nativeStyleName) return { nativeStyleName, body: BUILT_IN_STYLE_BODIES[defaultId] };
|
|
2111
|
-
if (defaultId in BUILT_IN_STYLE_BODIES) return { body: BUILT_IN_STYLE_BODIES[defaultId] };
|
|
2112
|
-
const style = await getOutputStyle(workspaceId, defaultId);
|
|
2113
|
-
return style?.body ? { body: style.body } : void 0;
|
|
2333
|
+
// src/dashboard/settings.ts
|
|
2334
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
|
|
2335
|
+
import { dirname, resolve as resolve5 } from "path";
|
|
2336
|
+
|
|
2337
|
+
// src/dashboard/auto-title.ts
|
|
2338
|
+
var GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions";
|
|
2339
|
+
var TITLE_MODEL = "openai/gpt-oss-20b";
|
|
2340
|
+
var TIMEOUT_MS = 4e3;
|
|
2341
|
+
var MAX_BODY_CHARS = 4e3;
|
|
2342
|
+
var MAX_THREAD_CHARS = 12e3;
|
|
2343
|
+
var THREAD_TIMEOUT_MS = 15e3;
|
|
2344
|
+
var SUBJECT_MAX_CHARS = 30;
|
|
2345
|
+
var CATEGORY_MAX_CHARS = 12;
|
|
2346
|
+
var DEFAULT_TITLE_RULES = `- category: ONE short word or phrase, e.g. Bug, Feature, Question, Design, Ops, Docs, Idea. Reuse a category from the provided list whenever one fits; invent a new one only if none does.
|
|
2347
|
+
- subject: the most concise noun phrase that names the topic, as short as possible (aim for 3 to 6 words, or under 15 Chinese characters). Keep only the core topic: drop filler, background, verbs like "help me" or "how to", and details already implied by the category or channel. Not a copied sentence, no quotes, no Markdown, no trailing punctuation.
|
|
2348
|
+
- If the Workspace or Channel instructions contain title or naming rules, follow them over the defaults above.
|
|
2349
|
+
- Keep the message's own language. If it is Traditional Chinese (Taiwan), use Taiwan wording and keep technical terms in English.
|
|
2350
|
+
- Never use em dashes or en dashes.
|
|
2351
|
+
- When given a whole conversation, weight its END: if the goal changed along the way, name the current one. Ignore status noise such as stopped or cancelled run markers, error notices and progress placeholders.`;
|
|
2352
|
+
function systemPrompt(rules) {
|
|
2353
|
+
return `You label a new discussion thread. Reply with JSON only: {"category": string, "subject": string}. Hard limits: category max ${CATEGORY_MAX_CHARS} characters, subject max ${SUBJECT_MAX_CHARS} characters.
|
|
2354
|
+
|
|
2355
|
+
${rules}`;
|
|
2114
2356
|
}
|
|
2115
|
-
|
|
2357
|
+
function extractCategories(labels) {
|
|
2358
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2359
|
+
for (const label of labels) {
|
|
2360
|
+
const m = label?.match(/^\[([^\]]{1,24})\]/);
|
|
2361
|
+
if (m && m[1]) seen.add(m[1].trim());
|
|
2362
|
+
}
|
|
2363
|
+
return [...seen].slice(0, 15);
|
|
2364
|
+
}
|
|
2365
|
+
function clean(s, max) {
|
|
2366
|
+
if (typeof s !== "string") return "";
|
|
2367
|
+
const t = s.replace(/[`*_#"'“”「」]/g, "").replace(/[–—]/g, ",").replace(/\s+/g, " ").trim();
|
|
2368
|
+
return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
|
|
2369
|
+
}
|
|
2370
|
+
function formatAutoTitle(category, subject) {
|
|
2371
|
+
const cat = clean(category, CATEGORY_MAX_CHARS).replace(/[\[\]]/g, "");
|
|
2372
|
+
const sub = clean(subject, SUBJECT_MAX_CHARS);
|
|
2373
|
+
if (!cat || !sub) return null;
|
|
2374
|
+
return `[${cat}] ${sub}`;
|
|
2375
|
+
}
|
|
2376
|
+
async function buildAutoTitle(input) {
|
|
2377
|
+
const apiKey = process.env["GROQ_API_KEY"];
|
|
2378
|
+
if (!apiKey || !input.body.trim()) return null;
|
|
2116
2379
|
try {
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
|
|
2380
|
+
const whole = input.scope === "thread";
|
|
2381
|
+
const context = [
|
|
2382
|
+
`Channel: #${input.channelName}`,
|
|
2383
|
+
input.workspacePrompt ? `Workspace instructions:
|
|
2384
|
+
${input.workspacePrompt.slice(0, 1500)}` : "",
|
|
2385
|
+
input.channelPrompt ? `Channel instructions:
|
|
2386
|
+
${input.channelPrompt.slice(0, 1500)}` : "",
|
|
2387
|
+
`Existing categories: ${input.existingCategories.length ? input.existingCategories.join(", ") : "(none yet)"}`,
|
|
2388
|
+
whole ? `Conversation (latest last):
|
|
2389
|
+
${input.body.slice(-MAX_THREAD_CHARS)}` : `Opening message:
|
|
2390
|
+
${input.body.slice(0, MAX_BODY_CHARS)}`
|
|
2391
|
+
].filter(Boolean).join("\n\n");
|
|
2392
|
+
const res = await fetch(GROQ_API_URL, {
|
|
2393
|
+
method: "POST",
|
|
2394
|
+
signal: AbortSignal.timeout(whole ? THREAD_TIMEOUT_MS : TIMEOUT_MS),
|
|
2395
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
2396
|
+
body: JSON.stringify({
|
|
2397
|
+
model: TITLE_MODEL,
|
|
2398
|
+
temperature: 0.2,
|
|
2399
|
+
// Reasoning model: the cap covers reasoning tokens too, so keep it roomy.
|
|
2400
|
+
reasoning_effort: "low",
|
|
2401
|
+
max_tokens: 400,
|
|
2402
|
+
response_format: { type: "json_object" },
|
|
2403
|
+
messages: [
|
|
2404
|
+
{
|
|
2405
|
+
role: "system",
|
|
2406
|
+
content: systemPrompt(input.titlePrompt?.trim().slice(0, 2e3) || DEFAULT_TITLE_RULES)
|
|
2407
|
+
},
|
|
2408
|
+
{ role: "user", content: context }
|
|
2409
|
+
]
|
|
2410
|
+
})
|
|
2411
|
+
});
|
|
2412
|
+
if (!res.ok) throw new Error(`Groq request failed (${res.status})`);
|
|
2413
|
+
const data = await res.json();
|
|
2414
|
+
const raw = data.choices?.[0]?.message?.content;
|
|
2415
|
+
if (!raw) return null;
|
|
2416
|
+
const parsed = JSON.parse(raw);
|
|
2417
|
+
return formatAutoTitle(parsed.category, parsed.subject);
|
|
2120
2418
|
} catch (err) {
|
|
2121
|
-
console.error("
|
|
2122
|
-
return
|
|
2419
|
+
console.error("Auto title failed, keeping first-line fallback:", err);
|
|
2420
|
+
return null;
|
|
2123
2421
|
}
|
|
2124
2422
|
}
|
|
2125
2423
|
|
|
2126
|
-
// src/dashboard/settings.ts
|
|
2127
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, rmSync } from "fs";
|
|
2128
|
-
import { dirname, resolve as resolve4 } from "path";
|
|
2129
|
-
|
|
2130
2424
|
// src/store/workspace-settings-store.ts
|
|
2131
2425
|
var BRANDING_DEFAULTS = {
|
|
2132
2426
|
theme: "default",
|
|
@@ -2135,7 +2429,9 @@ var BRANDING_DEFAULTS = {
|
|
|
2135
2429
|
displayName: null,
|
|
2136
2430
|
gaMeasurementId: null,
|
|
2137
2431
|
botName: null,
|
|
2138
|
-
botAvatar: null
|
|
2432
|
+
botAvatar: null,
|
|
2433
|
+
autoTitleEnabled: true,
|
|
2434
|
+
autoTitlePrompt: null
|
|
2139
2435
|
};
|
|
2140
2436
|
function rowToBranding(row) {
|
|
2141
2437
|
return {
|
|
@@ -2147,7 +2443,9 @@ function rowToBranding(row) {
|
|
|
2147
2443
|
displayName: row["display_name"] ?? null,
|
|
2148
2444
|
gaMeasurementId: row["ga_measurement_id"] ?? null,
|
|
2149
2445
|
botName: row["bot_name"] ?? null,
|
|
2150
|
-
botAvatar: row["bot_avatar"] ?? null
|
|
2446
|
+
botAvatar: row["bot_avatar"] ?? null,
|
|
2447
|
+
autoTitleEnabled: row["auto_title_enabled"] !== false,
|
|
2448
|
+
autoTitlePrompt: row["auto_title_prompt"] ?? null
|
|
2151
2449
|
};
|
|
2152
2450
|
}
|
|
2153
2451
|
var CACHE_TTL_MS = 3e4;
|
|
@@ -2155,7 +2453,7 @@ var cache = /* @__PURE__ */ new Map();
|
|
|
2155
2453
|
async function getWorkspaceBranding(profile) {
|
|
2156
2454
|
const hit = cache.get(profile);
|
|
2157
2455
|
if (hit && Date.now() - hit.readAt < CACHE_TTL_MS) return hit.value;
|
|
2158
|
-
const { data, error } = await getSupabaseClient().from("workspace_settings").select("theme, font, logo, display_name, ga_measurement_id, bot_name, bot_avatar").eq("profile", profile).maybeSingle();
|
|
2456
|
+
const { data, error } = await getSupabaseClient().from("workspace_settings").select("theme, font, logo, display_name, ga_measurement_id, bot_name, bot_avatar, auto_title_enabled, auto_title_prompt").eq("profile", profile).maybeSingle();
|
|
2159
2457
|
if (error) throw new Error(`failed to read workspace settings: ${error.message}`);
|
|
2160
2458
|
const value = data ? rowToBranding(data) : null;
|
|
2161
2459
|
cache.set(profile, { value, readAt: Date.now() });
|
|
@@ -2170,7 +2468,9 @@ async function upsertWorkspaceBranding(profile, patch) {
|
|
|
2170
2468
|
displayName: patch.displayName === void 0 ? current.displayName : patch.displayName,
|
|
2171
2469
|
gaMeasurementId: patch.gaMeasurementId === void 0 ? current.gaMeasurementId : patch.gaMeasurementId,
|
|
2172
2470
|
botName: patch.botName === void 0 ? current.botName : patch.botName,
|
|
2173
|
-
botAvatar: patch.botAvatar === void 0 ? current.botAvatar : patch.botAvatar
|
|
2471
|
+
botAvatar: patch.botAvatar === void 0 ? current.botAvatar : patch.botAvatar,
|
|
2472
|
+
autoTitleEnabled: patch.autoTitleEnabled ?? current.autoTitleEnabled,
|
|
2473
|
+
autoTitlePrompt: patch.autoTitlePrompt === void 0 ? current.autoTitlePrompt : patch.autoTitlePrompt
|
|
2174
2474
|
};
|
|
2175
2475
|
const { error } = await getSupabaseClient().from("workspace_settings").upsert(
|
|
2176
2476
|
{
|
|
@@ -2182,6 +2482,8 @@ async function upsertWorkspaceBranding(profile, patch) {
|
|
|
2182
2482
|
ga_measurement_id: next.gaMeasurementId,
|
|
2183
2483
|
bot_name: next.botName,
|
|
2184
2484
|
bot_avatar: next.botAvatar,
|
|
2485
|
+
auto_title_enabled: next.autoTitleEnabled,
|
|
2486
|
+
auto_title_prompt: next.autoTitlePrompt,
|
|
2185
2487
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2186
2488
|
},
|
|
2187
2489
|
{ onConflict: "profile" }
|
|
@@ -2192,7 +2494,15 @@ async function upsertWorkspaceBranding(profile, patch) {
|
|
|
2192
2494
|
}
|
|
2193
2495
|
|
|
2194
2496
|
// src/dashboard/settings.ts
|
|
2195
|
-
var VALID_THEMES = [
|
|
2497
|
+
var VALID_THEMES = [
|
|
2498
|
+
"default",
|
|
2499
|
+
"classic",
|
|
2500
|
+
"sanctuary",
|
|
2501
|
+
"light",
|
|
2502
|
+
"sanctuary-light",
|
|
2503
|
+
"folio",
|
|
2504
|
+
"cheer"
|
|
2505
|
+
];
|
|
2196
2506
|
var VALID_FONTS = [
|
|
2197
2507
|
"default",
|
|
2198
2508
|
"system",
|
|
@@ -2200,7 +2510,8 @@ var VALID_FONTS = [
|
|
|
2200
2510
|
"exo2",
|
|
2201
2511
|
"lato",
|
|
2202
2512
|
"jakarta",
|
|
2203
|
-
"playfair"
|
|
2513
|
+
"playfair",
|
|
2514
|
+
"inter"
|
|
2204
2515
|
];
|
|
2205
2516
|
var LEGACY_AGENT_TO_MODEL = {
|
|
2206
2517
|
claude: "sonnet",
|
|
@@ -2240,7 +2551,8 @@ function normalizeProviders(entries) {
|
|
|
2240
2551
|
for (const runner of KNOWN_RUNNERS) {
|
|
2241
2552
|
if (!seen.has(runner)) chain.push({ runner });
|
|
2242
2553
|
}
|
|
2243
|
-
if (!chain[0].model)
|
|
2554
|
+
if (!chain[0].model)
|
|
2555
|
+
chain[0] = { runner: chain[0].runner, model: firstModelFor(chain[0].runner) };
|
|
2244
2556
|
return chain;
|
|
2245
2557
|
}
|
|
2246
2558
|
function parseProviders(raw, legacy) {
|
|
@@ -2277,9 +2589,9 @@ function parseAgentProfiles(raw) {
|
|
|
2277
2589
|
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
2278
2590
|
}
|
|
2279
2591
|
function loadDashboardSettings() {
|
|
2280
|
-
if (!
|
|
2592
|
+
if (!existsSync9(paths.DASHBOARD_SETTINGS_FILE)) return localDefaults();
|
|
2281
2593
|
try {
|
|
2282
|
-
const raw = JSON.parse(
|
|
2594
|
+
const raw = JSON.parse(readFileSync4(paths.DASHBOARD_SETTINGS_FILE, "utf-8"));
|
|
2283
2595
|
const providers = parseProviders(raw.providers, {
|
|
2284
2596
|
agent: normalizeDefaultModel(raw.agent, LOCAL_DEFAULTS.agent),
|
|
2285
2597
|
fallbackModel: typeof raw.fallbackModel === "string" && isAllowedModel(raw.fallbackModel) ? raw.fallbackModel : void 0
|
|
@@ -2293,6 +2605,7 @@ function loadDashboardSettings() {
|
|
|
2293
2605
|
return localDefaults();
|
|
2294
2606
|
}
|
|
2295
2607
|
}
|
|
2608
|
+
var AUTO_TITLE_PROMPT_MAX = 2e3;
|
|
2296
2609
|
function withDefaultModel(providers, model) {
|
|
2297
2610
|
const runner = runnerForModel(model);
|
|
2298
2611
|
const rest = providers.filter((p) => p.runner !== runner);
|
|
@@ -2306,18 +2619,18 @@ function withFallbackModel(providers, model) {
|
|
|
2306
2619
|
return [cleared[0], { runner, model }, ...cleared.slice(1).filter((p) => p.runner !== runner)];
|
|
2307
2620
|
}
|
|
2308
2621
|
function readSettingsFile() {
|
|
2309
|
-
if (!
|
|
2622
|
+
if (!existsSync9(paths.DASHBOARD_SETTINGS_FILE)) return null;
|
|
2310
2623
|
try {
|
|
2311
|
-
const raw = JSON.parse(
|
|
2624
|
+
const raw = JSON.parse(readFileSync4(paths.DASHBOARD_SETTINGS_FILE, "utf-8"));
|
|
2312
2625
|
return raw && typeof raw === "object" ? raw : null;
|
|
2313
2626
|
} catch {
|
|
2314
2627
|
return null;
|
|
2315
2628
|
}
|
|
2316
2629
|
}
|
|
2317
2630
|
function writeSettings(next) {
|
|
2318
|
-
|
|
2631
|
+
mkdirSync6(dirname(paths.DASHBOARD_SETTINGS_FILE), { recursive: true });
|
|
2319
2632
|
const merged = { ...readSettingsFile() ?? {}, ...next };
|
|
2320
|
-
|
|
2633
|
+
writeFileSync3(paths.DASHBOARD_SETTINGS_FILE, JSON.stringify(merged, null, 2), "utf-8");
|
|
2321
2634
|
return next;
|
|
2322
2635
|
}
|
|
2323
2636
|
function saveDashboardSettings(patch) {
|
|
@@ -2347,7 +2660,9 @@ function brandingToSettings(branding) {
|
|
|
2347
2660
|
font: branding.font,
|
|
2348
2661
|
logo: branding.logo ?? void 0,
|
|
2349
2662
|
displayName: branding.displayName ?? void 0,
|
|
2350
|
-
gaMeasurementId: branding.gaMeasurementId ?? void 0
|
|
2663
|
+
gaMeasurementId: branding.gaMeasurementId ?? void 0,
|
|
2664
|
+
autoTitleEnabled: branding.autoTitleEnabled,
|
|
2665
|
+
autoTitlePrompt: branding.autoTitlePrompt ?? void 0
|
|
2351
2666
|
};
|
|
2352
2667
|
}
|
|
2353
2668
|
function legacyBranding(raw) {
|
|
@@ -2373,25 +2688,31 @@ async function saveWorkspaceBranding(patch, profile = getProfile()) {
|
|
|
2373
2688
|
font: patch.font && VALID_FONTS.includes(patch.font) ? patch.font : current.font,
|
|
2374
2689
|
logo: patch.logo === null ? null : typeof patch.logo === "string" ? patch.logo : void 0,
|
|
2375
2690
|
displayName: patch.displayName === null || patch.displayName === "" ? null : typeof patch.displayName === "string" ? patch.displayName.trim() : void 0,
|
|
2376
|
-
gaMeasurementId: patch.gaMeasurementId === null || patch.gaMeasurementId === "" ? null : typeof patch.gaMeasurementId === "string" ? parseGaMeasurementId(patch.gaMeasurementId) : void 0
|
|
2691
|
+
gaMeasurementId: patch.gaMeasurementId === null || patch.gaMeasurementId === "" ? null : typeof patch.gaMeasurementId === "string" ? parseGaMeasurementId(patch.gaMeasurementId) : void 0,
|
|
2692
|
+
autoTitleEnabled: typeof patch.autoTitleEnabled === "boolean" ? patch.autoTitleEnabled : void 0,
|
|
2693
|
+
autoTitlePrompt: patch.autoTitlePrompt === null || patch.autoTitlePrompt === "" ? null : typeof patch.autoTitlePrompt === "string" ? patch.autoTitlePrompt.trim().slice(0, AUTO_TITLE_PROMPT_MAX) || null : void 0
|
|
2377
2694
|
};
|
|
2378
2695
|
return brandingToSettings(await upsertWorkspaceBranding(profile, next));
|
|
2379
2696
|
}
|
|
2380
2697
|
async function loadFullDashboardSettings(profile = getProfile()) {
|
|
2381
|
-
return {
|
|
2698
|
+
return {
|
|
2699
|
+
...await loadWorkspaceBranding(profile),
|
|
2700
|
+
...loadDashboardSettings(),
|
|
2701
|
+
autoTitleDefaultPrompt: DEFAULT_TITLE_RULES
|
|
2702
|
+
};
|
|
2382
2703
|
}
|
|
2383
2704
|
function defaultPromptPath() {
|
|
2384
|
-
return
|
|
2705
|
+
return resolve5(paths.PACKAGE_ROOT, "system.default.md");
|
|
2385
2706
|
}
|
|
2386
2707
|
function readDefaultPrompt() {
|
|
2387
2708
|
const path = defaultPromptPath();
|
|
2388
|
-
return
|
|
2709
|
+
return existsSync9(path) ? readFileSync4(path, "utf-8").trim() : "";
|
|
2389
2710
|
}
|
|
2390
2711
|
function loadWorkspacePrompt() {
|
|
2391
2712
|
const defaultPrompt = readDefaultPrompt();
|
|
2392
|
-
const custom =
|
|
2713
|
+
const custom = existsSync9(paths.PROMPT_FILE);
|
|
2393
2714
|
return {
|
|
2394
|
-
prompt: custom ?
|
|
2715
|
+
prompt: custom ? readFileSync4(paths.PROMPT_FILE, "utf-8").trim() : defaultPrompt,
|
|
2395
2716
|
defaultPrompt,
|
|
2396
2717
|
custom
|
|
2397
2718
|
};
|
|
@@ -2399,13 +2720,13 @@ function loadWorkspacePrompt() {
|
|
|
2399
2720
|
function saveWorkspacePrompt(prompt) {
|
|
2400
2721
|
const trimmed = prompt.trim();
|
|
2401
2722
|
if (!trimmed || trimmed === readDefaultPrompt()) return resetWorkspacePrompt();
|
|
2402
|
-
|
|
2403
|
-
|
|
2723
|
+
mkdirSync6(dirname(paths.PROMPT_FILE), { recursive: true });
|
|
2724
|
+
writeFileSync3(paths.PROMPT_FILE, `${trimmed}
|
|
2404
2725
|
`, "utf-8");
|
|
2405
2726
|
return loadWorkspacePrompt();
|
|
2406
2727
|
}
|
|
2407
2728
|
function resetWorkspacePrompt() {
|
|
2408
|
-
|
|
2729
|
+
rmSync2(paths.PROMPT_FILE, { force: true });
|
|
2409
2730
|
return loadWorkspacePrompt();
|
|
2410
2731
|
}
|
|
2411
2732
|
function saveAgentProfile(name, patch) {
|
|
@@ -2445,9 +2766,9 @@ async function saveBotIdentity(patch, profile = getProfile()) {
|
|
|
2445
2766
|
|
|
2446
2767
|
// src/codex/agent.ts
|
|
2447
2768
|
import { spawn as spawn2 } from "child_process";
|
|
2448
|
-
import { existsSync as
|
|
2769
|
+
import { existsSync as existsSync10, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync4 } from "fs";
|
|
2449
2770
|
import { tmpdir } from "os";
|
|
2450
|
-
import { join as
|
|
2771
|
+
import { join as join5 } from "path";
|
|
2451
2772
|
var AUTH_ERROR_PATTERNS = [
|
|
2452
2773
|
/401 unauthorized/i,
|
|
2453
2774
|
/missing bearer or basic authentication/i,
|
|
@@ -2578,8 +2899,8 @@ function getMaxDurationMs2() {
|
|
|
2578
2899
|
}
|
|
2579
2900
|
function codexAuthAvailable(env = process.env) {
|
|
2580
2901
|
if (env["OPENAI_API_KEY"]?.trim()) return true;
|
|
2581
|
-
const home = env["CODEX_HOME"]?.trim() ||
|
|
2582
|
-
return
|
|
2902
|
+
const home = env["CODEX_HOME"]?.trim() || join5(env["HOME"] ?? "", ".codex");
|
|
2903
|
+
return existsSync10(join5(home, "auth.json"));
|
|
2583
2904
|
}
|
|
2584
2905
|
function applySystemPrompt(prompt, options, resumeSessionId) {
|
|
2585
2906
|
if (resumeSessionId || !options.systemPrompt?.trim()) return prompt;
|
|
@@ -2628,13 +2949,13 @@ function recordCodexUsage(response, options) {
|
|
|
2628
2949
|
});
|
|
2629
2950
|
}
|
|
2630
2951
|
function runCodexCli(prompt, cwd, options, resumeSessionId) {
|
|
2631
|
-
return new Promise((
|
|
2952
|
+
return new Promise((resolve7, reject) => {
|
|
2632
2953
|
const finish = (response) => {
|
|
2633
2954
|
recordCodexUsage(response, options);
|
|
2634
|
-
|
|
2955
|
+
resolve7(response);
|
|
2635
2956
|
};
|
|
2636
|
-
const scratch = mkdtempSync(
|
|
2637
|
-
const lastMessageFile =
|
|
2957
|
+
const scratch = mkdtempSync(join5(tmpdir(), "flyspace-codex-"));
|
|
2958
|
+
const lastMessageFile = join5(scratch, "last-message.txt");
|
|
2638
2959
|
const args = buildArgs2(prompt, options, lastMessageFile, resumeSessionId);
|
|
2639
2960
|
console.log(`[codex] spawning codex CLI in ${cwd}`);
|
|
2640
2961
|
console.log(`[codex] args: ${args.slice(0, -1).join(" ")}`);
|
|
@@ -2704,7 +3025,7 @@ function runCodexCli(prompt, cwd, options, resumeSessionId) {
|
|
|
2704
3025
|
const cleanup = () => {
|
|
2705
3026
|
clearTimeout(timeoutHandle);
|
|
2706
3027
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
2707
|
-
|
|
3028
|
+
rmSync3(scratch, { recursive: true, force: true });
|
|
2708
3029
|
};
|
|
2709
3030
|
child.stdout.on("data", (data) => {
|
|
2710
3031
|
const chunk = data.toString();
|
|
@@ -2725,7 +3046,7 @@ function runCodexCli(prompt, cwd, options, resumeSessionId) {
|
|
|
2725
3046
|
child.on("close", (code) => {
|
|
2726
3047
|
let lastMessage = "";
|
|
2727
3048
|
try {
|
|
2728
|
-
lastMessage =
|
|
3049
|
+
lastMessage = readFileSync5(lastMessageFile, "utf8").trim();
|
|
2729
3050
|
} catch {
|
|
2730
3051
|
lastMessage = "";
|
|
2731
3052
|
}
|
|
@@ -2761,7 +3082,7 @@ function runCodexCli(prompt, cwd, options, resumeSessionId) {
|
|
|
2761
3082
|
});
|
|
2762
3083
|
}
|
|
2763
3084
|
async function askCodex(prompt, cwd, options = {}, resumeSessionId) {
|
|
2764
|
-
if (!
|
|
3085
|
+
if (!existsSync10(cwd) || !statSync4(cwd).isDirectory()) {
|
|
2765
3086
|
console.error(`[codex] working directory does not exist: ${cwd}`);
|
|
2766
3087
|
return {
|
|
2767
3088
|
sessionId: resumeSessionId ?? "",
|
|
@@ -3209,12 +3530,12 @@ function renderAsciiTable(t) {
|
|
|
3209
3530
|
(h, i) => Math.max(h.length, ...rows2.map((r) => (r[i] || "").length))
|
|
3210
3531
|
);
|
|
3211
3532
|
const fmt = (cells) => "| " + cells.map((c, i) => c.padEnd(widths[i])).join(" | ") + " |";
|
|
3212
|
-
const
|
|
3213
|
-
return [fmt(headers),
|
|
3533
|
+
const sep4 = "|" + widths.map((w) => "-".repeat(w + 2)).join("|") + "|";
|
|
3534
|
+
return [fmt(headers), sep4, ...rows2.map(fmt)].join("\n");
|
|
3214
3535
|
}
|
|
3215
3536
|
|
|
3216
3537
|
// src/store/channel-cache.ts
|
|
3217
|
-
import { mkdirSync as
|
|
3538
|
+
import { mkdirSync as mkdirSync7 } from "fs";
|
|
3218
3539
|
|
|
3219
3540
|
// src/store/sqlite.ts
|
|
3220
3541
|
import Database from "better-sqlite3";
|
|
@@ -3238,7 +3559,7 @@ function getDb() {
|
|
|
3238
3559
|
const file = paths.DB_FILE;
|
|
3239
3560
|
const existing = dbs.get(file);
|
|
3240
3561
|
if (existing) return existing;
|
|
3241
|
-
|
|
3562
|
+
mkdirSync7(paths.DATA_DIR, { recursive: true });
|
|
3242
3563
|
const db = openLocalDatabase(file);
|
|
3243
3564
|
if (!db) return null;
|
|
3244
3565
|
db.exec(`
|
|
@@ -3374,7 +3695,6 @@ async function runAutomation(opts) {
|
|
|
3374
3695
|
|
|
3375
3696
|
`;
|
|
3376
3697
|
try {
|
|
3377
|
-
const outputStyle = await resolveOutputStyleForProfile(getProfile());
|
|
3378
3698
|
const useCodex = (explicitModel ? runnerForModel(explicitModel) === "codex" : runnerForModel(loadDashboardSettings().agent) === "codex") && !requiresClaudeAutomationRunner(prompt);
|
|
3379
3699
|
const response = useCodex ? await askCodex(automationPrefix + prompt, cwd, {
|
|
3380
3700
|
...explicitModel && runnerForModel(explicitModel) === "codex" ? { model: explicitModel } : codexModel?.trim() ? { model: codexModel.trim() } : {},
|
|
@@ -3384,7 +3704,7 @@ async function runAutomation(opts) {
|
|
|
3384
3704
|
botName,
|
|
3385
3705
|
void 0,
|
|
3386
3706
|
resolveChannelPrompt(channel),
|
|
3387
|
-
|
|
3707
|
+
resolveSelectedOutputStyle(claudeConfigDir)?.body
|
|
3388
3708
|
),
|
|
3389
3709
|
// Same attribution as the Claude branch below — no userId, the
|
|
3390
3710
|
// schedule's name is the label.
|
|
@@ -3399,7 +3719,6 @@ async function runAutomation(opts) {
|
|
|
3399
3719
|
model,
|
|
3400
3720
|
maxBudgetUsd,
|
|
3401
3721
|
channelPrompt: resolveChannelPrompt(channel),
|
|
3402
|
-
outputStyle,
|
|
3403
3722
|
// No userId: nobody triggered this. The schedule's name is the
|
|
3404
3723
|
// label, which is what makes "which automation is burning tokens"
|
|
3405
3724
|
// answerable without joining anything.
|
|
@@ -3446,7 +3765,7 @@ var DEFAULT_TIMEOUT_MS = 12e4;
|
|
|
3446
3765
|
var KILL_GRACE_MS3 = 5e3;
|
|
3447
3766
|
function runScriptAutomation(opts) {
|
|
3448
3767
|
const { name, script, cwd, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
|
|
3449
|
-
return new Promise((
|
|
3768
|
+
return new Promise((resolve7, reject) => {
|
|
3450
3769
|
const child = spawn3("/bin/bash", [script], { cwd, env: resolveMiseEnv(cwd), stdio: ["ignore", "pipe", "pipe"] });
|
|
3451
3770
|
let stdout = "";
|
|
3452
3771
|
let stderr = "";
|
|
@@ -3479,7 +3798,7 @@ function runScriptAutomation(opts) {
|
|
|
3479
3798
|
} else if (code !== 0) {
|
|
3480
3799
|
reject(new Error(`script exited with code ${code}: ${stderr.trim()}`));
|
|
3481
3800
|
} else {
|
|
3482
|
-
|
|
3801
|
+
resolve7();
|
|
3483
3802
|
}
|
|
3484
3803
|
});
|
|
3485
3804
|
});
|
|
@@ -3835,17 +4154,17 @@ var TriggerEngine = class {
|
|
|
3835
4154
|
};
|
|
3836
4155
|
|
|
3837
4156
|
// src/store/user-cache.ts
|
|
3838
|
-
import { existsSync as
|
|
3839
|
-
import { join as
|
|
4157
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
|
|
4158
|
+
import { join as join6 } from "path";
|
|
3840
4159
|
function jsonUserCachePath() {
|
|
3841
|
-
const file =
|
|
3842
|
-
return
|
|
4160
|
+
const file = join6(paths.CONFIG_DIR, "slack_users.json");
|
|
4161
|
+
return existsSync11(file) ? file : void 0;
|
|
3843
4162
|
}
|
|
3844
4163
|
function readJsonUserCache() {
|
|
3845
4164
|
const file = jsonUserCachePath();
|
|
3846
4165
|
if (!file) return void 0;
|
|
3847
4166
|
try {
|
|
3848
|
-
const parsed = JSON.parse(
|
|
4167
|
+
const parsed = JSON.parse(readFileSync6(file, "utf8"));
|
|
3849
4168
|
return parsed.users;
|
|
3850
4169
|
} catch {
|
|
3851
4170
|
return void 0;
|
|
@@ -4237,8 +4556,8 @@ var MessageQueue = class {
|
|
|
4237
4556
|
const chains = Array.from(this.chains.values());
|
|
4238
4557
|
if (chains.length === 0) return true;
|
|
4239
4558
|
let timer;
|
|
4240
|
-
const timeout = new Promise((
|
|
4241
|
-
timer = setTimeout(() =>
|
|
4559
|
+
const timeout = new Promise((resolve7) => {
|
|
4560
|
+
timer = setTimeout(() => resolve7(false), timeoutMs);
|
|
4242
4561
|
});
|
|
4243
4562
|
const done = Promise.all(chains).then(() => true);
|
|
4244
4563
|
const result = await Promise.race([done, timeout]);
|
|
@@ -4252,7 +4571,7 @@ var MessageQueue = class {
|
|
|
4252
4571
|
|
|
4253
4572
|
// src/slack/file-downloader.ts
|
|
4254
4573
|
import { writeFile } from "fs/promises";
|
|
4255
|
-
import { join as
|
|
4574
|
+
import { join as join7, extname } from "path";
|
|
4256
4575
|
var debug3 = process.env["DEBUG"] === "true";
|
|
4257
4576
|
var SUPPORTED_IMAGE_MIMETYPES = /* @__PURE__ */ new Set([
|
|
4258
4577
|
"image/png",
|
|
@@ -4352,7 +4671,7 @@ async function downloadOne(file, botToken) {
|
|
|
4352
4671
|
const ext = extname(originalName) || `.${file.filetype ?? "bin"}`;
|
|
4353
4672
|
const safeName = sanitizeFilename(originalName.replace(ext, ""));
|
|
4354
4673
|
const filename = `${Date.now()}-${file.id ?? "unnamed"}-${safeName}${ext}`;
|
|
4355
|
-
const localPath =
|
|
4674
|
+
const localPath = join7(paths.UPLOADS_DIR, filename);
|
|
4356
4675
|
await writeFile(localPath, buffer);
|
|
4357
4676
|
if (debug3)
|
|
4358
4677
|
console.log(`[file-downloader] saved ${file.id} -> ${localPath} (${buffer.length} bytes)`);
|
|
@@ -4767,44 +5086,44 @@ async function summariseThreadTranscript(transcript, opts, deps = {}) {
|
|
|
4767
5086
|
}
|
|
4768
5087
|
|
|
4769
5088
|
// src/claude/skills.ts
|
|
4770
|
-
import { existsSync as
|
|
4771
|
-
import { homedir as
|
|
4772
|
-
import { basename as basename2, dirname as dirname2, join as
|
|
5089
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
5090
|
+
import { homedir as homedir4 } from "os";
|
|
5091
|
+
import { basename as basename2, dirname as dirname2, join as join9 } from "path";
|
|
4773
5092
|
import yaml3 from "js-yaml";
|
|
4774
5093
|
|
|
4775
5094
|
// src/claude/skill-files.ts
|
|
4776
5095
|
import {
|
|
4777
|
-
existsSync as
|
|
4778
|
-
mkdirSync as
|
|
4779
|
-
readFileSync as
|
|
4780
|
-
readdirSync,
|
|
4781
|
-
rmSync as
|
|
4782
|
-
statSync as
|
|
4783
|
-
writeFileSync as
|
|
5096
|
+
existsSync as existsSync12,
|
|
5097
|
+
mkdirSync as mkdirSync8,
|
|
5098
|
+
readFileSync as readFileSync7,
|
|
5099
|
+
readdirSync as readdirSync2,
|
|
5100
|
+
rmSync as rmSync4,
|
|
5101
|
+
statSync as statSync5,
|
|
5102
|
+
writeFileSync as writeFileSync4
|
|
4784
5103
|
} from "fs";
|
|
4785
|
-
import { homedir as
|
|
4786
|
-
import { join as
|
|
5104
|
+
import { homedir as homedir3 } from "os";
|
|
5105
|
+
import { join as join8, resolve as resolve6, sep as sep3 } from "path";
|
|
4787
5106
|
import yaml2 from "js-yaml";
|
|
4788
5107
|
var MAX_SKILL_BYTES = 2 * 1024 * 1024;
|
|
4789
5108
|
var SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
4790
5109
|
function expandHome(p) {
|
|
4791
|
-
if (p === "~") return
|
|
4792
|
-
if (p.startsWith("~/")) return
|
|
5110
|
+
if (p === "~") return homedir3();
|
|
5111
|
+
if (p.startsWith("~/")) return join8(homedir3(), p.slice(2));
|
|
4793
5112
|
return p;
|
|
4794
5113
|
}
|
|
4795
5114
|
function workspaceSkillsDir(configDir) {
|
|
4796
|
-
return
|
|
5115
|
+
return join8(configDir ? expandHome(configDir) : join8(homedir3(), ".claude"), "skills");
|
|
4797
5116
|
}
|
|
4798
5117
|
function channelSkillsDir(cwd) {
|
|
4799
|
-
return
|
|
5118
|
+
return join8(cwd, ".claude", "skills");
|
|
4800
5119
|
}
|
|
4801
5120
|
function isValidSkillName(name) {
|
|
4802
5121
|
return SKILL_NAME.test(name);
|
|
4803
5122
|
}
|
|
4804
5123
|
function resolveSkillPath(dir, name) {
|
|
4805
5124
|
if (!isValidSkillName(name)) return null;
|
|
4806
|
-
const abs =
|
|
4807
|
-
if (!abs.startsWith(
|
|
5125
|
+
const abs = resolve6(dir, name, "SKILL.md");
|
|
5126
|
+
if (!abs.startsWith(resolve6(dir) + sep3)) return null;
|
|
4808
5127
|
return abs;
|
|
4809
5128
|
}
|
|
4810
5129
|
function splitFrontmatter(raw) {
|
|
@@ -4831,7 +5150,7 @@ ${body.replace(/^\n+/, "").trimEnd()}
|
|
|
4831
5150
|
}
|
|
4832
5151
|
function readMeta(abs) {
|
|
4833
5152
|
try {
|
|
4834
|
-
const { fm } = splitFrontmatter(
|
|
5153
|
+
const { fm } = splitFrontmatter(readFileSync7(abs, "utf-8"));
|
|
4835
5154
|
return {
|
|
4836
5155
|
name: typeof fm["name"] === "string" ? fm["name"] : void 0,
|
|
4837
5156
|
description: typeof fm["description"] === "string" ? fm["description"] : void 0
|
|
@@ -4841,20 +5160,20 @@ function readMeta(abs) {
|
|
|
4841
5160
|
}
|
|
4842
5161
|
}
|
|
4843
5162
|
function listSkillFiles(dir) {
|
|
4844
|
-
if (!
|
|
5163
|
+
if (!existsSync12(dir)) return [];
|
|
4845
5164
|
let entries;
|
|
4846
5165
|
try {
|
|
4847
|
-
entries =
|
|
5166
|
+
entries = readdirSync2(dir);
|
|
4848
5167
|
} catch {
|
|
4849
5168
|
return [];
|
|
4850
5169
|
}
|
|
4851
5170
|
const rows2 = [];
|
|
4852
5171
|
for (const entry of entries) {
|
|
4853
5172
|
if (!isValidSkillName(entry)) continue;
|
|
4854
|
-
const abs =
|
|
5173
|
+
const abs = join8(dir, entry, "SKILL.md");
|
|
4855
5174
|
let stat;
|
|
4856
5175
|
try {
|
|
4857
|
-
stat =
|
|
5176
|
+
stat = statSync5(abs);
|
|
4858
5177
|
} catch {
|
|
4859
5178
|
continue;
|
|
4860
5179
|
}
|
|
@@ -4875,12 +5194,12 @@ function readSkillFile(dir, name) {
|
|
|
4875
5194
|
if (!abs) return null;
|
|
4876
5195
|
let stat;
|
|
4877
5196
|
try {
|
|
4878
|
-
stat =
|
|
5197
|
+
stat = statSync5(abs);
|
|
4879
5198
|
} catch {
|
|
4880
5199
|
return null;
|
|
4881
5200
|
}
|
|
4882
5201
|
if (!stat.isFile() || stat.size > MAX_SKILL_BYTES) return null;
|
|
4883
|
-
const { fm, body } = splitFrontmatter(
|
|
5202
|
+
const { fm, body } = splitFrontmatter(readFileSync7(abs, "utf-8"));
|
|
4884
5203
|
return {
|
|
4885
5204
|
name,
|
|
4886
5205
|
description: typeof fm["description"] === "string" ? fm["description"].trim() : "",
|
|
@@ -4894,7 +5213,7 @@ function writeSkillFile(dir, name, patch, baseMtime) {
|
|
|
4894
5213
|
if (!abs) return { status: "not-found" };
|
|
4895
5214
|
let stat;
|
|
4896
5215
|
try {
|
|
4897
|
-
stat =
|
|
5216
|
+
stat = statSync5(abs);
|
|
4898
5217
|
} catch {
|
|
4899
5218
|
return { status: "not-found" };
|
|
4900
5219
|
}
|
|
@@ -4902,18 +5221,18 @@ function writeSkillFile(dir, name, patch, baseMtime) {
|
|
|
4902
5221
|
if (baseMtime && stat.mtime.toISOString() !== baseMtime) {
|
|
4903
5222
|
return { status: "conflict", mtime: stat.mtime.toISOString() };
|
|
4904
5223
|
}
|
|
4905
|
-
const { fm } = splitFrontmatter(
|
|
5224
|
+
const { fm } = splitFrontmatter(readFileSync7(abs, "utf-8"));
|
|
4906
5225
|
const content = buildSkillFile({ ...fm, name, description: patch.description }, patch.body);
|
|
4907
5226
|
if (Buffer.byteLength(content, "utf8") > MAX_SKILL_BYTES) return { status: "too-large" };
|
|
4908
|
-
|
|
4909
|
-
return { status: "ok", mtime:
|
|
5227
|
+
writeFileSync4(abs, content, "utf8");
|
|
5228
|
+
return { status: "ok", mtime: statSync5(abs).mtime.toISOString() };
|
|
4910
5229
|
}
|
|
4911
5230
|
function createSkillFile(dir, name, description) {
|
|
4912
5231
|
const abs = resolveSkillPath(dir, name);
|
|
4913
5232
|
if (!abs) return { status: "bad-name" };
|
|
4914
|
-
if (
|
|
4915
|
-
|
|
4916
|
-
|
|
5233
|
+
if (existsSync12(abs)) return { status: "exists" };
|
|
5234
|
+
mkdirSync8(join8(dir, name), { recursive: true });
|
|
5235
|
+
writeFileSync4(
|
|
4917
5236
|
abs,
|
|
4918
5237
|
buildSkillFile(
|
|
4919
5238
|
{ name, description },
|
|
@@ -4929,24 +5248,24 @@ Describe what this skill does and when to use it.
|
|
|
4929
5248
|
}
|
|
4930
5249
|
function deleteSkillFile(dir, name) {
|
|
4931
5250
|
const abs = resolveSkillPath(dir, name);
|
|
4932
|
-
if (!abs || !
|
|
4933
|
-
|
|
5251
|
+
if (!abs || !existsSync12(abs)) return { status: "not-found" };
|
|
5252
|
+
rmSync4(join8(dir, name), { recursive: true, force: true });
|
|
4934
5253
|
return { status: "ok" };
|
|
4935
5254
|
}
|
|
4936
5255
|
|
|
4937
5256
|
// src/claude/skills.ts
|
|
4938
5257
|
function defaultConfigDir() {
|
|
4939
|
-
return
|
|
5258
|
+
return join9(homedir4(), ".claude");
|
|
4940
5259
|
}
|
|
4941
5260
|
function expandHome2(p) {
|
|
4942
|
-
if (p === "~") return
|
|
4943
|
-
if (p.startsWith("~/")) return
|
|
5261
|
+
if (p === "~") return homedir4();
|
|
5262
|
+
if (p.startsWith("~/")) return join9(homedir4(), p.slice(2));
|
|
4944
5263
|
return p;
|
|
4945
5264
|
}
|
|
4946
5265
|
function parseFrontmatter(file) {
|
|
4947
5266
|
let content;
|
|
4948
5267
|
try {
|
|
4949
|
-
content =
|
|
5268
|
+
content = readFileSync8(file, "utf-8");
|
|
4950
5269
|
} catch {
|
|
4951
5270
|
return {};
|
|
4952
5271
|
}
|
|
@@ -4964,25 +5283,25 @@ function parseFrontmatter(file) {
|
|
|
4964
5283
|
function isSymlinkedDir(root, entry) {
|
|
4965
5284
|
if (!entry.isSymbolicLink()) return false;
|
|
4966
5285
|
try {
|
|
4967
|
-
return
|
|
5286
|
+
return statSync6(join9(root, entry.name)).isDirectory();
|
|
4968
5287
|
} catch {
|
|
4969
5288
|
return false;
|
|
4970
5289
|
}
|
|
4971
5290
|
}
|
|
4972
5291
|
function findSkillFiles(root, maxDepth, out) {
|
|
4973
|
-
if (maxDepth < 0 || !
|
|
5292
|
+
if (maxDepth < 0 || !existsSync13(root)) return;
|
|
4974
5293
|
let entries;
|
|
4975
5294
|
try {
|
|
4976
|
-
entries =
|
|
5295
|
+
entries = readdirSync3(root, { withFileTypes: true });
|
|
4977
5296
|
} catch {
|
|
4978
5297
|
return;
|
|
4979
5298
|
}
|
|
4980
5299
|
for (const entry of entries) {
|
|
4981
5300
|
if (entry.isFile()) {
|
|
4982
|
-
if (entry.name === "SKILL.md") out.push(
|
|
5301
|
+
if (entry.name === "SKILL.md") out.push(join9(root, entry.name));
|
|
4983
5302
|
} else if (entry.isDirectory() || isSymlinkedDir(root, entry)) {
|
|
4984
5303
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
4985
|
-
findSkillFiles(
|
|
5304
|
+
findSkillFiles(join9(root, entry.name), maxDepth - 1, out);
|
|
4986
5305
|
}
|
|
4987
5306
|
}
|
|
4988
5307
|
}
|
|
@@ -4991,7 +5310,7 @@ function pluginNameFor(file, root) {
|
|
|
4991
5310
|
while (dir.startsWith(root) && dir !== dirname2(dir)) {
|
|
4992
5311
|
try {
|
|
4993
5312
|
const manifest = JSON.parse(
|
|
4994
|
-
|
|
5313
|
+
readFileSync8(join9(dir, ".claude-plugin", "plugin.json"), "utf-8")
|
|
4995
5314
|
);
|
|
4996
5315
|
if (typeof manifest.name === "string" && manifest.name.trim()) return manifest.name.trim();
|
|
4997
5316
|
} catch {
|
|
@@ -5020,10 +5339,10 @@ function collectFrom(root, source, maxDepth, namePrefix) {
|
|
|
5020
5339
|
function collectAll(configDir, cwd) {
|
|
5021
5340
|
const base = configDir ? expandHome2(configDir) : defaultConfigDir();
|
|
5022
5341
|
const collected = [
|
|
5023
|
-
...collectFrom(
|
|
5342
|
+
...collectFrom(join9(cwd, ".claude", "skills"), "project", 2),
|
|
5024
5343
|
...nestedProjectRoots(cwd).flatMap(({ dir, prefix }) => collectFrom(dir, "project", 2, prefix)),
|
|
5025
|
-
...collectFrom(
|
|
5026
|
-
...collectFrom(
|
|
5344
|
+
...collectFrom(join9(base, "skills"), "user", 2),
|
|
5345
|
+
...collectFrom(join9(base, "plugins"), "plugin", 8)
|
|
5027
5346
|
];
|
|
5028
5347
|
const byName = /* @__PURE__ */ new Map();
|
|
5029
5348
|
for (const skill of collected) {
|
|
@@ -5034,7 +5353,7 @@ function collectAll(configDir, cwd) {
|
|
|
5034
5353
|
function nestedProjectRoots(cwd) {
|
|
5035
5354
|
let entries;
|
|
5036
5355
|
try {
|
|
5037
|
-
entries =
|
|
5356
|
+
entries = readdirSync3(cwd, { withFileTypes: true });
|
|
5038
5357
|
} catch {
|
|
5039
5358
|
return [];
|
|
5040
5359
|
}
|
|
@@ -5042,8 +5361,8 @@ function nestedProjectRoots(cwd) {
|
|
|
5042
5361
|
for (const entry of entries) {
|
|
5043
5362
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
5044
5363
|
if (!entry.isDirectory() && !isSymlinkedDir(cwd, entry)) continue;
|
|
5045
|
-
const skillsDir =
|
|
5046
|
-
if (
|
|
5364
|
+
const skillsDir = join9(cwd, entry.name, ".claude", "skills");
|
|
5365
|
+
if (existsSync13(skillsDir)) roots.push({ dir: skillsDir, prefix: entry.name });
|
|
5047
5366
|
}
|
|
5048
5367
|
return roots;
|
|
5049
5368
|
}
|
|
@@ -5055,8 +5374,8 @@ function listPluginSkillDetails(configDir, cwd) {
|
|
|
5055
5374
|
for (const { file, ...skill } of collectAll(configDir, cwd)) {
|
|
5056
5375
|
if (skill.source !== "plugin") continue;
|
|
5057
5376
|
try {
|
|
5058
|
-
const { body } = splitFrontmatter(
|
|
5059
|
-
details.push({ ...skill, body, path: file, mtime:
|
|
5377
|
+
const { body } = splitFrontmatter(readFileSync8(file, "utf-8"));
|
|
5378
|
+
details.push({ ...skill, body, path: file, mtime: statSync6(file).mtime.toISOString() });
|
|
5060
5379
|
} catch {
|
|
5061
5380
|
}
|
|
5062
5381
|
}
|
|
@@ -5065,15 +5384,15 @@ function listPluginSkillDetails(configDir, cwd) {
|
|
|
5065
5384
|
function findSkillDetail(configDir, cwd, name, sources) {
|
|
5066
5385
|
const base = configDir ? expandHome2(configDir) : defaultConfigDir();
|
|
5067
5386
|
const roots = [
|
|
5068
|
-
[
|
|
5387
|
+
[join9(cwd, ".claude", "skills"), "project", 2],
|
|
5069
5388
|
...nestedProjectRoots(cwd).map(({ dir, prefix }) => [
|
|
5070
5389
|
dir,
|
|
5071
5390
|
"project",
|
|
5072
5391
|
2,
|
|
5073
5392
|
prefix
|
|
5074
5393
|
]),
|
|
5075
|
-
[
|
|
5076
|
-
[
|
|
5394
|
+
[join9(base, "skills"), "user", 2],
|
|
5395
|
+
[join9(base, "plugins"), "plugin", 8]
|
|
5077
5396
|
];
|
|
5078
5397
|
for (const [root, source, maxDepth, namePrefix] of roots) {
|
|
5079
5398
|
if (!sources.includes(source)) continue;
|
|
@@ -5087,8 +5406,8 @@ function findSkillDetail(configDir, cwd, name, sources) {
|
|
|
5087
5406
|
let raw;
|
|
5088
5407
|
let mtime;
|
|
5089
5408
|
try {
|
|
5090
|
-
raw =
|
|
5091
|
-
mtime =
|
|
5409
|
+
raw = readFileSync8(file, "utf-8");
|
|
5410
|
+
mtime = statSync6(file).mtime.toISOString();
|
|
5092
5411
|
} catch {
|
|
5093
5412
|
continue;
|
|
5094
5413
|
}
|
|
@@ -5579,7 +5898,6 @@ ${trimmedDetail.slice(0, 1500)}
|
|
|
5579
5898
|
const threadModelOverride = store.getModelOverride(channelId, sessionKey);
|
|
5580
5899
|
const effectiveModel = resolveModel(threadModelOverride, channelId, model);
|
|
5581
5900
|
const runner = threadModelOverride ? runnerForModel(threadModelOverride) : runnerForModel(loadDashboardSettings().agent);
|
|
5582
|
-
const outputStyle = await resolveOutputStyleForProfile(getProfile());
|
|
5583
5901
|
if (runner === "codex") {
|
|
5584
5902
|
const stored = store.getSession(channelId, sessionKey);
|
|
5585
5903
|
const resumeId = stored?.runner === "codex" ? stored.sessionId : void 0;
|
|
@@ -5593,7 +5911,7 @@ ${trimmedDetail.slice(0, 1500)}
|
|
|
5593
5911
|
botName,
|
|
5594
5912
|
void 0,
|
|
5595
5913
|
resolveChannelPrompt(channelId),
|
|
5596
|
-
|
|
5914
|
+
resolveSelectedOutputStyle(claudeConfigDir)?.body
|
|
5597
5915
|
),
|
|
5598
5916
|
signal: abortController.signal,
|
|
5599
5917
|
// Same attribution as the Claude path's `askOptions.usageContext`
|
|
@@ -5686,7 +6004,6 @@ ${trimmedDetail.slice(0, 1500)}
|
|
|
5686
6004
|
// workspace prompt for this channel only — same source the dashboard
|
|
5687
6005
|
// chat path reads through resolveChannelConfig().
|
|
5688
6006
|
channelPrompt: resolveChannelPrompt(channelId),
|
|
5689
|
-
outputStyle,
|
|
5690
6007
|
signal: abortController.signal,
|
|
5691
6008
|
// Attribution for the usage row each spawn writes. Sharing one options
|
|
5692
6009
|
// object across every attempt is deliberate: the retries below are
|
|
@@ -7848,13 +8165,13 @@ async function recoverStaleReactions(client, reactionStore) {
|
|
|
7848
8165
|
var ts2 = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
7849
8166
|
var POWER_POLL_MS = 3e4;
|
|
7850
8167
|
function isOnACPower() {
|
|
7851
|
-
return new Promise((
|
|
8168
|
+
return new Promise((resolve7) => {
|
|
7852
8169
|
execFile("pmset", ["-g", "ps"], { timeout: 2e3 }, (err, stdout) => {
|
|
7853
8170
|
if (err) {
|
|
7854
|
-
|
|
8171
|
+
resolve7(true);
|
|
7855
8172
|
return;
|
|
7856
8173
|
}
|
|
7857
|
-
|
|
8174
|
+
resolve7(stdout.includes("'AC Power'"));
|
|
7858
8175
|
});
|
|
7859
8176
|
});
|
|
7860
8177
|
}
|
|
@@ -7998,6 +8315,9 @@ export {
|
|
|
7998
8315
|
loadConfig,
|
|
7999
8316
|
refreshAutomationRegistry,
|
|
8000
8317
|
startAutomationRegistryPolling,
|
|
8318
|
+
isLogicalCwd,
|
|
8319
|
+
resolveLogicalCwd,
|
|
8320
|
+
ensureLogicalCwdDir,
|
|
8001
8321
|
expandPath,
|
|
8002
8322
|
canSeeChannel,
|
|
8003
8323
|
refreshChannelRegistry,
|
|
@@ -8012,13 +8332,25 @@ export {
|
|
|
8012
8332
|
resolveCwd,
|
|
8013
8333
|
resolveChannelPrompt,
|
|
8014
8334
|
createChannel,
|
|
8335
|
+
DefaultChannelError,
|
|
8015
8336
|
updateChannelById,
|
|
8016
8337
|
updateChannelsMcpConnections,
|
|
8017
8338
|
ensureAgentDmChannel,
|
|
8339
|
+
ensureGeneralChannel,
|
|
8340
|
+
ensureMemberDmChannel,
|
|
8018
8341
|
DEFAULT_TIMEZONE,
|
|
8019
8342
|
loadAutomations,
|
|
8020
8343
|
loadAutomationsFile,
|
|
8021
8344
|
loadEffectiveAutomations,
|
|
8345
|
+
BUILT_IN_OUTPUT_STYLES,
|
|
8346
|
+
OutputStyleConflictError,
|
|
8347
|
+
listOutputStyles,
|
|
8348
|
+
createOutputStyle,
|
|
8349
|
+
updateOutputStyle,
|
|
8350
|
+
deleteOutputStyle,
|
|
8351
|
+
resolveSelectedOutputStyle,
|
|
8352
|
+
getOutputStyleDefault,
|
|
8353
|
+
setOutputStyleDefault,
|
|
8022
8354
|
runnerForModel,
|
|
8023
8355
|
isCodexModel,
|
|
8024
8356
|
agentLabel,
|
|
@@ -8027,6 +8359,7 @@ export {
|
|
|
8027
8359
|
usageLimitedUntil,
|
|
8028
8360
|
usageLimitedModels,
|
|
8029
8361
|
buildSystemPrompt,
|
|
8362
|
+
docsAccessPromptNote,
|
|
8030
8363
|
askClaude,
|
|
8031
8364
|
getWorkspaceBySlug,
|
|
8032
8365
|
getWorkspaceById,
|
|
@@ -8036,6 +8369,7 @@ export {
|
|
|
8036
8369
|
deleteWorkspace,
|
|
8037
8370
|
setWorkspaceStripeSubscription,
|
|
8038
8371
|
markWorkspaceAgentConnected,
|
|
8372
|
+
markWorkspaceProjectsSetup,
|
|
8039
8373
|
getWorkspaceByStripeCustomerId,
|
|
8040
8374
|
getWorkspaceBilling,
|
|
8041
8375
|
updateWorkspacePlanStatus,
|
|
@@ -8046,6 +8380,9 @@ export {
|
|
|
8046
8380
|
listMembershipsByEmail,
|
|
8047
8381
|
listMemberships,
|
|
8048
8382
|
createMembership,
|
|
8383
|
+
HANDLE_PATTERN,
|
|
8384
|
+
HandleTakenError,
|
|
8385
|
+
setMembershipHandle,
|
|
8049
8386
|
LastAdminError,
|
|
8050
8387
|
updateMembershipRole,
|
|
8051
8388
|
markInviteResent,
|
|
@@ -8057,14 +8394,9 @@ export {
|
|
|
8057
8394
|
setAllowGuestAccess,
|
|
8058
8395
|
getAgentPaused,
|
|
8059
8396
|
setAgentPaused,
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
updateOutputStyle,
|
|
8064
|
-
deleteOutputStyle,
|
|
8065
|
-
getWorkspaceOutputStyleDefault,
|
|
8066
|
-
setWorkspaceOutputStyleDefault,
|
|
8067
|
-
resolveOutputStyleForProfile,
|
|
8397
|
+
DEFAULT_TITLE_RULES,
|
|
8398
|
+
extractCategories,
|
|
8399
|
+
buildAutoTitle,
|
|
8068
8400
|
BRANDING_DEFAULTS,
|
|
8069
8401
|
getWorkspaceBranding,
|
|
8070
8402
|
localDefaults,
|