@supanadit/morse-web 0.3.1 → 0.4.1
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/cli.mjs +37 -19
- package/dist/cli.mjs.map +2 -2
- package/dist/server.mjs +259 -70
- package/dist/server.mjs.map +4 -4
- package/dist/ui/index.html +1 -1
- package/dist/ui/{main-GPUR2OLU.js → main-GW3NH5OS.js} +43 -41
- package/dist/ui/webview.manifest.json +2 -2
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -32,6 +32,7 @@ var CLIENT_MESSAGE_TYPES = [
|
|
|
32
32
|
"session/compact",
|
|
33
33
|
"history/load",
|
|
34
34
|
"session/list",
|
|
35
|
+
"commands/refresh",
|
|
35
36
|
"project/list",
|
|
36
37
|
"project/open",
|
|
37
38
|
"model/set",
|
|
@@ -104,6 +105,11 @@ function frontendIdentity(manifest) {
|
|
|
104
105
|
// ../server/src/app.module.ts
|
|
105
106
|
import { Module } from "@nestjs/common";
|
|
106
107
|
|
|
108
|
+
// ../adapter-pi-rpc/dist/pi-rpc-agent.js
|
|
109
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
110
|
+
import { homedir } from "node:os";
|
|
111
|
+
import { basename, join, resolve } from "node:path";
|
|
112
|
+
|
|
107
113
|
// ../core/dist/domain.js
|
|
108
114
|
var THINKING_LEVELS = [
|
|
109
115
|
"off",
|
|
@@ -205,7 +211,12 @@ var SessionRegistry = class {
|
|
|
205
211
|
if (this.draftCache) {
|
|
206
212
|
return this.draftCache;
|
|
207
213
|
}
|
|
208
|
-
this.draftProbe
|
|
214
|
+
if (!this.draftProbe) {
|
|
215
|
+
this.draftProbe = this.probeDraft().catch((error) => {
|
|
216
|
+
this.draftProbe = void 0;
|
|
217
|
+
throw error;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
209
220
|
return this.draftProbe;
|
|
210
221
|
}
|
|
211
222
|
/** Opens a new (or resumes an existing) session and makes it active. */
|
|
@@ -435,6 +446,9 @@ var SessionRegistry = class {
|
|
|
435
446
|
await gateway.dispose();
|
|
436
447
|
return state;
|
|
437
448
|
} catch (error) {
|
|
449
|
+
if (error instanceof AgentUnavailableError) {
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
438
452
|
this.deps.logger.warn("Could not probe the draft model catalog", error);
|
|
439
453
|
return void 0;
|
|
440
454
|
}
|
|
@@ -1094,14 +1108,14 @@ var PiRpcClient = class {
|
|
|
1094
1108
|
throw this.failed ?? new AgentUnavailableError("The pi agent is not running.");
|
|
1095
1109
|
}
|
|
1096
1110
|
const id = `morse-${this.nextId++}`;
|
|
1097
|
-
return new Promise((
|
|
1111
|
+
return new Promise((resolve5, reject) => {
|
|
1098
1112
|
const timer = setTimeout(() => {
|
|
1099
1113
|
this.pending.delete(id);
|
|
1100
1114
|
reject(new AgentProtocolError(`pi did not answer "${command.type}" within ${timeoutMs} ms.`));
|
|
1101
1115
|
}, timeoutMs);
|
|
1102
1116
|
this.pending.set(id, {
|
|
1103
1117
|
command: command.type,
|
|
1104
|
-
resolve: (data) =>
|
|
1118
|
+
resolve: (data) => resolve5(data),
|
|
1105
1119
|
reject,
|
|
1106
1120
|
timer
|
|
1107
1121
|
});
|
|
@@ -1120,14 +1134,14 @@ var PiRpcClient = class {
|
|
|
1120
1134
|
return;
|
|
1121
1135
|
}
|
|
1122
1136
|
let exited = false;
|
|
1123
|
-
await new Promise((
|
|
1137
|
+
await new Promise((resolve5) => {
|
|
1124
1138
|
const finish = () => {
|
|
1125
1139
|
exited = true;
|
|
1126
|
-
|
|
1140
|
+
resolve5();
|
|
1127
1141
|
};
|
|
1128
1142
|
child.once("exit", finish);
|
|
1129
1143
|
child.stdin.end();
|
|
1130
|
-
setTimeout(
|
|
1144
|
+
setTimeout(resolve5, SHUTDOWN_GRACE_MS).unref?.();
|
|
1131
1145
|
});
|
|
1132
1146
|
if (!exited) {
|
|
1133
1147
|
child.kill();
|
|
@@ -1401,13 +1415,33 @@ var PiRpcAgent = class {
|
|
|
1401
1415
|
thinkingLevel: thinkingLevel ?? this.sessionState.thinkingLevel,
|
|
1402
1416
|
availableModels,
|
|
1403
1417
|
availableThinkingLevels: availableThinkingLevels.length > 0 ? availableThinkingLevels : [...THINKING_LEVELS],
|
|
1404
|
-
availableCommands:
|
|
1418
|
+
availableCommands: await buildCommandList(commands?.commands, this.commandContext()),
|
|
1405
1419
|
streaming: state.isStreaming === true
|
|
1406
1420
|
};
|
|
1407
1421
|
this.ready = true;
|
|
1408
1422
|
await this.refreshStats();
|
|
1409
1423
|
this.emit({ type: "agent/ready", at: this.now(), state: this.snapshotState() });
|
|
1410
1424
|
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Re-reads the resources behind `get_commands` and reports the result the same
|
|
1427
|
+
* way a fresh spawn would. The palette calls it the moment it opens, so a new
|
|
1428
|
+
* or edited template is there by the time the reader finishes typing.
|
|
1429
|
+
*/
|
|
1430
|
+
async refreshCommands() {
|
|
1431
|
+
if (this.disposed || !this.ready) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1434
|
+
const commands = await this.client.request({ type: "get_commands" }).catch(() => void 0);
|
|
1435
|
+
this.sessionState = {
|
|
1436
|
+
...this.sessionState,
|
|
1437
|
+
availableCommands: await buildCommandList(commands?.commands, this.commandContext())
|
|
1438
|
+
};
|
|
1439
|
+
this.emit({ type: "agent/state", at: this.now(), state: this.snapshotState() });
|
|
1440
|
+
}
|
|
1441
|
+
/** Where a command list is rebuilt from: the session's workspace and env. */
|
|
1442
|
+
commandContext() {
|
|
1443
|
+
return { cwd: this.options.workspace.cwd, env: this.options.env };
|
|
1444
|
+
}
|
|
1411
1445
|
/**
|
|
1412
1446
|
* Pulls the cumulative token/cost/context totals pi keeps for the session, so
|
|
1413
1447
|
* the footer can show the same numbers the TUI does. Best-effort: an older pi
|
|
@@ -1547,14 +1581,141 @@ function dedupeThinkingLevels(levels) {
|
|
|
1547
1581
|
function toCommandSource(value) {
|
|
1548
1582
|
return value === "prompt" || value === "skill" ? value : "extension";
|
|
1549
1583
|
}
|
|
1550
|
-
function
|
|
1551
|
-
|
|
1552
|
-
return
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1584
|
+
async function readTemplateFile(path) {
|
|
1585
|
+
if (path === void 0 || path.length === 0) {
|
|
1586
|
+
return void 0;
|
|
1587
|
+
}
|
|
1588
|
+
try {
|
|
1589
|
+
return await readFile(path, "utf8");
|
|
1590
|
+
} catch {
|
|
1591
|
+
return void 0;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
async function buildCommandList(items, context) {
|
|
1595
|
+
const commands = [];
|
|
1596
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1597
|
+
let projectTrusted = false;
|
|
1598
|
+
for (const item of items ?? []) {
|
|
1599
|
+
if (typeof item.name !== "string" || item.name.trim().length === 0) {
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
const source = toCommandSource(item.source);
|
|
1603
|
+
if (source !== "prompt") {
|
|
1604
|
+
commands.push({ name: item.name, description: item.description, source });
|
|
1605
|
+
seen.add(item.name);
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
if (item.sourceInfo?.scope === "project") {
|
|
1609
|
+
projectTrusted = true;
|
|
1610
|
+
}
|
|
1611
|
+
const template = await readTemplateFile(item.sourceInfo?.path);
|
|
1612
|
+
if (template === void 0) {
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
commands.push({
|
|
1616
|
+
name: item.name,
|
|
1617
|
+
// Prefer the freshly parsed description so an edited frontmatter reflects
|
|
1618
|
+
// too; pi's cached description is the fallback.
|
|
1619
|
+
description: promptDescription(template) ?? item.description,
|
|
1620
|
+
source,
|
|
1621
|
+
template
|
|
1622
|
+
});
|
|
1623
|
+
seen.add(item.name);
|
|
1624
|
+
}
|
|
1625
|
+
for (const dir of await promptDirs(projectTrusted, context)) {
|
|
1626
|
+
for (const file of await listPromptFiles(dir)) {
|
|
1627
|
+
if (seen.has(file.name)) {
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
const template = await readTemplateFile(file.path);
|
|
1631
|
+
if (template === void 0) {
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
commands.push({
|
|
1635
|
+
name: file.name,
|
|
1636
|
+
description: promptDescription(template),
|
|
1637
|
+
source: "prompt",
|
|
1638
|
+
template
|
|
1639
|
+
});
|
|
1640
|
+
seen.add(file.name);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return commands;
|
|
1644
|
+
}
|
|
1645
|
+
async function promptDirs(projectTrusted, context) {
|
|
1646
|
+
const dirs = [join(agentDir(context.env), "prompts")];
|
|
1647
|
+
if (projectTrusted || await isProjectTrusted(context.cwd, context.env)) {
|
|
1648
|
+
dirs.push(resolve(context.cwd, ".pi", "prompts"));
|
|
1649
|
+
}
|
|
1650
|
+
return dirs;
|
|
1651
|
+
}
|
|
1652
|
+
function agentDir(env) {
|
|
1653
|
+
const configured = env?.PI_CODING_AGENT_DIR?.trim();
|
|
1654
|
+
return configured && configured.length > 0 ? expandHome(configured) : join(homedir(), ".pi", "agent");
|
|
1655
|
+
}
|
|
1656
|
+
function expandHome(path) {
|
|
1657
|
+
if (path === "~") {
|
|
1658
|
+
return homedir();
|
|
1659
|
+
}
|
|
1660
|
+
if (path.startsWith("~/") || path.startsWith("~\\")) {
|
|
1661
|
+
return join(homedir(), path.slice(2));
|
|
1662
|
+
}
|
|
1663
|
+
return path;
|
|
1664
|
+
}
|
|
1665
|
+
async function isProjectTrusted(cwd, env) {
|
|
1666
|
+
try {
|
|
1667
|
+
const raw = await readFile(join(agentDir(env), "trust.json"), "utf8");
|
|
1668
|
+
const decisions = JSON.parse(raw);
|
|
1669
|
+
return decisions[cwd] === true || decisions[resolve(cwd)] === true;
|
|
1670
|
+
} catch {
|
|
1671
|
+
return false;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
async function listPromptFiles(dir) {
|
|
1675
|
+
const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
1676
|
+
const files = [];
|
|
1677
|
+
for (const entry of entries) {
|
|
1678
|
+
if (!entry.name.endsWith(".md")) {
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
const path = join(dir, entry.name);
|
|
1682
|
+
let isFile = entry.isFile();
|
|
1683
|
+
if (entry.isSymbolicLink()) {
|
|
1684
|
+
try {
|
|
1685
|
+
isFile = (await stat(path)).isFile();
|
|
1686
|
+
} catch {
|
|
1687
|
+
continue;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
if (isFile) {
|
|
1691
|
+
files.push({ name: basename(entry.name, ".md"), path });
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
return files;
|
|
1695
|
+
}
|
|
1696
|
+
function promptDescription(raw) {
|
|
1697
|
+
let body = raw;
|
|
1698
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(raw);
|
|
1699
|
+
if (frontmatter !== null) {
|
|
1700
|
+
const described = /^description:[ \t]*(.*)$/m.exec(frontmatter[1]);
|
|
1701
|
+
if (described !== null) {
|
|
1702
|
+
const value = unquote(described[1].trim());
|
|
1703
|
+
if (value.length > 0) {
|
|
1704
|
+
return value;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
body = raw.slice(frontmatter[0].length);
|
|
1708
|
+
}
|
|
1709
|
+
const firstLine = body.split("\n").find((line) => line.trim().length > 0);
|
|
1710
|
+
if (firstLine === void 0) {
|
|
1711
|
+
return void 0;
|
|
1712
|
+
}
|
|
1713
|
+
const trimmed = firstLine.trim();
|
|
1714
|
+
return trimmed.length > 60 ? `${trimmed.slice(0, 60)}...` : trimmed;
|
|
1715
|
+
}
|
|
1716
|
+
function unquote(value) {
|
|
1717
|
+
const match = /^(['"])([\s\S]*)\1$/.exec(value);
|
|
1718
|
+
return match === null ? value : match[2];
|
|
1558
1719
|
}
|
|
1559
1720
|
function toNoticeLevel(value) {
|
|
1560
1721
|
if (value === "error") {
|
|
@@ -1590,8 +1751,8 @@ ${mentions}` : mentions;
|
|
|
1590
1751
|
|
|
1591
1752
|
// ../adapter-pi-rpc/dist/internal/resolve-pi.js
|
|
1592
1753
|
import { accessSync, constants } from "node:fs";
|
|
1593
|
-
import { homedir } from "node:os";
|
|
1594
|
-
import { delimiter, isAbsolute, join } from "node:path";
|
|
1754
|
+
import { homedir as homedir2 } from "node:os";
|
|
1755
|
+
import { delimiter, isAbsolute, join as join2 } from "node:path";
|
|
1595
1756
|
var PI_INSTALL_COMMAND = "npm install -g @earendil-works/pi-coding-agent";
|
|
1596
1757
|
function buildRpcArgs(options) {
|
|
1597
1758
|
const args = ["--mode", "rpc"];
|
|
@@ -1640,7 +1801,7 @@ function findOnPath(binary, env = process.env) {
|
|
|
1640
1801
|
continue;
|
|
1641
1802
|
}
|
|
1642
1803
|
for (const extension of extensions) {
|
|
1643
|
-
const candidate =
|
|
1804
|
+
const candidate = join2(directory, `${binary}${extension}`);
|
|
1644
1805
|
if (isExecutableEntry(candidate)) {
|
|
1645
1806
|
return candidate;
|
|
1646
1807
|
}
|
|
@@ -1651,8 +1812,8 @@ function findOnPath(binary, env = process.env) {
|
|
|
1651
1812
|
function resolveSessionDir(configured, env = process.env) {
|
|
1652
1813
|
const fromEnv = env.PI_CODING_AGENT_SESSION_DIR;
|
|
1653
1814
|
const value = configured ?? (fromEnv && fromEnv.length > 0 ? fromEnv : void 0);
|
|
1654
|
-
const base = value ??
|
|
1655
|
-
return isAbsolute(base) ? base :
|
|
1815
|
+
const base = value ?? join2(homedir2(), ".pi", "agent", "sessions");
|
|
1816
|
+
return isAbsolute(base) ? base : join2(process.cwd(), base);
|
|
1656
1817
|
}
|
|
1657
1818
|
function isExecutableEntry(path) {
|
|
1658
1819
|
try {
|
|
@@ -1719,8 +1880,8 @@ var PiRpcAgentFactory = class {
|
|
|
1719
1880
|
};
|
|
1720
1881
|
|
|
1721
1882
|
// ../adapter-pi-rpc/dist/pi-rpc-session-catalog.js
|
|
1722
|
-
import { open, readdir, readFile, stat, unlink } from "node:fs/promises";
|
|
1723
|
-
import { join as
|
|
1883
|
+
import { open, readdir as readdir2, readFile as readFile2, stat as stat2, unlink } from "node:fs/promises";
|
|
1884
|
+
import { join as join3, resolve as resolve2, sep } from "node:path";
|
|
1724
1885
|
var TYPE_PREFIX = '{"type":"';
|
|
1725
1886
|
function typeAt(raw, start, end) {
|
|
1726
1887
|
if (!raw.startsWith(TYPE_PREFIX, start)) {
|
|
@@ -1795,8 +1956,8 @@ var PiRpcSessionCatalog = class {
|
|
|
1795
1956
|
* a delete of an arbitrary file.
|
|
1796
1957
|
*/
|
|
1797
1958
|
async remove(id) {
|
|
1798
|
-
const path =
|
|
1799
|
-
const directory =
|
|
1959
|
+
const path = resolve2(id);
|
|
1960
|
+
const directory = resolve2(this.directory);
|
|
1800
1961
|
if (!path.startsWith(`${directory}${sep}`) || !path.endsWith(".jsonl")) {
|
|
1801
1962
|
throw new Error(`Refusing to delete a session outside ${directory}`);
|
|
1802
1963
|
}
|
|
@@ -1809,13 +1970,13 @@ var PiRpcSessionCatalog = class {
|
|
|
1809
1970
|
if (!bucket.isDirectory()) {
|
|
1810
1971
|
continue;
|
|
1811
1972
|
}
|
|
1812
|
-
const bucketPath =
|
|
1973
|
+
const bucketPath = join3(this.directory, bucket.name);
|
|
1813
1974
|
for (const entry of await safeReadDir(bucketPath)) {
|
|
1814
1975
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) {
|
|
1815
1976
|
continue;
|
|
1816
1977
|
}
|
|
1817
|
-
const path =
|
|
1818
|
-
const info = await
|
|
1978
|
+
const path = join3(bucketPath, entry.name);
|
|
1979
|
+
const info = await stat2(path).catch(() => void 0);
|
|
1819
1980
|
files.push({ path, size: info?.size ?? 0, updatedAt: info?.mtimeMs ?? 0 });
|
|
1820
1981
|
}
|
|
1821
1982
|
}
|
|
@@ -1826,11 +1987,11 @@ var PiRpcSessionCatalog = class {
|
|
|
1826
1987
|
async function readSessionSummary(path, updatedAt, previous) {
|
|
1827
1988
|
let resumable = previous !== void 0 && previous.scanned > 0 && updatedAt >= previous.updatedAt;
|
|
1828
1989
|
if (resumable && previous !== void 0) {
|
|
1829
|
-
const info = await
|
|
1990
|
+
const info = await stat2(path).catch(() => void 0);
|
|
1830
1991
|
resumable = info !== void 0 && info.size > previous.scanned;
|
|
1831
1992
|
}
|
|
1832
1993
|
const from = resumable && previous !== void 0 ? previous.scanned : 0;
|
|
1833
|
-
const raw = resumable ? await readFrom(path, from).catch(() => void 0) : await
|
|
1994
|
+
const raw = resumable ? await readFrom(path, from).catch(() => void 0) : await readFile2(path, "utf8").catch(() => void 0);
|
|
1834
1995
|
if (raw === void 0) {
|
|
1835
1996
|
return void 0;
|
|
1836
1997
|
}
|
|
@@ -1951,7 +2112,7 @@ function titleFromText(text) {
|
|
|
1951
2112
|
}
|
|
1952
2113
|
async function safeReadDir(path) {
|
|
1953
2114
|
try {
|
|
1954
|
-
return await
|
|
2115
|
+
return await readdir2(path, { withFileTypes: true });
|
|
1955
2116
|
} catch {
|
|
1956
2117
|
return [];
|
|
1957
2118
|
}
|
|
@@ -2401,7 +2562,8 @@ function toCommandOption(command) {
|
|
|
2401
2562
|
return {
|
|
2402
2563
|
name: command.name,
|
|
2403
2564
|
description: command.description,
|
|
2404
|
-
source: command.source
|
|
2565
|
+
source: command.source,
|
|
2566
|
+
template: command.template
|
|
2405
2567
|
};
|
|
2406
2568
|
}
|
|
2407
2569
|
function toThinkingLevel2(level) {
|
|
@@ -2591,6 +2753,9 @@ var HostSessionController = class {
|
|
|
2591
2753
|
case "session/list":
|
|
2592
2754
|
await this.publishSessions();
|
|
2593
2755
|
return;
|
|
2756
|
+
case "commands/refresh":
|
|
2757
|
+
await this.refreshCommands();
|
|
2758
|
+
return;
|
|
2594
2759
|
case "project/list":
|
|
2595
2760
|
await this.publishProjects();
|
|
2596
2761
|
return;
|
|
@@ -2660,7 +2825,15 @@ var HostSessionController = class {
|
|
|
2660
2825
|
}
|
|
2661
2826
|
/** Fills the draft pickers from the registry's session-less probe. */
|
|
2662
2827
|
async warmDraft() {
|
|
2663
|
-
|
|
2828
|
+
try {
|
|
2829
|
+
this.draftCatalog = await this.options.services.registry.draftDefaults();
|
|
2830
|
+
} catch (error) {
|
|
2831
|
+
this.agentError = describeError(error);
|
|
2832
|
+
this.agentFailure = describeAgentFailure(error, this.options.agentHint);
|
|
2833
|
+
this.options.logger.warn("Could not probe the draft model catalog", error);
|
|
2834
|
+
this.emitState();
|
|
2835
|
+
return;
|
|
2836
|
+
}
|
|
2664
2837
|
if (this.activeKey === void 0 && !this.disposed) {
|
|
2665
2838
|
this.emitState();
|
|
2666
2839
|
}
|
|
@@ -3013,6 +3186,18 @@ var HostSessionController = class {
|
|
|
3013
3186
|
}
|
|
3014
3187
|
await this.guard(() => gateway.respondToInteraction(response));
|
|
3015
3188
|
}
|
|
3189
|
+
/**
|
|
3190
|
+
* The palette asked for a fresh command list. The agent re-reads the template
|
|
3191
|
+
* files and reports back through `agent/state`, so the composer sees a template
|
|
3192
|
+
* added, changed or deleted since the session started — without a pi reload.
|
|
3193
|
+
*/
|
|
3194
|
+
async refreshCommands() {
|
|
3195
|
+
const gateway = this.options.services.registry.active();
|
|
3196
|
+
if (gateway?.refreshCommands === void 0) {
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
await this.guard(() => gateway.refreshCommands());
|
|
3200
|
+
}
|
|
3016
3201
|
async runHostCommand(command, args, requestId) {
|
|
3017
3202
|
const handler = this.options.onHostCommand;
|
|
3018
3203
|
if (!handler) {
|
|
@@ -3353,8 +3538,8 @@ function hintOnly(hint) {
|
|
|
3353
3538
|
|
|
3354
3539
|
// ../server/src/internal/http/health.controller.ts
|
|
3355
3540
|
import { Controller, Get } from "@nestjs/common";
|
|
3356
|
-
import { readFile as
|
|
3357
|
-
import { join as
|
|
3541
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
3542
|
+
import { join as join4 } from "node:path";
|
|
3358
3543
|
import { Inject } from "@nestjs/common";
|
|
3359
3544
|
|
|
3360
3545
|
// ../server/src/app/tokens.ts
|
|
@@ -3375,11 +3560,14 @@ var HealthController = class {
|
|
|
3375
3560
|
status: "ok",
|
|
3376
3561
|
protocolVersion: PROTOCOL_VERSION,
|
|
3377
3562
|
workspace: this.config.workspace.cwd,
|
|
3378
|
-
frontend: this.config.uiDir
|
|
3563
|
+
frontend: this.config.uiDir,
|
|
3564
|
+
// Echoed so the CLI that spawned this daemon can tell it apart from an
|
|
3565
|
+
// unrelated process that later reused the same pid.
|
|
3566
|
+
...this.config.instance ? { instance: this.config.instance } : {}
|
|
3379
3567
|
};
|
|
3380
3568
|
}
|
|
3381
3569
|
async manifest() {
|
|
3382
|
-
const raw = await
|
|
3570
|
+
const raw = await readFile3(join4(this.config.uiDir, "webview.manifest.json"), "utf8").catch(
|
|
3383
3571
|
() => void 0
|
|
3384
3572
|
);
|
|
3385
3573
|
return parseFrontendManifest(raw) ?? { name: "unknown", protocolVersion: PROTOCOL_VERSION };
|
|
@@ -3430,10 +3618,10 @@ function withDetail(message, detail) {
|
|
|
3430
3618
|
}
|
|
3431
3619
|
|
|
3432
3620
|
// ../server/src/internal/projects/project-policy.ts
|
|
3433
|
-
import { isAbsolute as isAbsolute2, resolve as
|
|
3621
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
3434
3622
|
var ServerProjectPolicy = class {
|
|
3435
3623
|
constructor(roots) {
|
|
3436
|
-
this.roots = roots.filter((root) => root.trim().length > 0).map((root) =>
|
|
3624
|
+
this.roots = roots.filter((root) => root.trim().length > 0).map((root) => resolve3(root));
|
|
3437
3625
|
}
|
|
3438
3626
|
get allowList() {
|
|
3439
3627
|
return [...this.roots];
|
|
@@ -3445,7 +3633,7 @@ var ServerProjectPolicy = class {
|
|
|
3445
3633
|
if (this.roots.length === 0) {
|
|
3446
3634
|
return true;
|
|
3447
3635
|
}
|
|
3448
|
-
const target =
|
|
3636
|
+
const target = resolve3(path);
|
|
3449
3637
|
return this.roots.some(
|
|
3450
3638
|
(root) => target === root || target.startsWith(`${root}${sep2}`)
|
|
3451
3639
|
);
|
|
@@ -3491,7 +3679,7 @@ import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
|
|
|
3491
3679
|
// ../server/src/internal/uploads/upload-store.ts
|
|
3492
3680
|
import { existsSync } from "node:fs";
|
|
3493
3681
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3494
|
-
import { basename, isAbsolute as isAbsolute3, join as
|
|
3682
|
+
import { basename as basename2, isAbsolute as isAbsolute3, join as join5, relative, sep as sep3 } from "node:path";
|
|
3495
3683
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
3496
3684
|
var DEFAULT_UPLOAD_DIR = ".morse/uploads";
|
|
3497
3685
|
var UploadError = class extends Error {
|
|
@@ -3511,15 +3699,15 @@ async function saveUpload(cwd, request, uploadDir = DEFAULT_UPLOAD_DIR) {
|
|
|
3511
3699
|
`That file is ${formatMb(bytes.length)} MB; the limit is ${formatMb(MAX_UPLOAD_BYTES)} MB.`
|
|
3512
3700
|
);
|
|
3513
3701
|
}
|
|
3514
|
-
const directory = isAbsolute3(uploadDir) ? uploadDir :
|
|
3702
|
+
const directory = isAbsolute3(uploadDir) ? uploadDir : join5(cwd, uploadDir);
|
|
3515
3703
|
await mkdir(directory, { recursive: true });
|
|
3516
3704
|
const target = uniquePath(directory, name);
|
|
3517
3705
|
await writeFile(target, bytes);
|
|
3518
|
-
return { path: mentionPath(cwd, target), name:
|
|
3706
|
+
return { path: mentionPath(cwd, target), name: basename2(target), bytes: bytes.length };
|
|
3519
3707
|
}
|
|
3520
3708
|
function safeFileName(raw) {
|
|
3521
3709
|
const value = typeof raw === "string" ? raw : "";
|
|
3522
|
-
const base =
|
|
3710
|
+
const base = basename2(value.replace(/\\/g, "/")).trim();
|
|
3523
3711
|
const cleaned = base.replace(/[\u0000-\u001f<>:"|?*]/g, "_").replace(/^\.+/, "").trim();
|
|
3524
3712
|
if (cleaned.length === 0) {
|
|
3525
3713
|
throw new UploadError("That upload had no usable file name.");
|
|
@@ -3530,10 +3718,10 @@ function uniquePath(directory, name) {
|
|
|
3530
3718
|
const dot = name.lastIndexOf(".");
|
|
3531
3719
|
const stem = dot > 0 ? name.slice(0, dot) : name;
|
|
3532
3720
|
const extension = dot > 0 ? name.slice(dot) : "";
|
|
3533
|
-
let candidate =
|
|
3721
|
+
let candidate = join5(directory, name);
|
|
3534
3722
|
let suffix = 1;
|
|
3535
3723
|
while (existsSync(candidate)) {
|
|
3536
|
-
candidate =
|
|
3724
|
+
candidate = join5(directory, `${stem}-${suffix}${extension}`);
|
|
3537
3725
|
suffix += 1;
|
|
3538
3726
|
}
|
|
3539
3727
|
return candidate;
|
|
@@ -3548,9 +3736,9 @@ function formatMb(bytes) {
|
|
|
3548
3736
|
}
|
|
3549
3737
|
|
|
3550
3738
|
// ../server/src/internal/workspace/directory-browser.ts
|
|
3551
|
-
import { readdir as
|
|
3552
|
-
import { homedir as
|
|
3553
|
-
import { basename as
|
|
3739
|
+
import { readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
3740
|
+
import { homedir as homedir3 } from "node:os";
|
|
3741
|
+
import { basename as basename3, dirname, isAbsolute as isAbsolute4, join as join6, resolve as resolve4 } from "node:path";
|
|
3554
3742
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
3555
3743
|
"node_modules",
|
|
3556
3744
|
"dist",
|
|
@@ -3569,12 +3757,12 @@ var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
3569
3757
|
]);
|
|
3570
3758
|
async function browseDirectory(path, options) {
|
|
3571
3759
|
const requested = path?.trim();
|
|
3572
|
-
const start =
|
|
3760
|
+
const start = resolve4(requested && requested.length > 0 ? requested : options.defaultPath);
|
|
3573
3761
|
if (requested && !isAbsolute4(requested)) {
|
|
3574
3762
|
throw new Error(`A folder path must be absolute: ${requested}`);
|
|
3575
3763
|
}
|
|
3576
|
-
const entries = await
|
|
3577
|
-
const directories = entries.filter((entry) => entry.isDirectory()).filter((entry) => !shouldSkip(entry.name)).map((entry) => ({ name: entry.name, path:
|
|
3764
|
+
const entries = await readdir3(start, { withFileTypes: true });
|
|
3765
|
+
const directories = entries.filter((entry) => entry.isDirectory()).filter((entry) => !shouldSkip(entry.name)).map((entry) => ({ name: entry.name, path: join6(start, entry.name) })).sort((left, right) => left.name.localeCompare(right.name));
|
|
3578
3766
|
const parent = dirname(start);
|
|
3579
3767
|
return {
|
|
3580
3768
|
path: start,
|
|
@@ -3589,7 +3777,7 @@ function shouldSkip(name) {
|
|
|
3589
3777
|
}
|
|
3590
3778
|
async function isGitRepo(path) {
|
|
3591
3779
|
try {
|
|
3592
|
-
await
|
|
3780
|
+
await stat3(join6(path, ".git"));
|
|
3593
3781
|
return true;
|
|
3594
3782
|
} catch {
|
|
3595
3783
|
return false;
|
|
@@ -3598,24 +3786,24 @@ async function isGitRepo(path) {
|
|
|
3598
3786
|
function shortcutRoots(options) {
|
|
3599
3787
|
const seen = /* @__PURE__ */ new Set();
|
|
3600
3788
|
const roots = [];
|
|
3601
|
-
for (const candidate of [...options.roots ?? [],
|
|
3789
|
+
for (const candidate of [...options.roots ?? [], homedir3(), options.defaultPath]) {
|
|
3602
3790
|
if (!candidate || !isAbsolute4(candidate)) {
|
|
3603
3791
|
continue;
|
|
3604
3792
|
}
|
|
3605
|
-
const path =
|
|
3793
|
+
const path = resolve4(candidate);
|
|
3606
3794
|
if (seen.has(path)) {
|
|
3607
3795
|
continue;
|
|
3608
3796
|
}
|
|
3609
3797
|
seen.add(path);
|
|
3610
|
-
roots.push({ name:
|
|
3798
|
+
roots.push({ name: basename3(path) || path, path });
|
|
3611
3799
|
}
|
|
3612
3800
|
return roots;
|
|
3613
3801
|
}
|
|
3614
3802
|
|
|
3615
3803
|
// ../server/src/internal/workspace/workspace-index.ts
|
|
3616
3804
|
import { execFile } from "node:child_process";
|
|
3617
|
-
import { readdir as
|
|
3618
|
-
import { join as
|
|
3805
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
3806
|
+
import { join as join7, relative as relative2 } from "node:path";
|
|
3619
3807
|
var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set([
|
|
3620
3808
|
"node_modules",
|
|
3621
3809
|
"dist",
|
|
@@ -3699,12 +3887,12 @@ async function gitFileList(cwd) {
|
|
|
3699
3887
|
}
|
|
3700
3888
|
}
|
|
3701
3889
|
function runGit(args, cwd) {
|
|
3702
|
-
return new Promise((
|
|
3890
|
+
return new Promise((resolve5) => {
|
|
3703
3891
|
execFile(
|
|
3704
3892
|
"git",
|
|
3705
3893
|
args,
|
|
3706
3894
|
{ cwd, timeout: 5e3, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
|
|
3707
|
-
(error, stdout) =>
|
|
3895
|
+
(error, stdout) => resolve5(error ? void 0 : stdout)
|
|
3708
3896
|
);
|
|
3709
3897
|
});
|
|
3710
3898
|
}
|
|
@@ -3718,7 +3906,7 @@ async function walk(root, directory, files) {
|
|
|
3718
3906
|
}
|
|
3719
3907
|
let entries;
|
|
3720
3908
|
try {
|
|
3721
|
-
entries = await
|
|
3909
|
+
entries = await readdir4(directory, { withFileTypes: true });
|
|
3722
3910
|
} catch {
|
|
3723
3911
|
return;
|
|
3724
3912
|
}
|
|
@@ -3726,7 +3914,7 @@ async function walk(root, directory, files) {
|
|
|
3726
3914
|
if (files.length >= MAX_INDEXED_FILES) {
|
|
3727
3915
|
return;
|
|
3728
3916
|
}
|
|
3729
|
-
const full =
|
|
3917
|
+
const full = join7(directory, entry.name);
|
|
3730
3918
|
if (entry.isDirectory()) {
|
|
3731
3919
|
if (SKIPPED_DIRECTORIES2.has(entry.name) || entry.name.startsWith(".")) {
|
|
3732
3920
|
continue;
|
|
@@ -3921,24 +4109,24 @@ MorseGateway = __decorateClass([
|
|
|
3921
4109
|
], MorseGateway);
|
|
3922
4110
|
|
|
3923
4111
|
// ../server/src/app/config.ts
|
|
3924
|
-
import { basename as
|
|
4112
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute5, join as join8 } from "node:path";
|
|
3925
4113
|
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
3926
4114
|
import { fileURLToPath } from "node:url";
|
|
3927
4115
|
function loadConfig(env = process.env) {
|
|
3928
4116
|
const workspacePath = resolveWorkspacePath(env);
|
|
3929
4117
|
const configuredUiDir = env.MORSE_UI_DIR;
|
|
3930
4118
|
const moduleDir = dirname2(fileURLToPath(import.meta.url));
|
|
3931
|
-
const bundledUiDir =
|
|
3932
|
-
const monorepoUiDir =
|
|
3933
|
-
const defaultUiDir = existsSync2(
|
|
4119
|
+
const bundledUiDir = join8(moduleDir, "ui");
|
|
4120
|
+
const monorepoUiDir = join8(moduleDir, "..", "..", "..", "ui-angular", "dist");
|
|
4121
|
+
const defaultUiDir = existsSync2(join8(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
|
|
3934
4122
|
const defaultHost = "127.0.0.1";
|
|
3935
|
-
const uiDir = configuredUiDir ? isAbsolute5(configuredUiDir) ? configuredUiDir :
|
|
4123
|
+
const uiDir = configuredUiDir ? isAbsolute5(configuredUiDir) ? configuredUiDir : join8(process.cwd(), configuredUiDir) : defaultUiDir;
|
|
3936
4124
|
return {
|
|
3937
4125
|
host: env.MORSE_HOST ?? defaultHost,
|
|
3938
4126
|
port: parsePositiveInt(env.MORSE_PORT, 4399),
|
|
3939
4127
|
uiDir,
|
|
3940
4128
|
frontend: readFrontendIdentity(uiDir),
|
|
3941
|
-
workspace: { cwd: workspacePath, name:
|
|
4129
|
+
workspace: { cwd: workspacePath, name: basename4(workspacePath) || workspacePath },
|
|
3942
4130
|
piPath: env.MORSE_PI_PATH,
|
|
3943
4131
|
nodeEntryPath: env.MORSE_PI_ENTRY,
|
|
3944
4132
|
sessionDir: env.MORSE_SESSION_DIR,
|
|
@@ -3947,13 +4135,14 @@ function loadConfig(env = process.env) {
|
|
|
3947
4135
|
hotSessions: parsePositiveInt(env.MORSE_HOT_SESSIONS, 4),
|
|
3948
4136
|
projects: parseList(env.MORSE_PROJECTS),
|
|
3949
4137
|
uploadDir: env.MORSE_UPLOAD_DIR?.trim() || DEFAULT_UPLOAD_DIR,
|
|
3950
|
-
updateCheck: !isOff(env.MORSE_UPDATE_CHECK)
|
|
4138
|
+
updateCheck: !isOff(env.MORSE_UPDATE_CHECK),
|
|
4139
|
+
instance: env.MORSE_INSTANCE?.trim() || void 0
|
|
3951
4140
|
};
|
|
3952
4141
|
}
|
|
3953
4142
|
function readFrontendIdentity(uiDir) {
|
|
3954
4143
|
try {
|
|
3955
4144
|
return frontendIdentity(
|
|
3956
|
-
parseFrontendManifest(readFileSync(
|
|
4145
|
+
parseFrontendManifest(readFileSync(join8(uiDir, FRONTEND_MANIFEST_FILE), "utf8"))
|
|
3957
4146
|
);
|
|
3958
4147
|
} catch {
|
|
3959
4148
|
return void 0;
|
|
@@ -3984,7 +4173,7 @@ function resolveWorkspacePath(env) {
|
|
|
3984
4173
|
const start = process.cwd();
|
|
3985
4174
|
let directory = start;
|
|
3986
4175
|
for (; ; ) {
|
|
3987
|
-
const manifest =
|
|
4176
|
+
const manifest = join8(directory, "package.json");
|
|
3988
4177
|
if (existsSync2(manifest) && declaresWorkspaces(manifest)) {
|
|
3989
4178
|
return directory;
|
|
3990
4179
|
}
|