@supanadit/morse-web 0.2.1 → 0.3.0
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/LICENSE +1 -1
- package/dist/server.mjs +223 -60
- package/dist/server.mjs.map +3 -3
- package/dist/ui/fonts/LICENSE.txt +93 -0
- package/dist/ui/fonts/README.md +23 -0
- package/dist/ui/fonts/jetbrains-mono-latin-ext-wght-italic.woff2 +0 -0
- package/dist/ui/fonts/jetbrains-mono-latin-ext-wght-normal.woff2 +0 -0
- package/dist/ui/fonts/jetbrains-mono-latin-wght-italic.woff2 +0 -0
- package/dist/ui/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
- package/dist/ui/index.html +3 -3
- package/dist/ui/main-QNDG2ZHO.js +88 -0
- package/dist/ui/styles-6KHUSJIS.css +1 -0
- package/dist/ui/webview.manifest.json +3 -3
- package/package.json +2 -1
- package/dist/ui/main-6YN6VQNO.js +0 -88
- package/dist/ui/styles-WKFZG3MN.css +0 -1
package/LICENSE
CHANGED
package/dist/server.mjs
CHANGED
|
@@ -74,7 +74,8 @@ function hasKnownType(value, allowed) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// ../protocol/dist/version.js
|
|
77
|
-
var PROTOCOL_VERSION =
|
|
77
|
+
var PROTOCOL_VERSION = 17;
|
|
78
|
+
var FRONTEND_MANIFEST_FILE = "webview.manifest.json";
|
|
78
79
|
var DEFAULT_WS_PATH = "/ws";
|
|
79
80
|
|
|
80
81
|
// ../protocol/dist/frontend-manifest.js
|
|
@@ -96,6 +97,9 @@ function parseFrontendManifest(raw) {
|
|
|
96
97
|
return void 0;
|
|
97
98
|
}
|
|
98
99
|
}
|
|
100
|
+
function frontendIdentity(manifest) {
|
|
101
|
+
return manifest ? { name: manifest.name, version: manifest.version } : void 0;
|
|
102
|
+
}
|
|
99
103
|
|
|
100
104
|
// ../server/src/app.module.ts
|
|
101
105
|
import { Module } from "@nestjs/common";
|
|
@@ -121,8 +125,16 @@ var MorseError = class extends Error {
|
|
|
121
125
|
}
|
|
122
126
|
};
|
|
123
127
|
var AgentUnavailableError = class extends MorseError {
|
|
128
|
+
/**
|
|
129
|
+
* The next step, in a form a frontend can render (see `SessionViewState
|
|
130
|
+
* .agentFailure`). The message stays human-readable on its own; this is the
|
|
131
|
+
* machine-readable half, and only the layer that knows pi's package name can
|
|
132
|
+
* fill it in.
|
|
133
|
+
*/
|
|
134
|
+
remedy;
|
|
124
135
|
constructor(message, options) {
|
|
125
136
|
super(message, "agent-unavailable", options);
|
|
137
|
+
this.remedy = options?.remedy;
|
|
126
138
|
}
|
|
127
139
|
};
|
|
128
140
|
var AgentProtocolError = class extends MorseError {
|
|
@@ -1580,6 +1592,7 @@ ${mentions}` : mentions;
|
|
|
1580
1592
|
import { accessSync, constants } from "node:fs";
|
|
1581
1593
|
import { homedir } from "node:os";
|
|
1582
1594
|
import { delimiter, isAbsolute, join } from "node:path";
|
|
1595
|
+
var PI_INSTALL_COMMAND = "npm install -g @earendil-works/pi-coding-agent";
|
|
1583
1596
|
function buildRpcArgs(options) {
|
|
1584
1597
|
const args = ["--mode", "rpc"];
|
|
1585
1598
|
if (options.noSession) {
|
|
@@ -1611,10 +1624,10 @@ function resolvePi(options = {}) {
|
|
|
1611
1624
|
}
|
|
1612
1625
|
throw new AgentUnavailableError([
|
|
1613
1626
|
"The pi coding agent was not found.",
|
|
1614
|
-
|
|
1627
|
+
`Install it (\`${PI_INSTALL_COMMAND}\`) or point Morse at it:`,
|
|
1615
1628
|
'- VS Code: setting "morse.pi.path"',
|
|
1616
1629
|
"- NestJS host: env MORSE_PI_PATH (or MORSE_PI_ENTRY for a bundled rpc-entry.js)"
|
|
1617
|
-
].join("\n"));
|
|
1630
|
+
].join("\n"), { remedy: { install: PI_INSTALL_COMMAND } });
|
|
1618
1631
|
}
|
|
1619
1632
|
function findOnPath(binary, env = process.env) {
|
|
1620
1633
|
const pathValue = env.PATH ?? env.Path ?? "";
|
|
@@ -1706,27 +1719,71 @@ var PiRpcAgentFactory = class {
|
|
|
1706
1719
|
};
|
|
1707
1720
|
|
|
1708
1721
|
// ../adapter-pi-rpc/dist/pi-rpc-session-catalog.js
|
|
1709
|
-
import { readdir, readFile, stat, unlink } from "node:fs/promises";
|
|
1710
|
-
import {
|
|
1722
|
+
import { open, readdir, readFile, stat, unlink } from "node:fs/promises";
|
|
1723
|
+
import { join as join2, resolve, sep } from "node:path";
|
|
1724
|
+
var TYPE_PREFIX = '{"type":"';
|
|
1725
|
+
function typeAt(raw, start, end) {
|
|
1726
|
+
if (!raw.startsWith(TYPE_PREFIX, start)) {
|
|
1727
|
+
return void 0;
|
|
1728
|
+
}
|
|
1729
|
+
const close = raw.indexOf('"', start + TYPE_PREFIX.length);
|
|
1730
|
+
return close === -1 || close > end ? void 0 : raw.slice(start + TYPE_PREFIX.length, close);
|
|
1731
|
+
}
|
|
1711
1732
|
var PiRpcSessionCatalog = class {
|
|
1712
1733
|
directory;
|
|
1713
1734
|
maxSessions;
|
|
1714
1735
|
removeFile;
|
|
1736
|
+
readSummary;
|
|
1737
|
+
/**
|
|
1738
|
+
* Summaries survive between calls, keyed by path and validated by size + mtime.
|
|
1739
|
+
* The list is asked for far more often than sessions change — every reconnect,
|
|
1740
|
+
* every new session, every rename — and re-reading 50 transcripts each time was
|
|
1741
|
+
* the single most expensive thing the host did.
|
|
1742
|
+
*/
|
|
1743
|
+
cache = /* @__PURE__ */ new Map();
|
|
1715
1744
|
constructor(options = {}) {
|
|
1716
1745
|
this.directory = resolveSessionDir(options.sessionDir, options.env);
|
|
1717
1746
|
this.maxSessions = options.maxSessions ?? 50;
|
|
1718
1747
|
this.removeFile = options.removeFile ?? unlink;
|
|
1748
|
+
this.readSummary = options.readSummary ?? readSessionSummary;
|
|
1719
1749
|
}
|
|
1720
1750
|
get sessionDirectory() {
|
|
1721
1751
|
return this.directory;
|
|
1722
1752
|
}
|
|
1753
|
+
/**
|
|
1754
|
+
* A list already being built is handed to every caller instead of being started
|
|
1755
|
+
* again: two frontends connecting at the same moment, or a refresh landing while
|
|
1756
|
+
* a push is in flight, used to mean two full scans of the whole catalog.
|
|
1757
|
+
*/
|
|
1758
|
+
inFlight;
|
|
1723
1759
|
async list() {
|
|
1760
|
+
this.inFlight ??= this.buildList().finally(() => {
|
|
1761
|
+
this.inFlight = void 0;
|
|
1762
|
+
});
|
|
1763
|
+
return this.inFlight;
|
|
1764
|
+
}
|
|
1765
|
+
async buildList() {
|
|
1724
1766
|
const files = await this.collectSessionFiles();
|
|
1725
1767
|
const summaries = [];
|
|
1768
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1726
1769
|
for (const file of files.slice(0, this.maxSessions)) {
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1770
|
+
seen.add(file.path);
|
|
1771
|
+
const cached = this.cache.get(file.path);
|
|
1772
|
+
if (cached !== void 0 && cached.size === file.size && cached.updatedAt === file.updatedAt) {
|
|
1773
|
+
summaries.push(cached.summary);
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
const scan = await this.readSummary(file.path, file.updatedAt, cached);
|
|
1777
|
+
if (scan === void 0) {
|
|
1778
|
+
this.cache.delete(file.path);
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
this.cache.set(file.path, { size: file.size, updatedAt: file.updatedAt, ...scan });
|
|
1782
|
+
summaries.push(scan.summary);
|
|
1783
|
+
}
|
|
1784
|
+
for (const path of [...this.cache.keys()]) {
|
|
1785
|
+
if (!seen.has(path)) {
|
|
1786
|
+
this.cache.delete(path);
|
|
1730
1787
|
}
|
|
1731
1788
|
}
|
|
1732
1789
|
return summaries;
|
|
@@ -1759,61 +1816,108 @@ var PiRpcSessionCatalog = class {
|
|
|
1759
1816
|
}
|
|
1760
1817
|
const path = join2(bucketPath, entry.name);
|
|
1761
1818
|
const info = await stat(path).catch(() => void 0);
|
|
1762
|
-
files.push({ path, updatedAt: info?.mtimeMs ?? 0 });
|
|
1819
|
+
files.push({ path, size: info?.size ?? 0, updatedAt: info?.mtimeMs ?? 0 });
|
|
1763
1820
|
}
|
|
1764
1821
|
}
|
|
1765
1822
|
files.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
1766
1823
|
return files;
|
|
1767
1824
|
}
|
|
1768
1825
|
};
|
|
1769
|
-
async function readSessionSummary(path, updatedAt) {
|
|
1770
|
-
|
|
1826
|
+
async function readSessionSummary(path, updatedAt, previous) {
|
|
1827
|
+
let resumable = previous !== void 0 && previous.scanned > 0 && updatedAt >= previous.updatedAt;
|
|
1828
|
+
if (resumable && previous !== void 0) {
|
|
1829
|
+
const info = await stat(path).catch(() => void 0);
|
|
1830
|
+
resumable = info !== void 0 && info.size > previous.scanned;
|
|
1831
|
+
}
|
|
1832
|
+
const from = resumable && previous !== void 0 ? previous.scanned : 0;
|
|
1833
|
+
const raw = resumable ? await readFrom(path, from).catch(() => void 0) : await readFile(path, "utf8").catch(() => void 0);
|
|
1771
1834
|
if (raw === void 0) {
|
|
1772
1835
|
return void 0;
|
|
1773
1836
|
}
|
|
1774
|
-
let
|
|
1775
|
-
let
|
|
1776
|
-
let
|
|
1777
|
-
let
|
|
1778
|
-
let
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
if (message?.["role"] === "user") {
|
|
1800
|
-
firstUserText = textOf(message["content"]);
|
|
1837
|
+
let cwd = resumable && previous !== void 0 ? previous.summary.cwd : "";
|
|
1838
|
+
let name = resumable && previous !== void 0 ? previous.name : void 0;
|
|
1839
|
+
let firstUserText = resumable && previous !== void 0 ? previous.firstUserText : void 0;
|
|
1840
|
+
let messageCount = resumable && previous !== void 0 ? previous.summary.messageCount : 0;
|
|
1841
|
+
let scanned = from;
|
|
1842
|
+
let consumed = 0;
|
|
1843
|
+
const parseAt = (start, end) => parseLine(raw.slice(start, end));
|
|
1844
|
+
for (let start = 0; start < raw.length; ) {
|
|
1845
|
+
const newline = raw.indexOf("\n", start);
|
|
1846
|
+
const end = newline === -1 ? raw.length : newline;
|
|
1847
|
+
const complete = newline !== -1;
|
|
1848
|
+
let type;
|
|
1849
|
+
if (raw.charCodeAt(start) === 123) {
|
|
1850
|
+
type = complete ? typeAt(raw, start, end) ?? asString(parseAt(start, end)?.["type"]) : asString(parseAt(start, end)?.["type"]);
|
|
1851
|
+
if (type === "message") {
|
|
1852
|
+
messageCount += 1;
|
|
1853
|
+
if (firstUserText === void 0) {
|
|
1854
|
+
const message = asRecord(parseAt(start, end)?.["message"]);
|
|
1855
|
+
if (message?.["role"] === "user") {
|
|
1856
|
+
firstUserText = textOf(message["content"]);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
} else if (type === "session") {
|
|
1860
|
+
if (cwd.length === 0) {
|
|
1861
|
+
cwd = asString(parseAt(start, end)?.["cwd"]) ?? "";
|
|
1801
1862
|
}
|
|
1863
|
+
} else if ((type === "session_info" || type === "session_name") && !name) {
|
|
1864
|
+
name = asString(parseAt(start, end)?.["name"]);
|
|
1802
1865
|
}
|
|
1803
|
-
continue;
|
|
1804
1866
|
}
|
|
1805
|
-
if (
|
|
1806
|
-
|
|
1867
|
+
if (!complete) {
|
|
1868
|
+
if (type !== void 0) {
|
|
1869
|
+
consumed = raw.length;
|
|
1870
|
+
}
|
|
1871
|
+
break;
|
|
1807
1872
|
}
|
|
1873
|
+
consumed = newline + 1;
|
|
1874
|
+
start = newline + 1;
|
|
1875
|
+
}
|
|
1876
|
+
if (consumed > 0) {
|
|
1877
|
+
scanned = from + Buffer.byteLength(raw.slice(0, consumed));
|
|
1808
1878
|
}
|
|
1809
1879
|
return {
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1880
|
+
summary: {
|
|
1881
|
+
id: path,
|
|
1882
|
+
title: name ?? titleFromText(firstUserText) ?? "Untitled session",
|
|
1883
|
+
cwd,
|
|
1884
|
+
updatedAt,
|
|
1885
|
+
messageCount
|
|
1886
|
+
},
|
|
1887
|
+
scanned,
|
|
1888
|
+
name,
|
|
1889
|
+
firstUserText
|
|
1815
1890
|
};
|
|
1816
1891
|
}
|
|
1892
|
+
async function readFrom(path, from) {
|
|
1893
|
+
const handle = await open(path, "r");
|
|
1894
|
+
try {
|
|
1895
|
+
const info = await handle.stat();
|
|
1896
|
+
if (info.size <= from) {
|
|
1897
|
+
return "";
|
|
1898
|
+
}
|
|
1899
|
+
const length = info.size - from;
|
|
1900
|
+
const buffer = Buffer.alloc(length);
|
|
1901
|
+
let filled = 0;
|
|
1902
|
+
while (filled < length) {
|
|
1903
|
+
const { bytesRead } = await handle.read(buffer, filled, length - filled, from + filled);
|
|
1904
|
+
if (bytesRead === 0) {
|
|
1905
|
+
break;
|
|
1906
|
+
}
|
|
1907
|
+
filled += bytesRead;
|
|
1908
|
+
}
|
|
1909
|
+
return buffer.subarray(0, filled).toString("utf8");
|
|
1910
|
+
} finally {
|
|
1911
|
+
await handle.close();
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
function parseLine(line) {
|
|
1915
|
+
try {
|
|
1916
|
+
return JSON.parse(line);
|
|
1917
|
+
} catch {
|
|
1918
|
+
return void 0;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1817
1921
|
function textOf(content) {
|
|
1818
1922
|
const direct = asString(content);
|
|
1819
1923
|
if (direct !== void 0) {
|
|
@@ -2204,7 +2308,9 @@ var SessionTranscriptStore = class {
|
|
|
2204
2308
|
this.emit({
|
|
2205
2309
|
sessionKey,
|
|
2206
2310
|
message,
|
|
2207
|
-
items
|
|
2311
|
+
get items() {
|
|
2312
|
+
return projector?.snapshot() ?? [];
|
|
2313
|
+
}
|
|
2208
2314
|
});
|
|
2209
2315
|
}
|
|
2210
2316
|
});
|
|
@@ -2232,6 +2338,7 @@ function emptySessionViewState(workspace, meta) {
|
|
|
2232
2338
|
agentReady: meta.agentReady,
|
|
2233
2339
|
agentStarting: meta.agentStarting,
|
|
2234
2340
|
agentError: meta.agentError,
|
|
2341
|
+
agentFailure: meta.agentFailure,
|
|
2235
2342
|
hasOlderHistory: meta.hasOlderHistory,
|
|
2236
2343
|
loadingOlderHistory: meta.loadingOlderHistory
|
|
2237
2344
|
};
|
|
@@ -2270,6 +2377,7 @@ function toSessionViewState(state, meta) {
|
|
|
2270
2377
|
agentReady: meta.agentReady,
|
|
2271
2378
|
agentStarting: meta.agentStarting,
|
|
2272
2379
|
agentError: meta.agentError,
|
|
2380
|
+
agentFailure: meta.agentFailure,
|
|
2273
2381
|
hasOlderHistory: meta.hasOlderHistory,
|
|
2274
2382
|
loadingOlderHistory: meta.loadingOlderHistory
|
|
2275
2383
|
};
|
|
@@ -2362,6 +2470,8 @@ var HostSessionController = class {
|
|
|
2362
2470
|
agentReady = false;
|
|
2363
2471
|
agentStarting = false;
|
|
2364
2472
|
agentError;
|
|
2473
|
+
/** The machine-readable half of `agentError` (see `AgentFailure`). */
|
|
2474
|
+
agentFailure;
|
|
2365
2475
|
busy = false;
|
|
2366
2476
|
disposed = false;
|
|
2367
2477
|
hasOpenedOnce = false;
|
|
@@ -2537,7 +2647,16 @@ var HostSessionController = class {
|
|
|
2537
2647
|
this.loadingOlderHistory = false;
|
|
2538
2648
|
this.agentReady = false;
|
|
2539
2649
|
this.agentStarting = false;
|
|
2650
|
+
this.clearAgentFailure();
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Forgets the agent failure, message and all. Called wherever the agent comes
|
|
2654
|
+
* back (a ready event, an adopted session), so a stale screen never outlives
|
|
2655
|
+
* the problem it described.
|
|
2656
|
+
*/
|
|
2657
|
+
clearAgentFailure() {
|
|
2540
2658
|
this.agentError = void 0;
|
|
2659
|
+
this.agentFailure = void 0;
|
|
2541
2660
|
}
|
|
2542
2661
|
/** Fills the draft pickers from the registry's session-less probe. */
|
|
2543
2662
|
async warmDraft() {
|
|
@@ -2597,7 +2716,7 @@ var HostSessionController = class {
|
|
|
2597
2716
|
this.hasOlderHistory = false;
|
|
2598
2717
|
this.loadingOlderHistory = false;
|
|
2599
2718
|
this.agentReady = false;
|
|
2600
|
-
this.
|
|
2719
|
+
this.clearAgentFailure();
|
|
2601
2720
|
const next = this.options.services.registry.hotKeys().at(-1);
|
|
2602
2721
|
if (next) {
|
|
2603
2722
|
await this.runSessionChange(() => this.options.services.registry.activate(next));
|
|
@@ -2622,6 +2741,7 @@ var HostSessionController = class {
|
|
|
2622
2741
|
} catch (error) {
|
|
2623
2742
|
this.agentReady = false;
|
|
2624
2743
|
this.agentError = describeError(error);
|
|
2744
|
+
this.agentFailure = describeAgentFailure(error, this.options.agentHint);
|
|
2625
2745
|
this.options.logger.warn("Could not start the Morse agent", error);
|
|
2626
2746
|
this.fail(`Could not start the Morse agent: ${this.agentError}`, this.options.agentHint);
|
|
2627
2747
|
} finally {
|
|
@@ -2645,7 +2765,7 @@ var HostSessionController = class {
|
|
|
2645
2765
|
this.hasOlderHistory = false;
|
|
2646
2766
|
this.loadingOlderHistory = false;
|
|
2647
2767
|
this.agentReady = true;
|
|
2648
|
-
this.
|
|
2768
|
+
this.clearAgentFailure();
|
|
2649
2769
|
this.emitTranscript(opened.key);
|
|
2650
2770
|
this.emitState();
|
|
2651
2771
|
void this.publishSessions();
|
|
@@ -2932,7 +3052,7 @@ var HostSessionController = class {
|
|
|
2932
3052
|
switch (event.type) {
|
|
2933
3053
|
case "agent/ready":
|
|
2934
3054
|
this.agentReady = true;
|
|
2935
|
-
this.
|
|
3055
|
+
this.clearAgentFailure();
|
|
2936
3056
|
this.emitState();
|
|
2937
3057
|
return;
|
|
2938
3058
|
case "agent/state":
|
|
@@ -2948,6 +3068,7 @@ var HostSessionController = class {
|
|
|
2948
3068
|
case "agent/fatal":
|
|
2949
3069
|
this.agentReady = false;
|
|
2950
3070
|
this.agentError = event.message;
|
|
3071
|
+
this.agentFailure = hintOnly(this.options.agentHint);
|
|
2951
3072
|
this.options.transcripts.error(sessionKey, event.message, event.detail);
|
|
2952
3073
|
this.emitState();
|
|
2953
3074
|
return;
|
|
@@ -3098,6 +3219,7 @@ var HostSessionController = class {
|
|
|
3098
3219
|
agentReady: this.agentReady,
|
|
3099
3220
|
agentStarting: this.agentStarting,
|
|
3100
3221
|
agentError: this.agentError,
|
|
3222
|
+
agentFailure: this.agentFailure,
|
|
3101
3223
|
busy: this.busy,
|
|
3102
3224
|
hasOlderHistory: this.hasOlderHistory,
|
|
3103
3225
|
loadingOlderHistory: this.loadingOlderHistory
|
|
@@ -3209,6 +3331,25 @@ function describeError(error) {
|
|
|
3209
3331
|
}
|
|
3210
3332
|
return typeof error === "string" ? error : JSON.stringify(error);
|
|
3211
3333
|
}
|
|
3334
|
+
function describeAgentFailure(error, hint) {
|
|
3335
|
+
const code = agentErrorCode(error);
|
|
3336
|
+
const install = error instanceof AgentUnavailableError ? error.remedy?.install : void 0;
|
|
3337
|
+
const failure = {
|
|
3338
|
+
...code ? { code } : {},
|
|
3339
|
+
...install ? { install } : {},
|
|
3340
|
+
...hint ? { hint } : {}
|
|
3341
|
+
};
|
|
3342
|
+
return Object.keys(failure).length > 0 ? failure : void 0;
|
|
3343
|
+
}
|
|
3344
|
+
function agentErrorCode(error) {
|
|
3345
|
+
if (!(error instanceof MorseError)) {
|
|
3346
|
+
return void 0;
|
|
3347
|
+
}
|
|
3348
|
+
return error.code === "agent-unavailable" || error.code === "agent-protocol" ? error.code : void 0;
|
|
3349
|
+
}
|
|
3350
|
+
function hintOnly(hint) {
|
|
3351
|
+
return hint ? { hint } : void 0;
|
|
3352
|
+
}
|
|
3212
3353
|
|
|
3213
3354
|
// ../server/src/internal/http/health.controller.ts
|
|
3214
3355
|
import { Controller, Get } from "@nestjs/common";
|
|
@@ -3350,7 +3491,7 @@ import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
|
|
|
3350
3491
|
// ../server/src/internal/uploads/upload-store.ts
|
|
3351
3492
|
import { existsSync } from "node:fs";
|
|
3352
3493
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3353
|
-
import { basename
|
|
3494
|
+
import { basename, isAbsolute as isAbsolute3, join as join4, relative, sep as sep3 } from "node:path";
|
|
3354
3495
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
3355
3496
|
var DEFAULT_UPLOAD_DIR = ".morse/uploads";
|
|
3356
3497
|
var UploadError = class extends Error {
|
|
@@ -3374,11 +3515,11 @@ async function saveUpload(cwd, request, uploadDir = DEFAULT_UPLOAD_DIR) {
|
|
|
3374
3515
|
await mkdir(directory, { recursive: true });
|
|
3375
3516
|
const target = uniquePath(directory, name);
|
|
3376
3517
|
await writeFile(target, bytes);
|
|
3377
|
-
return { path: mentionPath(cwd, target), name:
|
|
3518
|
+
return { path: mentionPath(cwd, target), name: basename(target), bytes: bytes.length };
|
|
3378
3519
|
}
|
|
3379
3520
|
function safeFileName(raw) {
|
|
3380
3521
|
const value = typeof raw === "string" ? raw : "";
|
|
3381
|
-
const base =
|
|
3522
|
+
const base = basename(value.replace(/\\/g, "/")).trim();
|
|
3382
3523
|
const cleaned = base.replace(/[\u0000-\u001f<>:"|?*]/g, "_").replace(/^\.+/, "").trim();
|
|
3383
3524
|
if (cleaned.length === 0) {
|
|
3384
3525
|
throw new UploadError("That upload had no usable file name.");
|
|
@@ -3409,7 +3550,7 @@ function formatMb(bytes) {
|
|
|
3409
3550
|
// ../server/src/internal/workspace/directory-browser.ts
|
|
3410
3551
|
import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
|
3411
3552
|
import { homedir as homedir2 } from "node:os";
|
|
3412
|
-
import { basename as
|
|
3553
|
+
import { basename as basename2, dirname, isAbsolute as isAbsolute4, join as join5, resolve as resolve3 } from "node:path";
|
|
3413
3554
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
3414
3555
|
"node_modules",
|
|
3415
3556
|
"dist",
|
|
@@ -3466,7 +3607,7 @@ function shortcutRoots(options) {
|
|
|
3466
3607
|
continue;
|
|
3467
3608
|
}
|
|
3468
3609
|
seen.add(path);
|
|
3469
|
-
roots.push({ name:
|
|
3610
|
+
roots.push({ name: basename2(path) || path, path });
|
|
3470
3611
|
}
|
|
3471
3612
|
return roots;
|
|
3472
3613
|
}
|
|
@@ -3636,7 +3777,11 @@ var MorseSessionFactory = class {
|
|
|
3636
3777
|
// pi's fork re-parents the conversation, so a past prompt can be edited.
|
|
3637
3778
|
editMessage: true,
|
|
3638
3779
|
// The same fork can branch a new session and hand the prompt back instead.
|
|
3639
|
-
forkMessage: true
|
|
3780
|
+
forkMessage: true,
|
|
3781
|
+
// A page served by this host may ask the registry for the latest release, so
|
|
3782
|
+
// the sidebar can say when a newer Morse is out. A host without it stays
|
|
3783
|
+
// quiet; this one is on unless `MORSE_UPDATE_CHECK=0` says otherwise.
|
|
3784
|
+
updateCheck: this.config.updateCheck
|
|
3640
3785
|
};
|
|
3641
3786
|
}
|
|
3642
3787
|
createFor(emit) {
|
|
@@ -3652,6 +3797,8 @@ var MorseSessionFactory = class {
|
|
|
3652
3797
|
transcripts: this.transcripts,
|
|
3653
3798
|
policy: this.policy,
|
|
3654
3799
|
scope: { kind: "global" },
|
|
3800
|
+
// The bundle this host serves, so the panel can name its own version.
|
|
3801
|
+
...this.config.frontend ? { frontend: this.config.frontend } : {},
|
|
3655
3802
|
// The registry outlives this connection on purpose.
|
|
3656
3803
|
ownsRegistry: false,
|
|
3657
3804
|
// A page load must not create a session; the first prompt opens one.
|
|
@@ -3774,7 +3921,7 @@ MorseGateway = __decorateClass([
|
|
|
3774
3921
|
], MorseGateway);
|
|
3775
3922
|
|
|
3776
3923
|
// ../server/src/app/config.ts
|
|
3777
|
-
import { basename as
|
|
3924
|
+
import { basename as basename3, dirname as dirname2, isAbsolute as isAbsolute5, join as join7 } from "node:path";
|
|
3778
3925
|
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
3779
3926
|
import { fileURLToPath } from "node:url";
|
|
3780
3927
|
function loadConfig(env = process.env) {
|
|
@@ -3785,11 +3932,13 @@ function loadConfig(env = process.env) {
|
|
|
3785
3932
|
const monorepoUiDir = join7(moduleDir, "..", "..", "..", "ui-angular", "dist");
|
|
3786
3933
|
const defaultUiDir = existsSync2(join7(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
|
|
3787
3934
|
const defaultHost = "127.0.0.1";
|
|
3935
|
+
const uiDir = configuredUiDir ? isAbsolute5(configuredUiDir) ? configuredUiDir : join7(process.cwd(), configuredUiDir) : defaultUiDir;
|
|
3788
3936
|
return {
|
|
3789
3937
|
host: env.MORSE_HOST ?? defaultHost,
|
|
3790
3938
|
port: parsePositiveInt(env.MORSE_PORT, 4399),
|
|
3791
|
-
uiDir
|
|
3792
|
-
|
|
3939
|
+
uiDir,
|
|
3940
|
+
frontend: readFrontendIdentity(uiDir),
|
|
3941
|
+
workspace: { cwd: workspacePath, name: basename3(workspacePath) || workspacePath },
|
|
3793
3942
|
piPath: env.MORSE_PI_PATH,
|
|
3794
3943
|
nodeEntryPath: env.MORSE_PI_ENTRY,
|
|
3795
3944
|
sessionDir: env.MORSE_SESSION_DIR,
|
|
@@ -3797,9 +3946,19 @@ function loadConfig(env = process.env) {
|
|
|
3797
3946
|
requestTimeoutMs: parsePositiveInt(env.MORSE_REQUEST_TIMEOUT_MS, 3e4),
|
|
3798
3947
|
hotSessions: parsePositiveInt(env.MORSE_HOT_SESSIONS, 4),
|
|
3799
3948
|
projects: parseList(env.MORSE_PROJECTS),
|
|
3800
|
-
uploadDir: env.MORSE_UPLOAD_DIR?.trim() || DEFAULT_UPLOAD_DIR
|
|
3949
|
+
uploadDir: env.MORSE_UPLOAD_DIR?.trim() || DEFAULT_UPLOAD_DIR,
|
|
3950
|
+
updateCheck: !isOff(env.MORSE_UPDATE_CHECK)
|
|
3801
3951
|
};
|
|
3802
3952
|
}
|
|
3953
|
+
function readFrontendIdentity(uiDir) {
|
|
3954
|
+
try {
|
|
3955
|
+
return frontendIdentity(
|
|
3956
|
+
parseFrontendManifest(readFileSync(join7(uiDir, FRONTEND_MANIFEST_FILE), "utf8"))
|
|
3957
|
+
);
|
|
3958
|
+
} catch {
|
|
3959
|
+
return void 0;
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3803
3962
|
function parsePositiveInt(value, fallback) {
|
|
3804
3963
|
if (!value) {
|
|
3805
3964
|
return fallback;
|
|
@@ -3807,6 +3966,10 @@ function parsePositiveInt(value, fallback) {
|
|
|
3807
3966
|
const parsed = Number.parseInt(value, 10);
|
|
3808
3967
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
3809
3968
|
}
|
|
3969
|
+
function isOff(value) {
|
|
3970
|
+
const normalized = value?.trim().toLowerCase();
|
|
3971
|
+
return normalized === "0" || normalized === "false" || normalized === "off";
|
|
3972
|
+
}
|
|
3810
3973
|
function parseList(value) {
|
|
3811
3974
|
if (!value) {
|
|
3812
3975
|
return [];
|