flyspace-cli 0.1.33 → 0.1.35
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-KGX44L3V.js → chunk-KTMAS4TP.js} +10 -1
- package/dist/cli.js +573 -90
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -295,6 +295,15 @@ function resolveLogicalCwd(p) {
|
|
|
295
295
|
function isUnderLogicalRoot(cwd) {
|
|
296
296
|
return [logicalRoot(), ...workspaceRoots.values()].some((r) => cwd.startsWith(r + sep));
|
|
297
297
|
}
|
|
298
|
+
function toLogicalCwd(cwd) {
|
|
299
|
+
const fromRoot = relative(logicalRoot(), cwd).split(sep).join("/");
|
|
300
|
+
if (isLogicalCwd(fromRoot)) return fromRoot;
|
|
301
|
+
for (const [slug, root] of workspaceRoots) {
|
|
302
|
+
const name = relative(root, cwd);
|
|
303
|
+
if (isLogicalCwd(`${slug}/${name}`)) return `${slug}/${name}`;
|
|
304
|
+
}
|
|
305
|
+
return cwd;
|
|
306
|
+
}
|
|
298
307
|
function ensureLogicalCwdDir(cwd) {
|
|
299
308
|
if (!isUnderLogicalRoot(cwd) || existsSync2(cwd)) return;
|
|
300
309
|
mkdirSync(cwd, { recursive: true });
|
|
@@ -522,7 +531,7 @@ function resolveEffort(explicitEffort, channelId, fallback) {
|
|
|
522
531
|
function toRow(input) {
|
|
523
532
|
const row = {};
|
|
524
533
|
if (input.name !== void 0) row["name"] = input.name;
|
|
525
|
-
if (input.cwd !== void 0) row["cwd"] = isLogicalCwd(input.cwd) ? input.cwd : expandPath(input.cwd);
|
|
534
|
+
if (input.cwd !== void 0) row["cwd"] = isLogicalCwd(input.cwd) ? input.cwd : toLogicalCwd(expandPath(input.cwd));
|
|
526
535
|
if (input.categoryId !== void 0) row["category_id"] = input.categoryId;
|
|
527
536
|
if (input.externalChannelId !== void 0) row["external_channel_id"] = input.externalChannelId;
|
|
528
537
|
if (input.model !== void 0) row["model"] = input.model;
|
package/dist/cli.js
CHANGED
|
@@ -187,7 +187,7 @@ import {
|
|
|
187
187
|
warnOnBrokenChannelCwds,
|
|
188
188
|
workspaceSkillsDir,
|
|
189
189
|
writeSkillFile
|
|
190
|
-
} from "./chunk-
|
|
190
|
+
} from "./chunk-KTMAS4TP.js";
|
|
191
191
|
import {
|
|
192
192
|
firstReplyBody,
|
|
193
193
|
getThread,
|
|
@@ -621,14 +621,14 @@ function handleHostDbResult(id, res) {
|
|
|
621
621
|
}
|
|
622
622
|
function whenConnected() {
|
|
623
623
|
if (sender) return Promise.resolve(sender);
|
|
624
|
-
return new Promise((
|
|
624
|
+
return new Promise((resolve8, reject) => {
|
|
625
625
|
const timer2 = setTimeout(() => {
|
|
626
626
|
onSender = onSender.filter((w) => w !== wake);
|
|
627
627
|
reject(new Error("Host is not connected to the Data API"));
|
|
628
628
|
}, REQUEST_TIMEOUT_MS);
|
|
629
629
|
const wake = () => {
|
|
630
630
|
clearTimeout(timer2);
|
|
631
|
-
|
|
631
|
+
resolve8(sender);
|
|
632
632
|
};
|
|
633
633
|
onSender.push(wake);
|
|
634
634
|
});
|
|
@@ -636,12 +636,12 @@ function whenConnected() {
|
|
|
636
636
|
async function requestHostDb(req) {
|
|
637
637
|
const send2 = await whenConnected();
|
|
638
638
|
const id = randomUUID();
|
|
639
|
-
return new Promise((
|
|
639
|
+
return new Promise((resolve8, reject) => {
|
|
640
640
|
const timer2 = setTimeout(() => {
|
|
641
641
|
pending.delete(id);
|
|
642
642
|
reject(new Error("Data API did not answer in time"));
|
|
643
643
|
}, REQUEST_TIMEOUT_MS);
|
|
644
|
-
pending.set(id, { resolve:
|
|
644
|
+
pending.set(id, { resolve: resolve8, reject, timer: timer2 });
|
|
645
645
|
send2({ type: "db", id, req });
|
|
646
646
|
});
|
|
647
647
|
}
|
|
@@ -653,14 +653,14 @@ function handleAttachmentUrlResult(id, url) {
|
|
|
653
653
|
async function requestAttachmentUrl(key) {
|
|
654
654
|
const send2 = await whenConnected();
|
|
655
655
|
const id = randomUUID();
|
|
656
|
-
return new Promise((
|
|
656
|
+
return new Promise((resolve8, reject) => {
|
|
657
657
|
const timer2 = setTimeout(() => {
|
|
658
658
|
pendingUrls.delete(id);
|
|
659
659
|
reject(new Error("Data API did not answer in time"));
|
|
660
660
|
}, REQUEST_TIMEOUT_MS);
|
|
661
661
|
pendingUrls.set(id, (url) => {
|
|
662
662
|
clearTimeout(timer2);
|
|
663
|
-
|
|
663
|
+
resolve8(url);
|
|
664
664
|
});
|
|
665
665
|
send2({ type: "attachment-url", id, key });
|
|
666
666
|
});
|
|
@@ -807,10 +807,10 @@ function editConfig() {
|
|
|
807
807
|
const editor = process.env["EDITOR"] || "vi";
|
|
808
808
|
console.log(`[flyspace] Opening ${paths.CONFIG_FILE} in ${editor}...`);
|
|
809
809
|
const child = spawn(editor, [paths.CONFIG_FILE], { stdio: "inherit" });
|
|
810
|
-
return new Promise((
|
|
810
|
+
return new Promise((resolve8, reject) => {
|
|
811
811
|
child.on("close", (code) => {
|
|
812
812
|
if (code === 0) {
|
|
813
|
-
|
|
813
|
+
resolve8();
|
|
814
814
|
} else {
|
|
815
815
|
reject(new Error(`Editor exited with code ${code}`));
|
|
816
816
|
}
|
|
@@ -1861,7 +1861,7 @@ async function runAuthCmd(args) {
|
|
|
1861
1861
|
}
|
|
1862
1862
|
|
|
1863
1863
|
// ../api/src/version.ts
|
|
1864
|
-
var CLI_VERSION = true ? "0.1.
|
|
1864
|
+
var CLI_VERSION = true ? "0.1.35" : null;
|
|
1865
1865
|
function isOlderVersion(a, b) {
|
|
1866
1866
|
const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
1867
1867
|
const [pa, pb] = [parse(a), parse(b)];
|
|
@@ -2471,14 +2471,19 @@ function npmEnv(target) {
|
|
|
2471
2471
|
return nodeBin ? { ...process.env, PATH: `${nodeBin}:${process.env.PATH ?? ""}` } : process.env;
|
|
2472
2472
|
}
|
|
2473
2473
|
async function runUpgrade() {
|
|
2474
|
-
const current = "0.1.
|
|
2474
|
+
const current = "0.1.35";
|
|
2475
2475
|
console.log(`Current version: ${current}`);
|
|
2476
2476
|
console.log("Checking for updates...\n");
|
|
2477
2477
|
try {
|
|
2478
2478
|
const latest2 = execSync6(`npm view ${"flyspace-cli"} version`, { encoding: "utf-8", env: npmEnv() }).trim();
|
|
2479
2479
|
const stale = installTargets().filter((t) => installedVersion(t) !== latest2);
|
|
2480
2480
|
if (stale.length === 0) {
|
|
2481
|
-
|
|
2481
|
+
if (current === latest2) {
|
|
2482
|
+
console.log(`Already on the latest version (${current}).`);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
console.log(`${latest2} is installed but this process runs ${current}. Restarting...`);
|
|
2486
|
+
restartInstalledProfiles();
|
|
2482
2487
|
return;
|
|
2483
2488
|
}
|
|
2484
2489
|
console.log(`New version available: ${latest2}`);
|
|
@@ -4795,10 +4800,10 @@ var DEFAULT_DASHBOARD_PORT = 4747;
|
|
|
4795
4800
|
var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
|
|
4796
4801
|
async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
|
|
4797
4802
|
for (let port = start; port < start + attempts; port++) {
|
|
4798
|
-
const free = await new Promise((
|
|
4803
|
+
const free = await new Promise((resolve8) => {
|
|
4799
4804
|
const probe3 = createServer();
|
|
4800
|
-
probe3.once("error", () =>
|
|
4801
|
-
probe3.once("listening", () => probe3.close(() =>
|
|
4805
|
+
probe3.once("error", () => resolve8(false));
|
|
4806
|
+
probe3.once("listening", () => probe3.close(() => resolve8(true)));
|
|
4802
4807
|
probe3.listen(port, host);
|
|
4803
4808
|
});
|
|
4804
4809
|
if (free) return port;
|
|
@@ -5626,14 +5631,14 @@ async function stepBrowserTokens(botName) {
|
|
|
5626
5631
|
const appLink = page2.locator('a[href*="/apps/A"]').first();
|
|
5627
5632
|
const hasExistingApp = await appLink.isVisible({ timeout: 3e3 }).catch(() => false);
|
|
5628
5633
|
if (hasExistingApp) {
|
|
5629
|
-
const tokenPromise = new Promise((
|
|
5634
|
+
const tokenPromise = new Promise((resolve8) => {
|
|
5630
5635
|
page2.on("request", (req) => {
|
|
5631
5636
|
if (token) return;
|
|
5632
5637
|
const postData = req.postData() || "";
|
|
5633
5638
|
const match = postData.match(/xoxc-[a-f0-9-]+/);
|
|
5634
5639
|
if (match) {
|
|
5635
5640
|
token = match[0];
|
|
5636
|
-
|
|
5641
|
+
resolve8(token);
|
|
5637
5642
|
}
|
|
5638
5643
|
});
|
|
5639
5644
|
});
|
|
@@ -5684,7 +5689,7 @@ async function stepBrowserTokens(botName) {
|
|
|
5684
5689
|
log4("Almost done! The browser will now open an install page.\n");
|
|
5685
5690
|
console.log(' \x1B[1mPlease click "Install to Workspace", then click "Allow".\x1B[0m\n');
|
|
5686
5691
|
await page2.goto(installUrl);
|
|
5687
|
-
const installPage = await new Promise((
|
|
5692
|
+
const installPage = await new Promise((resolve8, reject) => {
|
|
5688
5693
|
let settled = false;
|
|
5689
5694
|
let interval;
|
|
5690
5695
|
let timeout;
|
|
@@ -5697,7 +5702,7 @@ async function stepBrowserTokens(botName) {
|
|
|
5697
5702
|
};
|
|
5698
5703
|
const checkUrl = (p) => {
|
|
5699
5704
|
if (p.url().includes("install-on-team?success=1")) {
|
|
5700
|
-
finish2(() =>
|
|
5705
|
+
finish2(() => resolve8(p));
|
|
5701
5706
|
}
|
|
5702
5707
|
};
|
|
5703
5708
|
context.on("page", (newPage) => {
|
|
@@ -6411,9 +6416,9 @@ async function runMcpCmd(args) {
|
|
|
6411
6416
|
|
|
6412
6417
|
// src/commands/dashboard.ts
|
|
6413
6418
|
import { spawn as spawn8, spawnSync } from "child_process";
|
|
6414
|
-
import { existsSync as
|
|
6419
|
+
import { existsSync as existsSync34 } from "fs";
|
|
6415
6420
|
import { platform as platform7 } from "os";
|
|
6416
|
-
import { join as
|
|
6421
|
+
import { join as join29 } from "path";
|
|
6417
6422
|
|
|
6418
6423
|
// src/reconcile.ts
|
|
6419
6424
|
import { existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
@@ -6586,7 +6591,7 @@ var requireDocsAccess = async (c, next) => {
|
|
|
6586
6591
|
};
|
|
6587
6592
|
|
|
6588
6593
|
// ../api/src/dashboard/api.ts
|
|
6589
|
-
import { Hono as
|
|
6594
|
+
import { Hono as Hono31 } from "hono";
|
|
6590
6595
|
|
|
6591
6596
|
// ../api/src/dashboard/public-share-routes.ts
|
|
6592
6597
|
import { existsSync as existsSync17, readFileSync as readFileSync16, statSync as statSync3 } from "fs";
|
|
@@ -7651,7 +7656,7 @@ function truncationNotice(lang, voice) {
|
|
|
7651
7656
|
if (lang === "en" || lang.startsWith("en-")) return TRUNCATION_NOTICE.en;
|
|
7652
7657
|
return null;
|
|
7653
7658
|
}
|
|
7654
|
-
var sleep2 = (ms) => new Promise((
|
|
7659
|
+
var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
7655
7660
|
async function synthesizeWithRetry(script, voice) {
|
|
7656
7661
|
let lastError;
|
|
7657
7662
|
for (let attempt = 1; attempt <= SYNTH_ATTEMPTS; attempt += 1) {
|
|
@@ -13992,14 +13997,244 @@ function createScopedMemoriesRouter() {
|
|
|
13992
13997
|
return router;
|
|
13993
13998
|
}
|
|
13994
13999
|
|
|
14000
|
+
// ../api/src/dashboard/session-import-routes.ts
|
|
14001
|
+
import { Hono as Hono28 } from "hono";
|
|
14002
|
+
var MAX_FOLDERS2 = 20;
|
|
14003
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
14004
|
+
var IMPORT_RANGES = ["all", "7d", "latest5"];
|
|
14005
|
+
async function ask(agent, req) {
|
|
14006
|
+
const res = await agent.sessionImportRequest?.(req);
|
|
14007
|
+
return res && res.op === req.op ? res : null;
|
|
14008
|
+
}
|
|
14009
|
+
function hostUnavailable2(c, agent) {
|
|
14010
|
+
return c.json(
|
|
14011
|
+
{
|
|
14012
|
+
error: "host offline",
|
|
14013
|
+
reason: agent.getHostCallFailure?.("sessionImportRequest") ?? "offline"
|
|
14014
|
+
},
|
|
14015
|
+
503
|
|
14016
|
+
);
|
|
14017
|
+
}
|
|
14018
|
+
function pickSessions(sessions2, range, now = Date.now()) {
|
|
14019
|
+
if (range === "latest5") return sessions2.slice(0, 5);
|
|
14020
|
+
if (range === "7d") return sessions2.filter((s) => s.lastUsed >= now - 7 * DAY_MS);
|
|
14021
|
+
return sessions2;
|
|
14022
|
+
}
|
|
14023
|
+
function channelForFolder(folder, channels) {
|
|
14024
|
+
return folder.linkedAs ? channels.find((ch) => ch.name === folder.linkedAs && !ch.archivedAt) : void 0;
|
|
14025
|
+
}
|
|
14026
|
+
async function importRows(channelIds) {
|
|
14027
|
+
if (!channelIds.length) return [];
|
|
14028
|
+
const { data, error: error2 } = await getSupabaseClient().from("session_imports").select("channel_id, agent, session_id, thread_id, last_message_uuid, message_count").in("channel_id", channelIds);
|
|
14029
|
+
if (error2) throw new Error(error2.message);
|
|
14030
|
+
return data ?? [];
|
|
14031
|
+
}
|
|
14032
|
+
async function importableFolders(folders, channels) {
|
|
14033
|
+
const mapped = folders.map((f) => ({ folder: f, channel: channelForFolder(f, channels) }));
|
|
14034
|
+
const rows = await importRows(mapped.flatMap((m) => m.channel ? [m.channel.id] : []));
|
|
14035
|
+
const byKey = new Map(rows.map((r) => [rowKey(r.channel_id, r.agent, r.session_id), r]));
|
|
14036
|
+
return mapped.map(({ folder, channel }) => {
|
|
14037
|
+
const sessions2 = folder.sessions.flatMap((s) => {
|
|
14038
|
+
const row = channel ? byKey.get(rowKey(channel.id, s.agent, s.sessionId)) : void 0;
|
|
14039
|
+
return s.bound && !row ? [] : [{ ...s, row }];
|
|
14040
|
+
});
|
|
14041
|
+
return { folder: { ...folder, sessions: sessions2 }, channel };
|
|
14042
|
+
}).filter((m) => m.folder.sessions.length);
|
|
14043
|
+
}
|
|
14044
|
+
var rowKey = (channelId, agent, sessionId) => `${channelId}:${agent}:${sessionId}`;
|
|
14045
|
+
function replyRows(threadId, messages, author, fallbackAt) {
|
|
14046
|
+
return messages.map((m, i) => ({
|
|
14047
|
+
thread_id: threadId,
|
|
14048
|
+
author: m.role === "user" ? author : AGENT_AUTHOR,
|
|
14049
|
+
body: m.text,
|
|
14050
|
+
created_at: m.at ?? new Date(fallbackAt + i).toISOString()
|
|
14051
|
+
}));
|
|
14052
|
+
}
|
|
14053
|
+
async function syncSession(agent, channelId, session, author, existing) {
|
|
14054
|
+
const supabase = getSupabaseClient();
|
|
14055
|
+
const read = await ask(agent, {
|
|
14056
|
+
op: "read",
|
|
14057
|
+
agent: session.agent,
|
|
14058
|
+
sessionId: session.sessionId,
|
|
14059
|
+
...existing?.last_message_uuid ? { afterUuid: existing.last_message_uuid } : {}
|
|
14060
|
+
});
|
|
14061
|
+
if (!read) return null;
|
|
14062
|
+
const messages = read.messages ?? [];
|
|
14063
|
+
if (!messages.length) return 0;
|
|
14064
|
+
const last = messages[messages.length - 1];
|
|
14065
|
+
if (existing) {
|
|
14066
|
+
const { error: error3 } = await supabase.from("thread_replies").insert(replyRows(existing.thread_id, messages, author, Date.now()));
|
|
14067
|
+
if (error3) throw new Error(error3.message);
|
|
14068
|
+
await supabase.from("session_imports").update({
|
|
14069
|
+
last_message_uuid: last.uuid,
|
|
14070
|
+
message_count: existing.message_count + messages.length,
|
|
14071
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
14072
|
+
}).eq("channel_id", channelId).eq("agent", session.agent).eq("session_id", session.sessionId);
|
|
14073
|
+
return messages.length;
|
|
14074
|
+
}
|
|
14075
|
+
const startedAt = messages[0].at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
14076
|
+
const { data: thread, error: threadError } = await supabase.from("threads").insert({
|
|
14077
|
+
channel_id: channelId,
|
|
14078
|
+
title: null,
|
|
14079
|
+
author,
|
|
14080
|
+
imported_agent: session.agent,
|
|
14081
|
+
created_at: startedAt
|
|
14082
|
+
}).select("id").single();
|
|
14083
|
+
if (threadError || !thread) throw new Error(threadError?.message ?? "could not create thread");
|
|
14084
|
+
const threadId = thread.id;
|
|
14085
|
+
const { error: claimError } = await supabase.from("session_imports").insert({
|
|
14086
|
+
channel_id: channelId,
|
|
14087
|
+
agent: session.agent,
|
|
14088
|
+
session_id: session.sessionId,
|
|
14089
|
+
thread_id: threadId,
|
|
14090
|
+
last_message_uuid: last.uuid,
|
|
14091
|
+
message_count: messages.length
|
|
14092
|
+
});
|
|
14093
|
+
if (claimError) {
|
|
14094
|
+
await supabase.from("threads").delete().eq("id", threadId);
|
|
14095
|
+
if (claimError.code === "23505") return 0;
|
|
14096
|
+
throw new Error(claimError.message);
|
|
14097
|
+
}
|
|
14098
|
+
const { error: error2 } = await supabase.from("thread_replies").insert(replyRows(threadId, messages, author, Date.parse(startedAt)));
|
|
14099
|
+
if (error2) throw new Error(error2.message);
|
|
14100
|
+
await ask(agent, {
|
|
14101
|
+
op: "bind",
|
|
14102
|
+
items: [{ channelId, threadId, agent: session.agent, sessionId: session.sessionId }]
|
|
14103
|
+
});
|
|
14104
|
+
return messages.length;
|
|
14105
|
+
}
|
|
14106
|
+
function createSessionImportRouter(agent) {
|
|
14107
|
+
const app = new Hono28();
|
|
14108
|
+
const scanFolders = async () => {
|
|
14109
|
+
const scan = await ask(agent, { op: "scan", slug: getProfile() });
|
|
14110
|
+
return scan && importableFolders(scan.folders, getProfileChannelRegistry());
|
|
14111
|
+
};
|
|
14112
|
+
const channelParam = (c) => getProfileChannelRegistry().find((ch) => ch.id === c.req.param("channelId"));
|
|
14113
|
+
app.get("/scan", async (c) => {
|
|
14114
|
+
const ctx = await resolveAuthContext(c);
|
|
14115
|
+
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
14116
|
+
const folders = await scanFolders();
|
|
14117
|
+
if (!folders) return hostUnavailable2(c, agent);
|
|
14118
|
+
return c.json({
|
|
14119
|
+
folders: folders.map(({ folder, channel }) => ({
|
|
14120
|
+
...folder,
|
|
14121
|
+
channel: channel ? { id: channel.id, name: channel.name } : null,
|
|
14122
|
+
sessions: folder.sessions.map(({ row, bound: _bound, ...s }) => ({
|
|
14123
|
+
...s,
|
|
14124
|
+
imported: !!row
|
|
14125
|
+
}))
|
|
14126
|
+
}))
|
|
14127
|
+
});
|
|
14128
|
+
});
|
|
14129
|
+
app.post("/import", async (c) => {
|
|
14130
|
+
const ctx = await resolveAuthContext(c);
|
|
14131
|
+
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
14132
|
+
const body = await c.req.json().catch(() => ({}));
|
|
14133
|
+
const picks = (Array.isArray(body.folders) ? body.folders : []).filter((f) => typeof f?.path === "string").map((f) => ({
|
|
14134
|
+
path: f.path,
|
|
14135
|
+
range: IMPORT_RANGES.includes(f.range ?? "") ? f.range : "all"
|
|
14136
|
+
}));
|
|
14137
|
+
if (!picks.length) return c.json({ error: "no folders chosen" }, 400);
|
|
14138
|
+
if (picks.length > MAX_FOLDERS2) return c.json({ error: `at most ${MAX_FOLDERS2} folders` }, 400);
|
|
14139
|
+
const folders = await scanFolders();
|
|
14140
|
+
if (!folders) return hostUnavailable2(c, agent);
|
|
14141
|
+
const offered = new Map(folders.map((m) => [m.folder.path, m]));
|
|
14142
|
+
if (picks.some((p) => !offered.has(p.path)))
|
|
14143
|
+
return c.json({ error: "folder was not offered by the scan" }, 400);
|
|
14144
|
+
const needLink = picks.filter((p) => !offered.get(p.path).channel);
|
|
14145
|
+
if (needLink.length && !isWorkspaceOwner(ctx)) {
|
|
14146
|
+
return c.json({ error: "workspace owner access required to add new folders" }, 403);
|
|
14147
|
+
}
|
|
14148
|
+
const taken = new Set(getProfileChannelRegistry().map((ch) => ch.name));
|
|
14149
|
+
const items = needLink.map((p) => ({
|
|
14150
|
+
name: uniqueChannelName(offered.get(p.path).folder.name, taken),
|
|
14151
|
+
path: p.path
|
|
14152
|
+
}));
|
|
14153
|
+
const linked = items.length ? await agent.linkProjectFolders?.(getProfile(), items) : [];
|
|
14154
|
+
if (!linked) return hostUnavailable2(c, agent);
|
|
14155
|
+
const created = /* @__PURE__ */ new Map();
|
|
14156
|
+
for (const [i, l] of linked.entries())
|
|
14157
|
+
created.set(items[i].path, await createChannel({ name: l.name, cwd: l.cwd }));
|
|
14158
|
+
const results = [];
|
|
14159
|
+
for (const p of picks) {
|
|
14160
|
+
const { folder, channel: existing } = offered.get(p.path);
|
|
14161
|
+
const channel = existing ?? created.get(p.path);
|
|
14162
|
+
let threads = 0;
|
|
14163
|
+
let messages = 0;
|
|
14164
|
+
for (const s of pickSessions(folder.sessions, p.range)) {
|
|
14165
|
+
if (s.row) continue;
|
|
14166
|
+
const n = await syncSession(agent, channel.id, s, ctx.handle, void 0);
|
|
14167
|
+
if (n === null) return hostUnavailable2(c, agent);
|
|
14168
|
+
if (n > 0) threads += 1;
|
|
14169
|
+
messages += n;
|
|
14170
|
+
}
|
|
14171
|
+
results.push({
|
|
14172
|
+
path: p.path,
|
|
14173
|
+
channel: { id: channel.id, name: channel.name },
|
|
14174
|
+
created: !existing,
|
|
14175
|
+
threads,
|
|
14176
|
+
messages
|
|
14177
|
+
});
|
|
14178
|
+
}
|
|
14179
|
+
return c.json({ results }, 201);
|
|
14180
|
+
});
|
|
14181
|
+
app.get("/channels/:channelId", async (c) => {
|
|
14182
|
+
const ctx = await resolveAuthContext(c);
|
|
14183
|
+
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
14184
|
+
const channel = channelParam(c);
|
|
14185
|
+
if (!channel) return c.json({ error: "channel not found" }, 404);
|
|
14186
|
+
const folders = await scanFolders();
|
|
14187
|
+
if (!folders) return hostUnavailable2(c, agent);
|
|
14188
|
+
const folder = folders.find((m) => m.channel?.id === channel.id)?.folder;
|
|
14189
|
+
const sessions2 = [];
|
|
14190
|
+
for (const { row, bound: _bound, ...s } of folder?.sessions ?? []) {
|
|
14191
|
+
let newMessages = 0;
|
|
14192
|
+
if (row) {
|
|
14193
|
+
const read = await ask(agent, {
|
|
14194
|
+
op: "read",
|
|
14195
|
+
agent: s.agent,
|
|
14196
|
+
sessionId: s.sessionId,
|
|
14197
|
+
...row.last_message_uuid ? { afterUuid: row.last_message_uuid } : {}
|
|
14198
|
+
});
|
|
14199
|
+
newMessages = read?.messages?.length ?? 0;
|
|
14200
|
+
}
|
|
14201
|
+
const status = !row ? "not-imported" : newMessages ? "new" : "synced";
|
|
14202
|
+
sessions2.push({ ...s, threadId: row?.thread_id ?? null, status, newMessages });
|
|
14203
|
+
}
|
|
14204
|
+
return c.json({ folder: folder?.path ?? null, sessions: sessions2 });
|
|
14205
|
+
});
|
|
14206
|
+
app.post("/channels/:channelId/sync", async (c) => {
|
|
14207
|
+
const ctx = await resolveAuthContext(c);
|
|
14208
|
+
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
14209
|
+
const channel = channelParam(c);
|
|
14210
|
+
if (!channel) return c.json({ error: "channel not found" }, 404);
|
|
14211
|
+
const body = await c.req.json().catch(() => ({}));
|
|
14212
|
+
const only = Array.isArray(body.sessionIds) ? new Set(body.sessionIds.filter((s) => typeof s === "string")) : null;
|
|
14213
|
+
const folders = await scanFolders();
|
|
14214
|
+
if (!folders) return hostUnavailable2(c, agent);
|
|
14215
|
+
const folder = folders.find((m) => m.channel?.id === channel.id)?.folder;
|
|
14216
|
+
let threads = 0;
|
|
14217
|
+
let messages = 0;
|
|
14218
|
+
for (const s of folder?.sessions ?? []) {
|
|
14219
|
+
if (only && !only.has(s.sessionId)) continue;
|
|
14220
|
+
const n = await syncSession(agent, channel.id, s, ctx.handle, s.row);
|
|
14221
|
+
if (n === null) return hostUnavailable2(c, agent);
|
|
14222
|
+
if (!s.row && n > 0) threads += 1;
|
|
14223
|
+
messages += n;
|
|
14224
|
+
}
|
|
14225
|
+
return c.json({ threads, messages });
|
|
14226
|
+
});
|
|
14227
|
+
return app;
|
|
14228
|
+
}
|
|
14229
|
+
|
|
13995
14230
|
// ../api/src/dashboard/host-status-signal.ts
|
|
13996
14231
|
var HOST_STATUS_CHANNEL = "host-status";
|
|
13997
14232
|
var channelReady = null;
|
|
13998
14233
|
function getHostStatusChannel() {
|
|
13999
|
-
channelReady ??= new Promise((
|
|
14234
|
+
channelReady ??= new Promise((resolve8) => {
|
|
14000
14235
|
const channel = getSupabaseClient().channel(HOST_STATUS_CHANNEL);
|
|
14001
14236
|
channel.subscribe((state) => {
|
|
14002
|
-
if (state === "SUBSCRIBED")
|
|
14237
|
+
if (state === "SUBSCRIBED") resolve8(channel);
|
|
14003
14238
|
});
|
|
14004
14239
|
});
|
|
14005
14240
|
return channelReady;
|
|
@@ -14164,6 +14399,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
14164
14399
|
});
|
|
14165
14400
|
api.route("/channels", createDocShareWriteRouter(agent));
|
|
14166
14401
|
api.route("/onboarding", createOnboardingRouter(agent));
|
|
14402
|
+
api.route("/session-import", createSessionImportRouter(agent));
|
|
14167
14403
|
api.route("/channels", createChannelGreetingRouter(agent));
|
|
14168
14404
|
api.route("/channels", createChannelMembersRouter());
|
|
14169
14405
|
api.route("/channels", createMemoriesRouter());
|
|
@@ -14418,7 +14654,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
14418
14654
|
// ../api/src/dashboard/device-auth-routes.ts
|
|
14419
14655
|
import { verify } from "crypto";
|
|
14420
14656
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
14421
|
-
import { Hono as
|
|
14657
|
+
import { Hono as Hono29 } from "hono";
|
|
14422
14658
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14423
14659
|
function cleanText(value, max) {
|
|
14424
14660
|
if (typeof value !== "string") return null;
|
|
@@ -14466,7 +14702,7 @@ async function boundWorkspaceIds(publicKey, userId) {
|
|
|
14466
14702
|
return new Set(existing ? (await listDeviceWorkspaces(existing)).map((w) => w.id) : []);
|
|
14467
14703
|
}
|
|
14468
14704
|
function createDeviceAuthRouter() {
|
|
14469
|
-
const app = new
|
|
14705
|
+
const app = new Hono29();
|
|
14470
14706
|
app.post("/code", async (c) => {
|
|
14471
14707
|
const body = await readBody(c);
|
|
14472
14708
|
const publicKey = typeof body["public_key"] === "string" ? body["public_key"] : "";
|
|
@@ -14785,7 +15021,7 @@ var workspaceRef = () => ({ kind: "workspace" });
|
|
|
14785
15021
|
var channelRef = (c) => ({ kind: "channel", channelId: c.req.param("id") });
|
|
14786
15022
|
|
|
14787
15023
|
// ../api/src/dashboard/output-styles-routes.ts
|
|
14788
|
-
import { Hono as
|
|
15024
|
+
import { Hono as Hono30 } from "hono";
|
|
14789
15025
|
function requireAdmin3(auth) {
|
|
14790
15026
|
return auth?.role === "admin" ? null : "admin access required";
|
|
14791
15027
|
}
|
|
@@ -14805,7 +15041,7 @@ function failure(c, err) {
|
|
|
14805
15041
|
return c.json({ error: message }, err instanceof OutputStyleConflictError ? 409 : 500);
|
|
14806
15042
|
}
|
|
14807
15043
|
function createOutputStylesRouter() {
|
|
14808
|
-
const app = new
|
|
15044
|
+
const app = new Hono30();
|
|
14809
15045
|
app.get("/", (c) => {
|
|
14810
15046
|
const auth = c.get("auth");
|
|
14811
15047
|
const denied = requireAdmin3(auth);
|
|
@@ -14897,7 +15133,7 @@ function createOutputStylesRouter() {
|
|
|
14897
15133
|
return app;
|
|
14898
15134
|
}
|
|
14899
15135
|
async function handleOutputStylesRequest(req) {
|
|
14900
|
-
const app = new
|
|
15136
|
+
const app = new Hono30();
|
|
14901
15137
|
app.use("*", async (c, next) => {
|
|
14902
15138
|
c.set("auth", { role: "admin" });
|
|
14903
15139
|
await next();
|
|
@@ -14918,7 +15154,7 @@ import { streamSSE } from "hono/streaming";
|
|
|
14918
15154
|
|
|
14919
15155
|
// ../api/src/dashboard/api.ts
|
|
14920
15156
|
function createGatedRouter(agent, register) {
|
|
14921
|
-
const api = new
|
|
15157
|
+
const api = new Hono31();
|
|
14922
15158
|
api.use("*", async (c, next) => {
|
|
14923
15159
|
const known = await listKnownWorkspaces();
|
|
14924
15160
|
const fromHost = profileFromHost(trustedHost(c), known);
|
|
@@ -14971,7 +15207,7 @@ import sharp7 from "sharp";
|
|
|
14971
15207
|
|
|
14972
15208
|
// src/dashboard/channels-routes.ts
|
|
14973
15209
|
import { existsSync as existsSync22, mkdirSync as mkdirSync10 } from "fs";
|
|
14974
|
-
import { Hono as
|
|
15210
|
+
import { Hono as Hono32 } from "hono";
|
|
14975
15211
|
function coerceAccess(raw) {
|
|
14976
15212
|
if (raw === "open" || raw === "*" || raw === "all") return raw;
|
|
14977
15213
|
if (Array.isArray(raw) && raw.every((v) => typeof v === "string")) return raw;
|
|
@@ -15034,7 +15270,7 @@ function toResponse3(channel) {
|
|
|
15034
15270
|
};
|
|
15035
15271
|
}
|
|
15036
15272
|
function createChannelWriteRouter() {
|
|
15037
|
-
const app = new
|
|
15273
|
+
const app = new Hono32();
|
|
15038
15274
|
app.post("/", async (c) => {
|
|
15039
15275
|
const body = await c.req.json().catch(() => ({}));
|
|
15040
15276
|
const name = typeof body["name"] === "string" ? body["name"].trim() : "";
|
|
@@ -15174,7 +15410,7 @@ import {
|
|
|
15174
15410
|
} from "fs";
|
|
15175
15411
|
import { Readable } from "stream";
|
|
15176
15412
|
import { basename as basename8, dirname as dirname8, extname, join as join23, relative, resolve as resolve5, sep } from "path";
|
|
15177
|
-
import { Hono as
|
|
15413
|
+
import { Hono as Hono33 } from "hono";
|
|
15178
15414
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
15179
15415
|
|
|
15180
15416
|
// src/dashboard/docs-watcher.ts
|
|
@@ -15476,7 +15712,7 @@ function needsRefresh(expiresAt, now = Date.now()) {
|
|
|
15476
15712
|
if (!expiresAt) return false;
|
|
15477
15713
|
return new Date(expiresAt).getTime() - now < REFRESH_SKEW_MS;
|
|
15478
15714
|
}
|
|
15479
|
-
var sleep3 = (ms) => new Promise((
|
|
15715
|
+
var sleep3 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
15480
15716
|
async function refreshConnection(params) {
|
|
15481
15717
|
const { workspaceId, connection, provider } = params;
|
|
15482
15718
|
const deps = params.deps ?? {};
|
|
@@ -15810,7 +16046,7 @@ ${req.user}`,
|
|
|
15810
16046
|
};
|
|
15811
16047
|
}
|
|
15812
16048
|
function run3(inv, cwd) {
|
|
15813
|
-
return new Promise((
|
|
16049
|
+
return new Promise((resolve8) => {
|
|
15814
16050
|
const child = spawn4(inv.command, inv.args, {
|
|
15815
16051
|
cwd,
|
|
15816
16052
|
env: inv.env,
|
|
@@ -15824,14 +16060,14 @@ function run3(inv, cwd) {
|
|
|
15824
16060
|
child.on("error", (err) => {
|
|
15825
16061
|
clearTimeout(timer2);
|
|
15826
16062
|
console.error(`[memory-extraction] ${inv.command} failed to start:`, err.message);
|
|
15827
|
-
|
|
16063
|
+
resolve8({ ok: false });
|
|
15828
16064
|
});
|
|
15829
16065
|
child.on("close", (code) => {
|
|
15830
16066
|
clearTimeout(timer2);
|
|
15831
16067
|
if (code !== 0) {
|
|
15832
16068
|
const detail = (stderr || stdout).trim().split("\n").at(-1) ?? "";
|
|
15833
16069
|
console.error(`[memory-extraction] ${inv.command} exited ${code}: ${detail.slice(0, 200)}`);
|
|
15834
|
-
|
|
16070
|
+
resolve8({ ok: false });
|
|
15835
16071
|
return;
|
|
15836
16072
|
}
|
|
15837
16073
|
let raw = stdout;
|
|
@@ -15842,7 +16078,7 @@ function run3(inv, cwd) {
|
|
|
15842
16078
|
raw = "";
|
|
15843
16079
|
}
|
|
15844
16080
|
}
|
|
15845
|
-
|
|
16081
|
+
resolve8({ ok: true, raw });
|
|
15846
16082
|
});
|
|
15847
16083
|
child.stdin.end(inv.stdin);
|
|
15848
16084
|
});
|
|
@@ -15904,7 +16140,7 @@ Rules:
|
|
|
15904
16140
|
- No preamble like "Summary:" \u2014 start directly with the summary text.`;
|
|
15905
16141
|
var HAIKU_TIMEOUT_MS = 6e4;
|
|
15906
16142
|
function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
15907
|
-
return new Promise((
|
|
16143
|
+
return new Promise((resolve8, reject) => {
|
|
15908
16144
|
const args = [
|
|
15909
16145
|
"--print",
|
|
15910
16146
|
"--model",
|
|
@@ -15955,7 +16191,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
|
15955
16191
|
reject(new Error("claude -p --model haiku returned empty output"));
|
|
15956
16192
|
return;
|
|
15957
16193
|
}
|
|
15958
|
-
|
|
16194
|
+
resolve8(text);
|
|
15959
16195
|
});
|
|
15960
16196
|
});
|
|
15961
16197
|
}
|
|
@@ -16351,6 +16587,15 @@ var ChatAgent = class {
|
|
|
16351
16587
|
* restart picks up edits. */
|
|
16352
16588
|
workspaceRoots = null;
|
|
16353
16589
|
/** Fire-and-forget entry point, called by chat-routes after every write. */
|
|
16590
|
+
/** The CLI of the session already bound to this thread, if any. */
|
|
16591
|
+
boundRunner(channelId, threadId) {
|
|
16592
|
+
const store = this.openSessionStore();
|
|
16593
|
+
try {
|
|
16594
|
+
return store.getSession(`chat:${channelId}`, threadId)?.runner;
|
|
16595
|
+
} finally {
|
|
16596
|
+
store.close();
|
|
16597
|
+
}
|
|
16598
|
+
}
|
|
16354
16599
|
handleMessage(msg) {
|
|
16355
16600
|
if (msg.author === AGENT_AUTHOR) return;
|
|
16356
16601
|
this.seenReplies.add(msg.replyId);
|
|
@@ -16731,7 +16976,7 @@ var ChatAgent = class {
|
|
|
16731
16976
|
);
|
|
16732
16977
|
ensureLogicalCwdDir(baseConfig.cwd);
|
|
16733
16978
|
const pickedModel = msg.model?.trim();
|
|
16734
|
-
let activeRunner = pickedModel ? runnerForModel(pickedModel) : defaultRunner;
|
|
16979
|
+
let activeRunner = pickedModel ? runnerForModel(pickedModel) : this.boundRunner(thread.channel_id, msg.threadId) ?? defaultRunner;
|
|
16735
16980
|
let channelConfig = pickedModel ? {
|
|
16736
16981
|
...baseConfig,
|
|
16737
16982
|
...activeRunner === "codex" ? { model: pickedModel, codexModel: pickedModel } : { model: pickedModel }
|
|
@@ -17037,22 +17282,22 @@ ${msg.body}` : msg.body;
|
|
|
17037
17282
|
let ready;
|
|
17038
17283
|
try {
|
|
17039
17284
|
channel = this.supabase().channel(`agent-thinking:${threadId}`);
|
|
17040
|
-
ready = new Promise((
|
|
17285
|
+
ready = new Promise((resolve8) => {
|
|
17041
17286
|
const timeout = setTimeout(() => {
|
|
17042
17287
|
console.error(`[chat-agent] thinking-channel ${threadId}: subscribe timed out after 10s`);
|
|
17043
|
-
|
|
17288
|
+
resolve8(false);
|
|
17044
17289
|
}, 1e4);
|
|
17045
17290
|
channel.subscribe((status, err) => {
|
|
17046
17291
|
if (status === "SUBSCRIBED") {
|
|
17047
17292
|
clearTimeout(timeout);
|
|
17048
|
-
|
|
17293
|
+
resolve8(true);
|
|
17049
17294
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
17050
17295
|
console.error(
|
|
17051
17296
|
`[chat-agent] thinking-channel ${threadId}: ${status}`,
|
|
17052
17297
|
err?.message ?? ""
|
|
17053
17298
|
);
|
|
17054
17299
|
clearTimeout(timeout);
|
|
17055
|
-
|
|
17300
|
+
resolve8(false);
|
|
17056
17301
|
}
|
|
17057
17302
|
});
|
|
17058
17303
|
});
|
|
@@ -17099,19 +17344,19 @@ ${msg.body}` : msg.body;
|
|
|
17099
17344
|
let ready;
|
|
17100
17345
|
try {
|
|
17101
17346
|
channel = this.supabase().channel("channel-activity");
|
|
17102
|
-
ready = new Promise((
|
|
17347
|
+
ready = new Promise((resolve8) => {
|
|
17103
17348
|
const timeout = setTimeout(() => {
|
|
17104
17349
|
console.error("[chat-agent] channel-activity: subscribe timed out after 10s");
|
|
17105
|
-
|
|
17350
|
+
resolve8(false);
|
|
17106
17351
|
}, 1e4);
|
|
17107
17352
|
channel.subscribe((status, err) => {
|
|
17108
17353
|
if (status === "SUBSCRIBED") {
|
|
17109
17354
|
clearTimeout(timeout);
|
|
17110
|
-
|
|
17355
|
+
resolve8(true);
|
|
17111
17356
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
17112
17357
|
console.error(`[chat-agent] channel-activity: ${status}`, err?.message ?? "");
|
|
17113
17358
|
clearTimeout(timeout);
|
|
17114
|
-
|
|
17359
|
+
resolve8(false);
|
|
17115
17360
|
}
|
|
17116
17361
|
});
|
|
17117
17362
|
});
|
|
@@ -18055,7 +18300,7 @@ function deleteDocEntry(root, path) {
|
|
|
18055
18300
|
};
|
|
18056
18301
|
}
|
|
18057
18302
|
function createDocsRouter({ gate = requireDocsAccess } = {}) {
|
|
18058
|
-
const app = new
|
|
18303
|
+
const app = new Hono33();
|
|
18059
18304
|
app.use("/:id/docs", gate);
|
|
18060
18305
|
app.use("/:id/docs/*", gate);
|
|
18061
18306
|
app.get("/:id/docs", (c) => {
|
|
@@ -18393,7 +18638,7 @@ function withDocLinkBridge(html) {
|
|
|
18393
18638
|
// src/dashboard/skills-routes-host.ts
|
|
18394
18639
|
import { existsSync as existsSync27, statSync as statSync7 } from "fs";
|
|
18395
18640
|
import { dirname as dirname9 } from "path";
|
|
18396
|
-
import { Hono as
|
|
18641
|
+
import { Hono as Hono34 } from "hono";
|
|
18397
18642
|
function workspaceScope() {
|
|
18398
18643
|
const claudeConfigDir = profileClaudeConfigDir();
|
|
18399
18644
|
const cwd = defaultClaudeCwd();
|
|
@@ -18499,12 +18744,12 @@ var diskBackend = {
|
|
|
18499
18744
|
write: writeSkillAndSync
|
|
18500
18745
|
};
|
|
18501
18746
|
function createWorkspaceSkillsRouter() {
|
|
18502
|
-
const app = new
|
|
18747
|
+
const app = new Hono34();
|
|
18503
18748
|
mountSkillRoutes(app, "/skills", workspaceRef, diskBackend);
|
|
18504
18749
|
return app;
|
|
18505
18750
|
}
|
|
18506
18751
|
function createChannelSkillsRouter() {
|
|
18507
|
-
const app = new
|
|
18752
|
+
const app = new Hono34();
|
|
18508
18753
|
mountSkillRoutes(app, "/:id/skill-files", channelRef, diskBackend);
|
|
18509
18754
|
return app;
|
|
18510
18755
|
}
|
|
@@ -18639,7 +18884,7 @@ import { randomUUID as randomUUID5 } from "crypto";
|
|
|
18639
18884
|
import { chmodSync, existsSync as existsSync29, mkdirSync as mkdirSync12, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
|
|
18640
18885
|
import { createRequire } from "module";
|
|
18641
18886
|
import { dirname as dirname10, join as join25 } from "path";
|
|
18642
|
-
import { Hono as
|
|
18887
|
+
import { Hono as Hono35 } from "hono";
|
|
18643
18888
|
var SESSION_TTL_MS = 10 * 6e4;
|
|
18644
18889
|
var URL_WAIT_MS = 3e4;
|
|
18645
18890
|
var TOKEN_WAIT_MS = 6e4;
|
|
@@ -18688,13 +18933,13 @@ function track(session) {
|
|
|
18688
18933
|
return [id, s];
|
|
18689
18934
|
}
|
|
18690
18935
|
function waitFor(s, parse, ms) {
|
|
18691
|
-
return new Promise((
|
|
18936
|
+
return new Promise((resolve8) => {
|
|
18692
18937
|
const started = Date.now();
|
|
18693
18938
|
const tick = setInterval(() => {
|
|
18694
18939
|
const v = parse();
|
|
18695
18940
|
if (v !== void 0 || s.status !== "pending" || Date.now() - started > ms) {
|
|
18696
18941
|
clearInterval(tick);
|
|
18697
|
-
|
|
18942
|
+
resolve8(v);
|
|
18698
18943
|
}
|
|
18699
18944
|
}, 200);
|
|
18700
18945
|
});
|
|
@@ -18796,7 +19041,7 @@ function failure2(c, err) {
|
|
|
18796
19041
|
return c.json({ error: err.message }, 500);
|
|
18797
19042
|
}
|
|
18798
19043
|
function createProviderLoginRouter() {
|
|
18799
|
-
const app = new
|
|
19044
|
+
const app = new Hono35();
|
|
18800
19045
|
app.use("*", async (c, next) => {
|
|
18801
19046
|
const denied = requireAdmin4(c.get("auth"));
|
|
18802
19047
|
if (denied) return c.json({ error: denied }, 403);
|
|
@@ -18840,7 +19085,7 @@ function createProviderLoginRouter() {
|
|
|
18840
19085
|
return app;
|
|
18841
19086
|
}
|
|
18842
19087
|
async function handleProviderLoginRequest(req) {
|
|
18843
|
-
const app = new
|
|
19088
|
+
const app = new Hono35();
|
|
18844
19089
|
app.use("*", async (c, next) => {
|
|
18845
19090
|
c.set("auth", { role: "admin" });
|
|
18846
19091
|
await next();
|
|
@@ -18992,18 +19237,253 @@ function createHostApiRouter(agent = {}) {
|
|
|
18992
19237
|
|
|
18993
19238
|
// src/dashboard/agent-bridge-host.ts
|
|
18994
19239
|
import { sign as signRaw, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
18995
|
-
import { existsSync as
|
|
19240
|
+
import { existsSync as existsSync32, readFileSync as readFileSync27 } from "fs";
|
|
18996
19241
|
import dotenv2 from "dotenv";
|
|
18997
19242
|
import { WebSocket as WebSocket2 } from "ws";
|
|
18998
19243
|
|
|
19244
|
+
// src/dashboard/session-import-host.ts
|
|
19245
|
+
import {
|
|
19246
|
+
closeSync as closeSync3,
|
|
19247
|
+
existsSync as existsSync31,
|
|
19248
|
+
openSync as openSync3,
|
|
19249
|
+
readdirSync as readdirSync6,
|
|
19250
|
+
readFileSync as readFileSync25,
|
|
19251
|
+
readSync as readSync3,
|
|
19252
|
+
realpathSync as realpathSync5
|
|
19253
|
+
} from "fs";
|
|
19254
|
+
import { basename as basename9, join as join26, resolve as resolve7 } from "path";
|
|
19255
|
+
var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
19256
|
+
var HEAD_BYTES = 64 * 1024;
|
|
19257
|
+
var TITLE_MAX = 80;
|
|
19258
|
+
function readHead2(file) {
|
|
19259
|
+
let fd;
|
|
19260
|
+
try {
|
|
19261
|
+
fd = openSync3(file, "r");
|
|
19262
|
+
const buf = Buffer.alloc(HEAD_BYTES);
|
|
19263
|
+
return buf.toString("utf-8", 0, readSync3(fd, buf, 0, buf.length, 0));
|
|
19264
|
+
} catch {
|
|
19265
|
+
return "";
|
|
19266
|
+
} finally {
|
|
19267
|
+
if (fd !== void 0) closeSync3(fd);
|
|
19268
|
+
}
|
|
19269
|
+
}
|
|
19270
|
+
function parseLines(text) {
|
|
19271
|
+
const out = [];
|
|
19272
|
+
for (const line of text.split("\n")) {
|
|
19273
|
+
if (!line) continue;
|
|
19274
|
+
try {
|
|
19275
|
+
out.push(JSON.parse(line));
|
|
19276
|
+
} catch {
|
|
19277
|
+
}
|
|
19278
|
+
}
|
|
19279
|
+
return out;
|
|
19280
|
+
}
|
|
19281
|
+
function cleanText2(text) {
|
|
19282
|
+
const t = text.trim();
|
|
19283
|
+
if (!t || t.startsWith("<")) return null;
|
|
19284
|
+
return t;
|
|
19285
|
+
}
|
|
19286
|
+
function blocksText(content, kinds) {
|
|
19287
|
+
if (typeof content === "string") return cleanText2(content);
|
|
19288
|
+
if (!Array.isArray(content)) return null;
|
|
19289
|
+
const parts = content.filter(
|
|
19290
|
+
(b) => kinds.includes(b?.type) && typeof b.text === "string"
|
|
19291
|
+
).map((b) => b.text);
|
|
19292
|
+
return parts.length ? cleanText2(parts.join("\n\n")) : null;
|
|
19293
|
+
}
|
|
19294
|
+
function boundSessionIds() {
|
|
19295
|
+
if (!existsSync31(paths.DB_FILE)) return /* @__PURE__ */ new Set();
|
|
19296
|
+
const store = new SessionStore(paths.DB_FILE);
|
|
19297
|
+
try {
|
|
19298
|
+
return new Set(store.listSessions().map((s) => s.sessionId));
|
|
19299
|
+
} finally {
|
|
19300
|
+
store.close();
|
|
19301
|
+
}
|
|
19302
|
+
}
|
|
19303
|
+
function claudeMessages(lines) {
|
|
19304
|
+
const out = [];
|
|
19305
|
+
for (const o of lines) {
|
|
19306
|
+
if (o["type"] !== "user" && o["type"] !== "assistant" || o["isMeta"] || o["isSidechain"])
|
|
19307
|
+
continue;
|
|
19308
|
+
if (o["entrypoint"] === "sdk-cli") continue;
|
|
19309
|
+
const role = o["type"];
|
|
19310
|
+
const text = blocksText(o["message"]?.content, ["text"]);
|
|
19311
|
+
if (!text || typeof o["uuid"] !== "string") continue;
|
|
19312
|
+
const at = typeof o["timestamp"] === "string" ? o["timestamp"] : null;
|
|
19313
|
+
const last = out[out.length - 1];
|
|
19314
|
+
if (role === "assistant" && last?.role === "assistant") {
|
|
19315
|
+
last.text += `
|
|
19316
|
+
|
|
19317
|
+
${text}`;
|
|
19318
|
+
last.uuid = o["uuid"];
|
|
19319
|
+
} else {
|
|
19320
|
+
out.push({ uuid: o["uuid"], role, text, at });
|
|
19321
|
+
}
|
|
19322
|
+
}
|
|
19323
|
+
return out;
|
|
19324
|
+
}
|
|
19325
|
+
function codexMessages(lines) {
|
|
19326
|
+
const out = [];
|
|
19327
|
+
lines.forEach((o, i) => {
|
|
19328
|
+
const p = o["payload"];
|
|
19329
|
+
if (o["type"] !== "response_item" || p?.type !== "message") return;
|
|
19330
|
+
if (p.role !== "user" && p.role !== "assistant") return;
|
|
19331
|
+
const text = blocksText(p.content, ["input_text", "output_text"]);
|
|
19332
|
+
if (!text) return;
|
|
19333
|
+
out.push({
|
|
19334
|
+
uuid: `line:${i}`,
|
|
19335
|
+
role: p.role,
|
|
19336
|
+
text,
|
|
19337
|
+
at: typeof o["timestamp"] === "string" ? o["timestamp"] : null
|
|
19338
|
+
});
|
|
19339
|
+
});
|
|
19340
|
+
return out;
|
|
19341
|
+
}
|
|
19342
|
+
function claudeSummary(file, mtime) {
|
|
19343
|
+
const head = parseLines(readHead2(file));
|
|
19344
|
+
if (head.some((o) => o["entrypoint"] === "sdk-cli")) return null;
|
|
19345
|
+
const first = claudeMessages(head).find((m) => m.role === "user");
|
|
19346
|
+
if (!first) return null;
|
|
19347
|
+
return {
|
|
19348
|
+
agent: "claude",
|
|
19349
|
+
sessionId: basename9(file, ".jsonl"),
|
|
19350
|
+
title: first.text.slice(0, TITLE_MAX),
|
|
19351
|
+
lastUsed: mtime
|
|
19352
|
+
};
|
|
19353
|
+
}
|
|
19354
|
+
function codexSummary(file, mtime) {
|
|
19355
|
+
const head = parseLines(readHead2(file));
|
|
19356
|
+
const meta = head.find((o) => o["type"] === "session_meta")?.["payload"];
|
|
19357
|
+
if (!meta?.id || !meta.cwd || meta.originator === "codex_exec") return null;
|
|
19358
|
+
const first = codexMessages(head).find((m) => m.role === "user");
|
|
19359
|
+
if (!first) return null;
|
|
19360
|
+
return {
|
|
19361
|
+
cwd: meta.cwd,
|
|
19362
|
+
session: {
|
|
19363
|
+
agent: "codex",
|
|
19364
|
+
sessionId: meta.id,
|
|
19365
|
+
title: first.text.slice(0, TITLE_MAX),
|
|
19366
|
+
lastUsed: mtime
|
|
19367
|
+
}
|
|
19368
|
+
};
|
|
19369
|
+
}
|
|
19370
|
+
function enabledAgents() {
|
|
19371
|
+
return new Set(
|
|
19372
|
+
detectRunners().filter((r) => r.installed && r.loggedIn).map((r) => r.id)
|
|
19373
|
+
);
|
|
19374
|
+
}
|
|
19375
|
+
function scanSessionFolders(slug) {
|
|
19376
|
+
const agents = enabledAgents();
|
|
19377
|
+
const bound = boundSessionIds();
|
|
19378
|
+
const since2 = Date.now() - MAX_AGE_MS;
|
|
19379
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
19380
|
+
const add = (cwd, s) => {
|
|
19381
|
+
const path = resolve7(cwd);
|
|
19382
|
+
if (isExcluded(path)) return;
|
|
19383
|
+
byPath.set(path, [
|
|
19384
|
+
...byPath.get(path) ?? [],
|
|
19385
|
+
bound.has(s.sessionId) ? { ...s, bound: true } : s
|
|
19386
|
+
]);
|
|
19387
|
+
};
|
|
19388
|
+
if (agents.has("claude")) {
|
|
19389
|
+
const root = join26(claudeDir(), "projects");
|
|
19390
|
+
let dirs = [];
|
|
19391
|
+
try {
|
|
19392
|
+
dirs = readdirSync6(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
19393
|
+
} catch {
|
|
19394
|
+
}
|
|
19395
|
+
for (const dir of dirs) {
|
|
19396
|
+
const files = jsonlFiles(join26(root, dir), 0).filter((f) => f.mtime >= since2);
|
|
19397
|
+
let cwd = null;
|
|
19398
|
+
for (const f of files) {
|
|
19399
|
+
cwd ??= cwdFromTranscript(f.file);
|
|
19400
|
+
if (cwd) break;
|
|
19401
|
+
}
|
|
19402
|
+
if (!cwd) continue;
|
|
19403
|
+
for (const f of files) {
|
|
19404
|
+
const s = claudeSummary(f.file, f.mtime);
|
|
19405
|
+
if (s) add(cwd, s);
|
|
19406
|
+
}
|
|
19407
|
+
}
|
|
19408
|
+
}
|
|
19409
|
+
if (agents.has("codex")) {
|
|
19410
|
+
for (const f of jsonlFiles(join26(codexDir(), "sessions"), 4)) {
|
|
19411
|
+
if (f.mtime < since2) continue;
|
|
19412
|
+
const s = codexSummary(f.file, f.mtime);
|
|
19413
|
+
if (s) add(s.cwd, s.session);
|
|
19414
|
+
}
|
|
19415
|
+
}
|
|
19416
|
+
const linked = linkedTargets(slug);
|
|
19417
|
+
const folders = [...byPath].map(([path, sessions2]) => {
|
|
19418
|
+
let real = path;
|
|
19419
|
+
try {
|
|
19420
|
+
real = realpathSync5(path);
|
|
19421
|
+
} catch {
|
|
19422
|
+
}
|
|
19423
|
+
sessions2.sort((a, b) => b.lastUsed - a.lastUsed);
|
|
19424
|
+
return {
|
|
19425
|
+
path,
|
|
19426
|
+
name: basename9(path),
|
|
19427
|
+
lastUsed: sessions2[0].lastUsed,
|
|
19428
|
+
sessions: sessions2,
|
|
19429
|
+
...linked.has(real) ? { linkedAs: linked.get(real) } : {}
|
|
19430
|
+
};
|
|
19431
|
+
});
|
|
19432
|
+
return folders.sort((a, b) => b.lastUsed - a.lastUsed);
|
|
19433
|
+
}
|
|
19434
|
+
function findTranscript(agent, sessionId) {
|
|
19435
|
+
if (!/^[A-Za-z0-9-]+$/.test(sessionId)) return null;
|
|
19436
|
+
if (agent === "claude") {
|
|
19437
|
+
const root = join26(claudeDir(), "projects");
|
|
19438
|
+
try {
|
|
19439
|
+
for (const e of readdirSync6(root, { withFileTypes: true })) {
|
|
19440
|
+
const file = join26(root, e.name, `${sessionId}.jsonl`);
|
|
19441
|
+
if (e.isDirectory() && existsSync31(file)) return file;
|
|
19442
|
+
}
|
|
19443
|
+
} catch {
|
|
19444
|
+
}
|
|
19445
|
+
return null;
|
|
19446
|
+
}
|
|
19447
|
+
return jsonlFiles(join26(codexDir(), "sessions"), 4).find((f) => f.file.endsWith(`${sessionId}.jsonl`))?.file ?? null;
|
|
19448
|
+
}
|
|
19449
|
+
function readSessionMessages(agent, sessionId, afterUuid) {
|
|
19450
|
+
const file = findTranscript(agent, sessionId);
|
|
19451
|
+
if (!file) return null;
|
|
19452
|
+
const lines = parseLines(readFileSync25(file, "utf-8"));
|
|
19453
|
+
const messages = agent === "claude" ? claudeMessages(lines) : codexMessages(lines);
|
|
19454
|
+
if (!afterUuid) return messages;
|
|
19455
|
+
const idx = messages.findIndex((m) => m.uuid === afterUuid);
|
|
19456
|
+
return idx === -1 ? [] : messages.slice(idx + 1);
|
|
19457
|
+
}
|
|
19458
|
+
function bindSessions(items) {
|
|
19459
|
+
const store = new SessionStore(paths.DB_FILE);
|
|
19460
|
+
try {
|
|
19461
|
+
for (const i of items)
|
|
19462
|
+
store.saveSession(`chat:${i.channelId}`, i.threadId, i.sessionId, i.agent);
|
|
19463
|
+
} finally {
|
|
19464
|
+
store.close();
|
|
19465
|
+
}
|
|
19466
|
+
return items.length;
|
|
19467
|
+
}
|
|
19468
|
+
function handleSessionImportRequest(req) {
|
|
19469
|
+
switch (req.op) {
|
|
19470
|
+
case "scan":
|
|
19471
|
+
return { op: "scan", folders: scanSessionFolders(req.slug) };
|
|
19472
|
+
case "read":
|
|
19473
|
+
return { op: "read", messages: readSessionMessages(req.agent, req.sessionId, req.afterUuid) };
|
|
19474
|
+
case "bind":
|
|
19475
|
+
return { op: "bind", bound: bindSessions(req.items) };
|
|
19476
|
+
}
|
|
19477
|
+
}
|
|
19478
|
+
|
|
18999
19479
|
// src/dashboard/docs-relay-host.ts
|
|
19000
|
-
import { Hono as
|
|
19480
|
+
import { Hono as Hono36 } from "hono";
|
|
19001
19481
|
var RETURNED_HEADERS = ["content-type", "content-disposition", "content-security-policy", "cache-control"];
|
|
19002
19482
|
async function handleDocsRequest(req) {
|
|
19003
19483
|
return runRelayed(req, (app) => app.route("/channels", createDocsRouter({ gate: async (_c, next) => next() })));
|
|
19004
19484
|
}
|
|
19005
19485
|
async function runRelayed(req, register) {
|
|
19006
|
-
const app = new
|
|
19486
|
+
const app = new Hono36();
|
|
19007
19487
|
app.use("*", async (c, next) => {
|
|
19008
19488
|
c.set("auth", req.auth);
|
|
19009
19489
|
await next();
|
|
@@ -19026,7 +19506,7 @@ async function handleHostRequest(req) {
|
|
|
19026
19506
|
}
|
|
19027
19507
|
|
|
19028
19508
|
// src/dashboard/doc-share-publisher.ts
|
|
19029
|
-
import { readFileSync as
|
|
19509
|
+
import { readFileSync as readFileSync26, statSync as statSync9 } from "fs";
|
|
19030
19510
|
import { extname as extname2, posix } from "path";
|
|
19031
19511
|
var MAX_ASSET_BYTES = 20 * 1024 * 1024;
|
|
19032
19512
|
var MAX_CONTENT_BYTES2 = 2 * 1024 * 1024;
|
|
@@ -19141,7 +19621,7 @@ function readDocShareBundle({ cwd, sourcePath }) {
|
|
|
19141
19621
|
}
|
|
19142
19622
|
if (!stat.isFile()) return { status: "not-found" };
|
|
19143
19623
|
if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
|
|
19144
|
-
const source =
|
|
19624
|
+
const source = readFileSync26(abs, "utf8");
|
|
19145
19625
|
const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
|
|
19146
19626
|
if (kind === "excalidraw") {
|
|
19147
19627
|
try {
|
|
@@ -19212,7 +19692,7 @@ function readDocShareBundle({ cwd, sourcePath }) {
|
|
|
19212
19692
|
let text = null;
|
|
19213
19693
|
try {
|
|
19214
19694
|
const size = statSync9(docAbs).size;
|
|
19215
|
-
if (size <= budget) text =
|
|
19695
|
+
if (size <= budget) text = readFileSync26(docAbs, "utf8");
|
|
19216
19696
|
} catch {
|
|
19217
19697
|
continue;
|
|
19218
19698
|
}
|
|
@@ -19229,7 +19709,7 @@ function readDocShareBundle({ cwd, sourcePath }) {
|
|
|
19229
19709
|
let body;
|
|
19230
19710
|
try {
|
|
19231
19711
|
if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
|
|
19232
|
-
body =
|
|
19712
|
+
body = readFileSync26(resolved2.abs);
|
|
19233
19713
|
} catch {
|
|
19234
19714
|
continue;
|
|
19235
19715
|
}
|
|
@@ -19257,13 +19737,13 @@ function readDocShareBundle({ cwd, sourcePath }) {
|
|
|
19257
19737
|
|
|
19258
19738
|
// src/dashboard/host-upgrade.ts
|
|
19259
19739
|
import { spawn as spawn7 } from "child_process";
|
|
19260
|
-
import { mkdirSync as mkdirSync13, openSync as
|
|
19261
|
-
import { join as
|
|
19740
|
+
import { mkdirSync as mkdirSync13, openSync as openSync4 } from "fs";
|
|
19741
|
+
import { join as join27 } from "path";
|
|
19262
19742
|
var running = false;
|
|
19263
19743
|
function startHostUpgrade() {
|
|
19264
19744
|
if (running) return { started: false };
|
|
19265
19745
|
mkdirSync13(paths.LOG_DIR, { recursive: true });
|
|
19266
|
-
const log5 =
|
|
19746
|
+
const log5 = openSync4(join27(paths.LOG_DIR, "host-upgrade.log"), "a");
|
|
19267
19747
|
const child = spawn7(process.execPath, [process.argv[1] ?? "", "upgrade"], {
|
|
19268
19748
|
detached: true,
|
|
19269
19749
|
stdio: ["ignore", log5, log5]
|
|
@@ -19284,7 +19764,7 @@ function dataSocketUrl() {
|
|
|
19284
19764
|
return `ws://127.0.0.1:${process.env["FLYSPACE_DATA_API_PORT"] ?? DEFAULT_DATA_API_PORT}${HOST_SOCKET_PATH}`;
|
|
19285
19765
|
}
|
|
19286
19766
|
function listLocalSessions() {
|
|
19287
|
-
if (!
|
|
19767
|
+
if (!existsSync32(paths.DB_FILE)) return [];
|
|
19288
19768
|
const store = new SessionStore(paths.DB_FILE);
|
|
19289
19769
|
try {
|
|
19290
19770
|
return store.listSessions();
|
|
@@ -19321,6 +19801,7 @@ function localAgentCallbacks(chatAgent) {
|
|
|
19321
19801
|
upgradeHost: () => startHostUpgrade(),
|
|
19322
19802
|
scanOnboarding: (slug) => scanOnboarding(slug),
|
|
19323
19803
|
linkProjectFolders: (slug, items) => linkProjectFolders(slug, items),
|
|
19804
|
+
sessionImportRequest: (req) => handleSessionImportRequest(req),
|
|
19324
19805
|
isHostOnline: () => true
|
|
19325
19806
|
};
|
|
19326
19807
|
}
|
|
@@ -19329,7 +19810,7 @@ function buildHostConfigSnapshot() {
|
|
|
19329
19810
|
const snapshot2 = {};
|
|
19330
19811
|
for (const profile of listProfiles()) {
|
|
19331
19812
|
snapshot2[profile] = runWithProfile(profile, () => ({
|
|
19332
|
-
env:
|
|
19813
|
+
env: existsSync32(paths.CONFIG_FILE) ? dotenv2.parse(readFileSync27(paths.CONFIG_FILE, "utf-8")) : {},
|
|
19333
19814
|
agentModel: loadDashboardSettings().agent
|
|
19334
19815
|
}));
|
|
19335
19816
|
}
|
|
@@ -19524,7 +20005,7 @@ async function channelNames(ids) {
|
|
|
19524
20005
|
async function deliverPending() {
|
|
19525
20006
|
const since2 = new Date(Date.now() - MAX_AGE_MINUTES * 6e4).toISOString();
|
|
19526
20007
|
const graceCutoff = new Date(Date.now() - GRACE_MS).toISOString();
|
|
19527
|
-
const { data, error: error2 } = await getSupabaseClient().from("notifications").select("id, user_id, thread_id, channel_id, kind, thread_replies (author, body)").is("delivered_at", null).is("read_at", null).gte("created_at", since2).lte("created_at", graceCutoff).order("created_at", { ascending: true }).limit(50);
|
|
20008
|
+
const { data, error: error2 } = await getSupabaseClient().from("notifications").select("id, user_id, thread_id, channel_id, reply_id, kind, thread_replies (author, body)").is("delivered_at", null).is("read_at", null).gte("created_at", since2).lte("created_at", graceCutoff).order("created_at", { ascending: true }).limit(50);
|
|
19528
20009
|
if (error2) {
|
|
19529
20010
|
console.error("[push] could not read pending notifications:", error2.message);
|
|
19530
20011
|
return;
|
|
@@ -19556,7 +20037,9 @@ async function deliverPending() {
|
|
|
19556
20037
|
await pushToUser(row.user_id, {
|
|
19557
20038
|
title: row.kind === "mention" ? `${author} mentioned you in #${channel}` : `${author} replied in #${channel}`,
|
|
19558
20039
|
body: preview(row.thread_replies?.body ?? ""),
|
|
19559
|
-
|
|
20040
|
+
// Same #m- anchor as the dashboard's messageAnchor(), so the thread
|
|
20041
|
+
// opens on this reply and flashes it.
|
|
20042
|
+
url: `/chat/${row.channel_id}/${row.thread_id}${row.reply_id ? `#m-${row.reply_id.slice(0, 8)}` : ""}`,
|
|
19560
20043
|
threadId: row.thread_id,
|
|
19561
20044
|
unreadCount
|
|
19562
20045
|
});
|
|
@@ -19579,12 +20062,12 @@ function startPushWatcher() {
|
|
|
19579
20062
|
}
|
|
19580
20063
|
|
|
19581
20064
|
// src/dashboard/machine-server.ts
|
|
19582
|
-
import { existsSync as
|
|
19583
|
-
import { join as
|
|
20065
|
+
import { existsSync as existsSync33 } from "fs";
|
|
20066
|
+
import { join as join28 } from "path";
|
|
19584
20067
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
19585
20068
|
|
|
19586
20069
|
// src/dashboard/app-icon-routes.ts
|
|
19587
|
-
import { Hono as
|
|
20070
|
+
import { Hono as Hono37 } from "hono";
|
|
19588
20071
|
var NEUTRAL_MANIFEST = {
|
|
19589
20072
|
id: "/",
|
|
19590
20073
|
name: "Flyspace",
|
|
@@ -19622,7 +20105,7 @@ async function withResolvedProfile2(c, next) {
|
|
|
19622
20105
|
return profile ? runWithProfile(profile, () => next()) : next();
|
|
19623
20106
|
}
|
|
19624
20107
|
function createAppIconRouter() {
|
|
19625
|
-
const app = new
|
|
20108
|
+
const app = new Hono37();
|
|
19626
20109
|
app.get("/manifest.json", withResolvedProfile2, async (c) => {
|
|
19627
20110
|
if (!await resolvedProfileHost(c)) return c.json(NEUTRAL_MANIFEST);
|
|
19628
20111
|
const profile = getProfile();
|
|
@@ -19662,7 +20145,7 @@ function pngResponse(c, png) {
|
|
|
19662
20145
|
}
|
|
19663
20146
|
|
|
19664
20147
|
// src/dashboard/machine-server.ts
|
|
19665
|
-
var WEB_DIST =
|
|
20148
|
+
var WEB_DIST = join28(paths.WEB_ROOT, "dist");
|
|
19666
20149
|
async function bootMachine() {
|
|
19667
20150
|
await reconcileAll().catch(
|
|
19668
20151
|
(err) => console.error("[dashboard] reconcileAll failed:", err.message)
|
|
@@ -19682,14 +20165,14 @@ async function bootMachine() {
|
|
|
19682
20165
|
function mountSpa(app) {
|
|
19683
20166
|
app.route("/share", createPublicShareAssetRouter(WEB_DIST));
|
|
19684
20167
|
app.route("/", createAppIconRouter());
|
|
19685
|
-
if (
|
|
20168
|
+
if (existsSync33(WEB_DIST) || existsSync33(join28(paths.WEB_ROOT, "package.json"))) {
|
|
19686
20169
|
app.use("/*", serveStatic({ root: WEB_DIST }));
|
|
19687
20170
|
}
|
|
19688
20171
|
app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
|
|
19689
|
-
const spaFallback = serveStatic({ path:
|
|
20172
|
+
const spaFallback = serveStatic({ path: join28(WEB_DIST, "index.html") });
|
|
19690
20173
|
app.get("*", async (c, next) => {
|
|
19691
|
-
if (!
|
|
19692
|
-
const isCheckout =
|
|
20174
|
+
if (!existsSync33(join28(WEB_DIST, "index.html"))) {
|
|
20175
|
+
const isCheckout = existsSync33(join28(paths.WEB_ROOT, "package.json"));
|
|
19693
20176
|
return c.text(
|
|
19694
20177
|
isCheckout ? `Workspace UI build missing (${WEB_DIST}).
|
|
19695
20178
|
Run \`pnpm build:web\` from the repo root.
|
|
@@ -19823,8 +20306,8 @@ function openBrowser2(url) {
|
|
|
19823
20306
|
}
|
|
19824
20307
|
}
|
|
19825
20308
|
function ensureWebBuild() {
|
|
19826
|
-
if (
|
|
19827
|
-
if (!
|
|
20309
|
+
if (existsSync34(join29(paths.WEB_ROOT, "dist", "index.html"))) return;
|
|
20310
|
+
if (!existsSync34(join29(paths.WEB_ROOT, "package.json"))) return;
|
|
19828
20311
|
console.log(" packages/ui/dist missing, building the dashboard UI...");
|
|
19829
20312
|
const result = spawnSync("pnpm", ["build"], {
|
|
19830
20313
|
cwd: paths.WEB_ROOT,
|
|
@@ -19874,7 +20357,7 @@ async function runDashboard(args) {
|
|
|
19874
20357
|
}
|
|
19875
20358
|
|
|
19876
20359
|
// src/commands/workspace.ts
|
|
19877
|
-
import { existsSync as
|
|
20360
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync14, writeFileSync as writeFileSync13 } from "fs";
|
|
19878
20361
|
var USAGE7 = `
|
|
19879
20362
|
Usage: flyspace workspace <subcommand> [options]
|
|
19880
20363
|
|
|
@@ -19960,7 +20443,7 @@ function provisionLocal(botName) {
|
|
|
19960
20443
|
ensureDirs();
|
|
19961
20444
|
console.log(` config dir ready (${paths.CONFIG_DIR})`);
|
|
19962
20445
|
console.log(` data dir ready (${paths.DATA_DIR})`);
|
|
19963
|
-
if (
|
|
20446
|
+
if (existsSync35(paths.CONFIG_FILE)) {
|
|
19964
20447
|
console.log(" config.env exists");
|
|
19965
20448
|
} else {
|
|
19966
20449
|
writeFileSync13(paths.CONFIG_FILE, `BOT_NAME=${botName}
|
|
@@ -19977,7 +20460,7 @@ async function provisionChannels(specs) {
|
|
|
19977
20460
|
continue;
|
|
19978
20461
|
}
|
|
19979
20462
|
const cwd = expandPath(spec.cwd);
|
|
19980
|
-
if (!
|
|
20463
|
+
if (!existsSync35(cwd)) {
|
|
19981
20464
|
mkdirSync14(cwd, { recursive: true });
|
|
19982
20465
|
console.log(` created cwd ${cwd}`);
|
|
19983
20466
|
}
|
|
@@ -20055,10 +20538,10 @@ async function runReconcileCmd(args) {
|
|
|
20055
20538
|
}
|
|
20056
20539
|
|
|
20057
20540
|
// src/cli.ts
|
|
20058
|
-
import { join as
|
|
20541
|
+
import { join as join30 } from "path";
|
|
20059
20542
|
var home = flyspaceHome();
|
|
20060
20543
|
if (home) {
|
|
20061
|
-
const bin =
|
|
20544
|
+
const bin = join30(home, "bin");
|
|
20062
20545
|
const path = process.env.PATH ?? "";
|
|
20063
20546
|
if (!path.split(":").includes(bin)) process.env.PATH = path ? `${bin}:${path}` : bin;
|
|
20064
20547
|
}
|
|
@@ -20196,7 +20679,7 @@ async function main() {
|
|
|
20196
20679
|
break;
|
|
20197
20680
|
case "-v":
|
|
20198
20681
|
case "--version":
|
|
20199
|
-
printRaw("0.1.
|
|
20682
|
+
printRaw("0.1.35");
|
|
20200
20683
|
break;
|
|
20201
20684
|
case "-h":
|
|
20202
20685
|
case "--help":
|
package/dist/index.js
CHANGED