@supanadit/morse-web 0.10.0 → 0.12.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/dist/server.mjs +348 -46
- package/dist/server.mjs.map +4 -4
- package/dist/ui/3rdpartylicenses.txt +116 -0
- package/dist/ui/chunk-BJlUk5wu.js +1 -0
- package/dist/ui/chunk-C_meY2xd.js +58 -0
- package/dist/ui/chunk-DxDqTq2a.js +9 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/main-33GJA27U.js +120 -0
- package/dist/ui/styles-EGDFBSSZ.css +1 -0
- package/dist/ui/webview.manifest.json +3 -3
- package/package.json +2 -1
- package/dist/ui/main-DHSGAX7S.js +0 -98
- package/dist/ui/styles-CNPQRZ4U.css +0 -1
package/dist/server.mjs
CHANGED
|
@@ -38,7 +38,11 @@ var CLIENT_MESSAGE_TYPES = [
|
|
|
38
38
|
"model/set",
|
|
39
39
|
"thinking/set",
|
|
40
40
|
"interaction/respond",
|
|
41
|
-
"host/command"
|
|
41
|
+
"host/command",
|
|
42
|
+
"terminal/open",
|
|
43
|
+
"terminal/input",
|
|
44
|
+
"terminal/resize",
|
|
45
|
+
"terminal/close"
|
|
42
46
|
];
|
|
43
47
|
function encodeWireMessage(message) {
|
|
44
48
|
return JSON.stringify(message);
|
|
@@ -75,7 +79,7 @@ function hasKnownType(value, allowed) {
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
// ../protocol/dist/version.js
|
|
78
|
-
var PROTOCOL_VERSION =
|
|
82
|
+
var PROTOCOL_VERSION = 20;
|
|
79
83
|
var FRONTEND_MANIFEST_FILE = "webview.manifest.json";
|
|
80
84
|
var DEFAULT_WS_PATH = "/ws";
|
|
81
85
|
|
|
@@ -1706,11 +1710,11 @@ function promptDescription(raw) {
|
|
|
1706
1710
|
}
|
|
1707
1711
|
body = raw.slice(frontmatter[0].length);
|
|
1708
1712
|
}
|
|
1709
|
-
const
|
|
1710
|
-
if (
|
|
1713
|
+
const firstLine = body.split("\n").find((line) => line.trim().length > 0);
|
|
1714
|
+
if (firstLine === void 0) {
|
|
1711
1715
|
return void 0;
|
|
1712
1716
|
}
|
|
1713
|
-
const trimmed =
|
|
1717
|
+
const trimmed = firstLine.trim();
|
|
1714
1718
|
return trimmed.length > 60 ? `${trimmed.slice(0, 60)}...` : trimmed;
|
|
1715
1719
|
}
|
|
1716
1720
|
function unquote(value) {
|
|
@@ -2667,6 +2671,14 @@ var HostSessionController = class {
|
|
|
2667
2671
|
loadingOlderHistory = false;
|
|
2668
2672
|
/** Discriminates history item ids across pages (see `historyItems`). */
|
|
2669
2673
|
historyPageSeq = 0;
|
|
2674
|
+
/** The shells this client has open, keyed by the terminal id it named. */
|
|
2675
|
+
terminals = /* @__PURE__ */ new Map();
|
|
2676
|
+
/**
|
|
2677
|
+
* Bumped whenever a terminal id is opened or closed. A shell's late `exit`
|
|
2678
|
+
* (a replaced one, a killed one) compares its generation and stays silent, so
|
|
2679
|
+
* it cannot delete or report over the shell now holding the id.
|
|
2680
|
+
*/
|
|
2681
|
+
terminalGenerations = /* @__PURE__ */ new Map();
|
|
2670
2682
|
constructor(options) {
|
|
2671
2683
|
this.options = options;
|
|
2672
2684
|
}
|
|
@@ -2709,6 +2721,11 @@ var HostSessionController = class {
|
|
|
2709
2721
|
return;
|
|
2710
2722
|
}
|
|
2711
2723
|
this.disposed = true;
|
|
2724
|
+
for (const process2 of this.terminals.values()) {
|
|
2725
|
+
process2.kill();
|
|
2726
|
+
}
|
|
2727
|
+
this.terminals.clear();
|
|
2728
|
+
this.terminalGenerations.clear();
|
|
2712
2729
|
this.detachRegistry?.();
|
|
2713
2730
|
this.detachRegistry = void 0;
|
|
2714
2731
|
this.detachTranscripts?.();
|
|
@@ -2796,10 +2813,83 @@ var HostSessionController = class {
|
|
|
2796
2813
|
case "host/command":
|
|
2797
2814
|
await this.runHostCommand(message.payload.command, message.payload.args, message.payload.requestId);
|
|
2798
2815
|
return;
|
|
2816
|
+
case "terminal/open":
|
|
2817
|
+
await this.openTerminal(message.payload);
|
|
2818
|
+
return;
|
|
2819
|
+
case "terminal/input":
|
|
2820
|
+
this.terminals.get(message.payload.terminalId)?.write(message.payload.data);
|
|
2821
|
+
return;
|
|
2822
|
+
case "terminal/resize":
|
|
2823
|
+
this.terminals.get(message.payload.terminalId)?.resize(message.payload.cols, message.payload.rows);
|
|
2824
|
+
return;
|
|
2825
|
+
case "terminal/close":
|
|
2826
|
+
this.closeTerminal(message.payload.terminalId);
|
|
2827
|
+
return;
|
|
2799
2828
|
default:
|
|
2800
2829
|
return;
|
|
2801
2830
|
}
|
|
2802
2831
|
}
|
|
2832
|
+
/**
|
|
2833
|
+
* Starts a shell for the bottom panel. The directory is the viewing session's
|
|
2834
|
+
* (or the draft's), never an arbitrary path the client names — the backend
|
|
2835
|
+
* still re-checks it against the host policy before spawning.
|
|
2836
|
+
*/
|
|
2837
|
+
async openTerminal(payload) {
|
|
2838
|
+
if (this.options.terminal === void 0) {
|
|
2839
|
+
this.emitTerminalExit(payload.terminalId, void 0, "This host has no terminal.");
|
|
2840
|
+
return;
|
|
2841
|
+
}
|
|
2842
|
+
this.closeTerminal(payload.terminalId);
|
|
2843
|
+
const generation = this.terminalGenerations.get(payload.terminalId) ?? 0;
|
|
2844
|
+
const cwd = payload.cwd ?? this.currentState().workspace.cwd;
|
|
2845
|
+
const cols = clampGeometry(payload.cols, 80);
|
|
2846
|
+
const rows = clampGeometry(payload.rows, 24);
|
|
2847
|
+
let process2;
|
|
2848
|
+
try {
|
|
2849
|
+
process2 = await this.options.terminal.open({ cwd, cols, rows }, {
|
|
2850
|
+
output: (data) => {
|
|
2851
|
+
if (this.terminalGenerations.get(payload.terminalId) === generation) {
|
|
2852
|
+
this.emitTerminalOutput(payload.terminalId, data);
|
|
2853
|
+
}
|
|
2854
|
+
},
|
|
2855
|
+
exit: (code, error) => {
|
|
2856
|
+
if (this.terminalGenerations.get(payload.terminalId) !== generation) {
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
this.terminals.delete(payload.terminalId);
|
|
2860
|
+
this.emitTerminalExit(payload.terminalId, code, error);
|
|
2861
|
+
}
|
|
2862
|
+
});
|
|
2863
|
+
} catch (error) {
|
|
2864
|
+
this.options.logger.warn("Failed to open a terminal", error);
|
|
2865
|
+
this.emitTerminalExit(payload.terminalId, void 0, describeTerminalError(error));
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
if (!this.disposed && this.terminalGenerations.get(payload.terminalId) === generation) {
|
|
2869
|
+
this.terminals.set(payload.terminalId, process2);
|
|
2870
|
+
} else {
|
|
2871
|
+
process2.kill();
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
closeTerminal(terminalId) {
|
|
2875
|
+
this.terminalGenerations.set(terminalId, (this.terminalGenerations.get(terminalId) ?? 0) + 1);
|
|
2876
|
+
const process2 = this.terminals.get(terminalId);
|
|
2877
|
+
if (process2 === void 0) {
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2880
|
+
this.terminals.delete(terminalId);
|
|
2881
|
+
process2.kill();
|
|
2882
|
+
}
|
|
2883
|
+
emitTerminalOutput(terminalId, data) {
|
|
2884
|
+
if (data.length > 0 && !this.disposed) {
|
|
2885
|
+
this.options.emit({ type: "terminal/output", payload: { terminalId, data } });
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
emitTerminalExit(terminalId, code, error) {
|
|
2889
|
+
if (!this.disposed) {
|
|
2890
|
+
this.options.emit({ type: "terminal/exit", payload: { terminalId, code, error } });
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2803
2893
|
async openDefaultSession() {
|
|
2804
2894
|
const existingKey = this.options.services.registry.activeKeyOf();
|
|
2805
2895
|
if (existingKey) {
|
|
@@ -3227,15 +3317,15 @@ var HostSessionController = class {
|
|
|
3227
3317
|
this.answerHostCommand(requestId, false, void 0, `This host does not support "${command}".`);
|
|
3228
3318
|
return;
|
|
3229
3319
|
}
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
}
|
|
3234
|
-
|
|
3235
|
-
this.answerHostCommand(requestId,
|
|
3320
|
+
let value;
|
|
3321
|
+
try {
|
|
3322
|
+
value = await handler(command, args, { cwd: this.currentState().workspace.cwd });
|
|
3323
|
+
} catch (error) {
|
|
3324
|
+
this.options.logger.error(`Morse command failed: ${describeError(error)}`, error);
|
|
3325
|
+
this.answerHostCommand(requestId, false, void 0, `"${command}" failed`);
|
|
3236
3326
|
return;
|
|
3237
3327
|
}
|
|
3238
|
-
this.answerHostCommand(requestId,
|
|
3328
|
+
this.answerHostCommand(requestId, true, value);
|
|
3239
3329
|
}
|
|
3240
3330
|
/** Replies to a host command the frontend asked a value for. */
|
|
3241
3331
|
answerHostCommand(requestId, ok, data, error) {
|
|
@@ -3543,6 +3633,15 @@ function describeError(error) {
|
|
|
3543
3633
|
}
|
|
3544
3634
|
return typeof error === "string" ? error : JSON.stringify(error);
|
|
3545
3635
|
}
|
|
3636
|
+
function clampGeometry(value, fallback) {
|
|
3637
|
+
if (value === void 0 || !Number.isFinite(value)) {
|
|
3638
|
+
return fallback;
|
|
3639
|
+
}
|
|
3640
|
+
return Math.max(1, Math.min(1e3, Math.floor(value)));
|
|
3641
|
+
}
|
|
3642
|
+
function describeTerminalError(error) {
|
|
3643
|
+
return error instanceof MorseError ? error.message : describeError(error);
|
|
3644
|
+
}
|
|
3546
3645
|
function describeAgentFailure(error, hint) {
|
|
3547
3646
|
const code = agentErrorCode(error);
|
|
3548
3647
|
const install = error instanceof AgentUnavailableError ? error.remedy?.install : void 0;
|
|
@@ -3667,8 +3766,75 @@ var ServerProjectPolicy = class {
|
|
|
3667
3766
|
}
|
|
3668
3767
|
};
|
|
3669
3768
|
|
|
3670
|
-
// ../server/src/internal/
|
|
3769
|
+
// ../server/src/internal/terminal/terminal.service.ts
|
|
3770
|
+
import { spawn as spawnPty } from "node-pty";
|
|
3671
3771
|
import { Inject as Inject2, Injectable } from "@nestjs/common";
|
|
3772
|
+
var SIGKILL_AFTER_MS = 1500;
|
|
3773
|
+
function defaultShell() {
|
|
3774
|
+
const configured = process.env.SHELL?.trim();
|
|
3775
|
+
if (configured !== void 0 && configured.length > 0) {
|
|
3776
|
+
return configured;
|
|
3777
|
+
}
|
|
3778
|
+
return process.platform === "win32" ? "powershell.exe" : "/bin/bash";
|
|
3779
|
+
}
|
|
3780
|
+
var ServerTerminalBackend = class {
|
|
3781
|
+
constructor(policy, logger) {
|
|
3782
|
+
this.policy = policy;
|
|
3783
|
+
this.logger = logger;
|
|
3784
|
+
}
|
|
3785
|
+
open(options, sink) {
|
|
3786
|
+
if (!this.policy.canOpen(options.cwd)) {
|
|
3787
|
+
throw new UnsupportedByHostError(
|
|
3788
|
+
`Morse is not allowed to run a terminal in ${options.cwd}.`
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
const shell = defaultShell();
|
|
3792
|
+
const pty = spawnPty(shell, [], {
|
|
3793
|
+
name: "xterm-256color",
|
|
3794
|
+
cols: options.cols,
|
|
3795
|
+
rows: options.rows,
|
|
3796
|
+
cwd: options.cwd,
|
|
3797
|
+
env: { ...process.env, TERM: "xterm-256color", MORSE_TERMINAL: "1" }
|
|
3798
|
+
});
|
|
3799
|
+
this.logger.info(`Terminal opened in ${options.cwd} (${shell}, pid ${pty.pid})`);
|
|
3800
|
+
let exited = false;
|
|
3801
|
+
const data = pty.onData((chunk) => sink.output(chunk));
|
|
3802
|
+
pty.onExit(({ exitCode }) => {
|
|
3803
|
+
exited = true;
|
|
3804
|
+
data.dispose();
|
|
3805
|
+
sink.exit(exitCode);
|
|
3806
|
+
});
|
|
3807
|
+
return {
|
|
3808
|
+
write: (input) => {
|
|
3809
|
+
if (!exited) {
|
|
3810
|
+
pty.write(input);
|
|
3811
|
+
}
|
|
3812
|
+
},
|
|
3813
|
+
resize: (cols, rows) => {
|
|
3814
|
+
if (!exited) {
|
|
3815
|
+
pty.resize(cols, rows);
|
|
3816
|
+
}
|
|
3817
|
+
},
|
|
3818
|
+
kill: () => {
|
|
3819
|
+
if (exited) {
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3822
|
+
exited = true;
|
|
3823
|
+
pty.kill();
|
|
3824
|
+
const timer = setTimeout(() => pty.kill("SIGKILL"), SIGKILL_AFTER_MS);
|
|
3825
|
+
timer.unref?.();
|
|
3826
|
+
}
|
|
3827
|
+
};
|
|
3828
|
+
}
|
|
3829
|
+
};
|
|
3830
|
+
ServerTerminalBackend = __decorateClass([
|
|
3831
|
+
Injectable(),
|
|
3832
|
+
__decorateParam(0, Inject2(MORSE_PROJECT_POLICY)),
|
|
3833
|
+
__decorateParam(1, Inject2(MORSE_LOGGER))
|
|
3834
|
+
], ServerTerminalBackend);
|
|
3835
|
+
|
|
3836
|
+
// ../server/src/internal/registry.ts
|
|
3837
|
+
import { Inject as Inject3, Injectable as Injectable2 } from "@nestjs/common";
|
|
3672
3838
|
function createSessionRegistry(adapter, config, logger) {
|
|
3673
3839
|
return new SessionRegistry({
|
|
3674
3840
|
factory: adapter.factory,
|
|
@@ -3689,19 +3855,19 @@ var MorseRegistryLifecycle = class {
|
|
|
3689
3855
|
}
|
|
3690
3856
|
};
|
|
3691
3857
|
MorseRegistryLifecycle = __decorateClass([
|
|
3692
|
-
|
|
3693
|
-
__decorateParam(0,
|
|
3694
|
-
__decorateParam(1,
|
|
3858
|
+
Injectable2(),
|
|
3859
|
+
__decorateParam(0, Inject3(MORSE_SESSION_REGISTRY)),
|
|
3860
|
+
__decorateParam(1, Inject3(MORSE_TRANSCRIPT_STORE))
|
|
3695
3861
|
], MorseRegistryLifecycle);
|
|
3696
3862
|
|
|
3697
3863
|
// ../server/src/internal/ws/morse.gateway.ts
|
|
3698
3864
|
import {
|
|
3699
3865
|
WebSocketGateway
|
|
3700
3866
|
} from "@nestjs/websockets";
|
|
3701
|
-
import { Inject as
|
|
3867
|
+
import { Inject as Inject5 } from "@nestjs/common";
|
|
3702
3868
|
|
|
3703
3869
|
// ../server/src/internal/ws/session-factory.service.ts
|
|
3704
|
-
import { Inject as
|
|
3870
|
+
import { Inject as Inject4, Injectable as Injectable3 } from "@nestjs/common";
|
|
3705
3871
|
|
|
3706
3872
|
// ../server/src/internal/uploads/upload-store.ts
|
|
3707
3873
|
import { existsSync } from "node:fs";
|
|
@@ -4087,7 +4253,10 @@ async function commitGit(cwd, message) {
|
|
|
4087
4253
|
return { ok: false, message: "This project is not a git repository." };
|
|
4088
4254
|
}
|
|
4089
4255
|
const result = await gitExec(["commit", "-m", text], cwd, 6e4);
|
|
4090
|
-
|
|
4256
|
+
if (result.ok) {
|
|
4257
|
+
return { ok: true };
|
|
4258
|
+
}
|
|
4259
|
+
return { ok: false, message: describeCommitFailure(result.output) };
|
|
4091
4260
|
}
|
|
4092
4261
|
async function checkoutGit(cwd, branch, create) {
|
|
4093
4262
|
const name = branch.trim();
|
|
@@ -4103,13 +4272,21 @@ async function checkoutGit(cwd, branch, create) {
|
|
|
4103
4272
|
cwd,
|
|
4104
4273
|
6e4
|
|
4105
4274
|
);
|
|
4106
|
-
return { ok: result.ok, message: result.ok ? void 0 :
|
|
4275
|
+
return { ok: result.ok, message: result.ok ? void 0 : gitRefusal(result.output) };
|
|
4107
4276
|
}
|
|
4108
4277
|
function isBranchName(name) {
|
|
4109
4278
|
return name.length > 0 && !name.startsWith("-") && !name.includes("..") && !name.includes("@{") && !name.endsWith("/") && !/\s/.test(name) && !/[~^:?*\[\\]/.test(name);
|
|
4110
4279
|
}
|
|
4111
|
-
function
|
|
4112
|
-
|
|
4280
|
+
function describeCommitFailure(output) {
|
|
4281
|
+
if (/nothing to commit|no changes added to commit|nothing added to commit/i.test(output)) {
|
|
4282
|
+
return "Nothing is staged to commit. Stage a change first.";
|
|
4283
|
+
}
|
|
4284
|
+
return gitRefusal(output);
|
|
4285
|
+
}
|
|
4286
|
+
function gitRefusal(output) {
|
|
4287
|
+
const line = output.split("\n").map((candidate) => candidate.trim()).find(
|
|
4288
|
+
(candidate) => candidate.length > 0 && !candidate.startsWith("Command failed:") && !/^On branch /.test(candidate) && !/^Your branch /.test(candidate)
|
|
4289
|
+
);
|
|
4113
4290
|
if (line === void 0) {
|
|
4114
4291
|
return void 0;
|
|
4115
4292
|
}
|
|
@@ -4193,9 +4370,12 @@ function gitExec(args, cwd, timeout = 5e3) {
|
|
|
4193
4370
|
}
|
|
4194
4371
|
},
|
|
4195
4372
|
(error, stdout, stderr) => {
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4373
|
+
if (error === null) {
|
|
4374
|
+
resolve6({ ok: true, output: stdout.trim() });
|
|
4375
|
+
return;
|
|
4376
|
+
}
|
|
4377
|
+
const output = [stderr.trim(), stdout.trim()].filter((part) => part.length > 0).join("\n");
|
|
4378
|
+
resolve6({ ok: false, output: output.length > 0 ? output : error.message.trim() });
|
|
4199
4379
|
}
|
|
4200
4380
|
);
|
|
4201
4381
|
});
|
|
@@ -4332,14 +4512,90 @@ function toPosix3(path) {
|
|
|
4332
4512
|
return path.split("\\").join("/");
|
|
4333
4513
|
}
|
|
4334
4514
|
|
|
4515
|
+
// ../server/src/internal/workspace/workbench-store.ts
|
|
4516
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4517
|
+
import { join as join8 } from "node:path";
|
|
4518
|
+
var LAYOUT_FILE = "workbench.json";
|
|
4519
|
+
var DRAFTS_FILE = "drafts.json";
|
|
4520
|
+
var LAYOUT_MAX_BYTES = 256 * 1024;
|
|
4521
|
+
var DRAFTS_MAX_BYTES = 32 * 1024 * 1024;
|
|
4522
|
+
function readWorkbench(dataDir) {
|
|
4523
|
+
return readSlot(join8(dataDir, LAYOUT_FILE), LAYOUT_MAX_BYTES);
|
|
4524
|
+
}
|
|
4525
|
+
function saveWorkbench(dataDir, snapshot) {
|
|
4526
|
+
return saveSlot(dataDir, LAYOUT_FILE, snapshot, LAYOUT_MAX_BYTES);
|
|
4527
|
+
}
|
|
4528
|
+
function readDrafts(dataDir) {
|
|
4529
|
+
return readSlot(join8(dataDir, DRAFTS_FILE), DRAFTS_MAX_BYTES);
|
|
4530
|
+
}
|
|
4531
|
+
function saveDrafts(dataDir, snapshot) {
|
|
4532
|
+
return saveSlot(dataDir, DRAFTS_FILE, snapshot, DRAFTS_MAX_BYTES);
|
|
4533
|
+
}
|
|
4534
|
+
function readSlot(path, maxBytes) {
|
|
4535
|
+
let raw;
|
|
4536
|
+
try {
|
|
4537
|
+
raw = readFileSync(path, "utf8");
|
|
4538
|
+
} catch {
|
|
4539
|
+
return void 0;
|
|
4540
|
+
}
|
|
4541
|
+
if (raw.length > maxBytes) {
|
|
4542
|
+
return void 0;
|
|
4543
|
+
}
|
|
4544
|
+
let parsed;
|
|
4545
|
+
try {
|
|
4546
|
+
parsed = JSON.parse(raw);
|
|
4547
|
+
} catch {
|
|
4548
|
+
return void 0;
|
|
4549
|
+
}
|
|
4550
|
+
return asSnapshot(parsed);
|
|
4551
|
+
}
|
|
4552
|
+
function saveSlot(dataDir, file, snapshot, maxBytes) {
|
|
4553
|
+
const valid = asSnapshot(snapshot);
|
|
4554
|
+
if (valid === void 0) {
|
|
4555
|
+
throw new UnsupportedByHostError(`Saving ${file} needs a { version, data } snapshot.`);
|
|
4556
|
+
}
|
|
4557
|
+
const serialized = JSON.stringify(valid);
|
|
4558
|
+
if (Buffer.byteLength(serialized, "utf8") > maxBytes) {
|
|
4559
|
+
throw new UnsupportedByHostError(`That ${file} snapshot is too large to save.`);
|
|
4560
|
+
}
|
|
4561
|
+
const target = join8(dataDir, file);
|
|
4562
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
4563
|
+
try {
|
|
4564
|
+
mkdirSync(dataDir, { recursive: true });
|
|
4565
|
+
writeFileSync(temp, serialized);
|
|
4566
|
+
renameSync(temp, target);
|
|
4567
|
+
return true;
|
|
4568
|
+
} catch {
|
|
4569
|
+
try {
|
|
4570
|
+
writeFileSync(target, serialized);
|
|
4571
|
+
return true;
|
|
4572
|
+
} catch {
|
|
4573
|
+
rmSync(temp, { force: true });
|
|
4574
|
+
return false;
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
function asSnapshot(value) {
|
|
4579
|
+
if (typeof value !== "object" || value === null) {
|
|
4580
|
+
return void 0;
|
|
4581
|
+
}
|
|
4582
|
+
const candidate = value;
|
|
4583
|
+
const version = candidate["version"];
|
|
4584
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version < 1) {
|
|
4585
|
+
return void 0;
|
|
4586
|
+
}
|
|
4587
|
+
return { version, data: candidate["data"] };
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4335
4590
|
// ../server/src/internal/ws/session-factory.service.ts
|
|
4336
4591
|
var MorseSessionFactory = class {
|
|
4337
|
-
constructor(registry, transcripts, policy, logger, config) {
|
|
4592
|
+
constructor(registry, transcripts, policy, logger, config, terminal) {
|
|
4338
4593
|
this.registry = registry;
|
|
4339
4594
|
this.transcripts = transcripts;
|
|
4340
4595
|
this.policy = policy;
|
|
4341
4596
|
this.logger = logger;
|
|
4342
4597
|
this.config = config;
|
|
4598
|
+
this.terminal = terminal;
|
|
4343
4599
|
}
|
|
4344
4600
|
capabilities() {
|
|
4345
4601
|
return {
|
|
@@ -4361,6 +4617,14 @@ var MorseSessionFactory = class {
|
|
|
4361
4617
|
// The same reasoning for history: VS Code has Source Control, so the
|
|
4362
4618
|
// browser host is the one that answers `gitLog` for its own git panel.
|
|
4363
4619
|
gitPanel: true,
|
|
4620
|
+
// VS Code already has an integrated terminal; the browser host has none, so
|
|
4621
|
+
// the bottom panel's terminal is this host's. It runs in the viewing
|
|
4622
|
+
// session's directory and is gated by `ProjectPolicy`, like a session.
|
|
4623
|
+
terminal: true,
|
|
4624
|
+
// The browser host restores the tabs, panel, terminals and half-written
|
|
4625
|
+
// prompts the reader left open, kept under `<MORSE_HOME>`. VS Code has its
|
|
4626
|
+
// own tab restoration and leaves this off.
|
|
4627
|
+
workbench: true,
|
|
4364
4628
|
// A browser cannot hand a dragged file's path to pi, so the host takes the
|
|
4365
4629
|
// bytes and writes them next to the session; the frontend then `@mentions`
|
|
4366
4630
|
// the path it gets back.
|
|
@@ -4399,6 +4663,8 @@ var MorseSessionFactory = class {
|
|
|
4399
4663
|
// A page load must not create a session; the first prompt opens one.
|
|
4400
4664
|
autoOpen: false,
|
|
4401
4665
|
onHostCommand: (command, args, context) => this.runHostCommand(command, args, context),
|
|
4666
|
+
// The bottom panel's shell: this host can spawn one on its own machine.
|
|
4667
|
+
terminal: this.terminal,
|
|
4402
4668
|
agentHint: "Install the pi CLI and make sure it is on PATH, or start the server with MORSE_PI_PATH set."
|
|
4403
4669
|
});
|
|
4404
4670
|
}
|
|
@@ -4422,13 +4688,25 @@ var MorseSessionFactory = class {
|
|
|
4422
4688
|
});
|
|
4423
4689
|
return { ...listing, canOpen: this.policy.canOpen(listing.path) };
|
|
4424
4690
|
}
|
|
4691
|
+
case "readWorkbench": {
|
|
4692
|
+
return readWorkbench(this.config.dataDir) ?? null;
|
|
4693
|
+
}
|
|
4694
|
+
case "saveWorkbench": {
|
|
4695
|
+
return { ok: saveWorkbench(this.config.dataDir, args) };
|
|
4696
|
+
}
|
|
4697
|
+
case "readDrafts": {
|
|
4698
|
+
return readDrafts(this.config.dataDir) ?? null;
|
|
4699
|
+
}
|
|
4700
|
+
case "saveDrafts": {
|
|
4701
|
+
return { ok: saveDrafts(this.config.dataDir, args) };
|
|
4702
|
+
}
|
|
4425
4703
|
case "listFiles": {
|
|
4426
4704
|
const cwd = this.requireWritableCwd(context);
|
|
4427
4705
|
const files = await workspaceFiles(cwd, this.logger, { fresh: args?.fresh === true });
|
|
4428
4706
|
return { files };
|
|
4429
4707
|
}
|
|
4430
4708
|
case "readFile": {
|
|
4431
|
-
const cwd = this.
|
|
4709
|
+
const cwd = this.commandCwd(context, args);
|
|
4432
4710
|
const path = typeof args?.path === "string" ? args.path : "";
|
|
4433
4711
|
if (path.length === 0) {
|
|
4434
4712
|
throw new UnsupportedByHostError('readFile needs a "path" argument.');
|
|
@@ -4452,7 +4730,7 @@ var MorseSessionFactory = class {
|
|
|
4452
4730
|
return readGitStatus(cwd);
|
|
4453
4731
|
}
|
|
4454
4732
|
case "gitDiff": {
|
|
4455
|
-
const cwd = this.
|
|
4733
|
+
const cwd = this.commandCwd(context, args);
|
|
4456
4734
|
const path = typeof args?.path === "string" ? args.path : "";
|
|
4457
4735
|
if (path.length === 0) {
|
|
4458
4736
|
throw new UnsupportedByHostError('gitDiff needs a "path" argument.');
|
|
@@ -4473,7 +4751,7 @@ var MorseSessionFactory = class {
|
|
|
4473
4751
|
return readCommitFiles(cwd, hash);
|
|
4474
4752
|
}
|
|
4475
4753
|
case "gitCommitDiff": {
|
|
4476
|
-
const cwd = this.
|
|
4754
|
+
const cwd = this.commandCwd(context, args);
|
|
4477
4755
|
const hash = typeof args?.hash === "string" ? args.hash : "";
|
|
4478
4756
|
const path = typeof args?.path === "string" ? args.path : "";
|
|
4479
4757
|
if (path.length === 0) {
|
|
@@ -4526,6 +4804,22 @@ var MorseSessionFactory = class {
|
|
|
4526
4804
|
}
|
|
4527
4805
|
return cwd;
|
|
4528
4806
|
}
|
|
4807
|
+
/**
|
|
4808
|
+
* The directory a file command works in. A restored preview tab carries the
|
|
4809
|
+
* project it was read from (`args.cwd`), because the host may still be on
|
|
4810
|
+
* another session when that tab is loaded; the policy checks it exactly like a
|
|
4811
|
+
* terminal's directory. Without one, the viewing session's directory is used.
|
|
4812
|
+
*/
|
|
4813
|
+
commandCwd(context, args) {
|
|
4814
|
+
const requested = args?.cwd;
|
|
4815
|
+
if (typeof requested === "string" && requested.length > 0) {
|
|
4816
|
+
if (!this.policy.canOpen(requested)) {
|
|
4817
|
+
throw new UnsupportedByHostError(`Morse is not allowed to work in ${requested}.`);
|
|
4818
|
+
}
|
|
4819
|
+
return requested;
|
|
4820
|
+
}
|
|
4821
|
+
return this.requireWritableCwd(context);
|
|
4822
|
+
}
|
|
4529
4823
|
/** The cwd of the session this connection is showing. */
|
|
4530
4824
|
activeCwd() {
|
|
4531
4825
|
const key = this.registry.activeKeyOf();
|
|
@@ -4534,12 +4828,13 @@ var MorseSessionFactory = class {
|
|
|
4534
4828
|
}
|
|
4535
4829
|
};
|
|
4536
4830
|
MorseSessionFactory = __decorateClass([
|
|
4537
|
-
|
|
4538
|
-
__decorateParam(0,
|
|
4539
|
-
__decorateParam(1,
|
|
4540
|
-
__decorateParam(2,
|
|
4541
|
-
__decorateParam(3,
|
|
4542
|
-
__decorateParam(4,
|
|
4831
|
+
Injectable3(),
|
|
4832
|
+
__decorateParam(0, Inject4(MORSE_SESSION_REGISTRY)),
|
|
4833
|
+
__decorateParam(1, Inject4(MORSE_TRANSCRIPT_STORE)),
|
|
4834
|
+
__decorateParam(2, Inject4(MORSE_PROJECT_POLICY)),
|
|
4835
|
+
__decorateParam(3, Inject4(MORSE_LOGGER)),
|
|
4836
|
+
__decorateParam(4, Inject4(MORSE_CONFIG)),
|
|
4837
|
+
__decorateParam(5, Inject4(ServerTerminalBackend))
|
|
4543
4838
|
], MorseSessionFactory);
|
|
4544
4839
|
function gitPaths(args) {
|
|
4545
4840
|
const paths = args?.paths;
|
|
@@ -4592,23 +4887,24 @@ var MorseGateway = class {
|
|
|
4592
4887
|
};
|
|
4593
4888
|
MorseGateway = __decorateClass([
|
|
4594
4889
|
WebSocketGateway({ path: DEFAULT_WS_PATH }),
|
|
4595
|
-
__decorateParam(0,
|
|
4596
|
-
__decorateParam(1,
|
|
4890
|
+
__decorateParam(0, Inject5(MorseSessionFactory)),
|
|
4891
|
+
__decorateParam(1, Inject5(MORSE_LOGGER))
|
|
4597
4892
|
], MorseGateway);
|
|
4598
4893
|
|
|
4599
4894
|
// ../server/src/app/config.ts
|
|
4600
|
-
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute6, join as
|
|
4601
|
-
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
4895
|
+
import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute6, join as join9 } from "node:path";
|
|
4896
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
4897
|
+
import { homedir as homedir4 } from "node:os";
|
|
4602
4898
|
import { fileURLToPath } from "node:url";
|
|
4603
4899
|
function loadConfig(env = process.env) {
|
|
4604
4900
|
const workspacePath = resolveWorkspacePath(env);
|
|
4605
4901
|
const configuredUiDir = env.MORSE_UI_DIR;
|
|
4606
4902
|
const moduleDir = dirname2(fileURLToPath(import.meta.url));
|
|
4607
|
-
const bundledUiDir =
|
|
4608
|
-
const monorepoUiDir =
|
|
4609
|
-
const defaultUiDir = existsSync2(
|
|
4903
|
+
const bundledUiDir = join9(moduleDir, "ui");
|
|
4904
|
+
const monorepoUiDir = join9(moduleDir, "..", "..", "..", "ui-angular", "dist");
|
|
4905
|
+
const defaultUiDir = existsSync2(join9(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
|
|
4610
4906
|
const defaultHost = "127.0.0.1";
|
|
4611
|
-
const uiDir = configuredUiDir ? isAbsolute6(configuredUiDir) ? configuredUiDir :
|
|
4907
|
+
const uiDir = configuredUiDir ? isAbsolute6(configuredUiDir) ? configuredUiDir : join9(process.cwd(), configuredUiDir) : defaultUiDir;
|
|
4612
4908
|
return {
|
|
4613
4909
|
host: env.MORSE_HOST ?? defaultHost,
|
|
4614
4910
|
port: parsePositiveInt(env.MORSE_PORT, 4399),
|
|
@@ -4623,6 +4919,7 @@ function loadConfig(env = process.env) {
|
|
|
4623
4919
|
hotSessions: parsePositiveInt(env.MORSE_HOT_SESSIONS, 4),
|
|
4624
4920
|
projects: parseList(env.MORSE_PROJECTS),
|
|
4625
4921
|
uploadDir: env.MORSE_UPLOAD_DIR?.trim() || DEFAULT_UPLOAD_DIR,
|
|
4922
|
+
dataDir: resolveDataDir(env),
|
|
4626
4923
|
updateCheck: !isOff(env.MORSE_UPDATE_CHECK),
|
|
4627
4924
|
instance: env.MORSE_INSTANCE?.trim() || void 0
|
|
4628
4925
|
};
|
|
@@ -4630,12 +4927,16 @@ function loadConfig(env = process.env) {
|
|
|
4630
4927
|
function readFrontendIdentity(uiDir) {
|
|
4631
4928
|
try {
|
|
4632
4929
|
return frontendIdentity(
|
|
4633
|
-
parseFrontendManifest(
|
|
4930
|
+
parseFrontendManifest(readFileSync2(join9(uiDir, FRONTEND_MANIFEST_FILE), "utf8"))
|
|
4634
4931
|
);
|
|
4635
4932
|
} catch {
|
|
4636
4933
|
return void 0;
|
|
4637
4934
|
}
|
|
4638
4935
|
}
|
|
4936
|
+
function resolveDataDir(env) {
|
|
4937
|
+
const explicit = env.MORSE_HOME?.trim();
|
|
4938
|
+
return explicit && explicit.length > 0 ? explicit : join9(homedir4(), ".morse");
|
|
4939
|
+
}
|
|
4639
4940
|
function parsePositiveInt(value, fallback) {
|
|
4640
4941
|
if (!value) {
|
|
4641
4942
|
return fallback;
|
|
@@ -4661,7 +4962,7 @@ function resolveWorkspacePath(env) {
|
|
|
4661
4962
|
const start = process.cwd();
|
|
4662
4963
|
let directory = start;
|
|
4663
4964
|
for (; ; ) {
|
|
4664
|
-
const manifest =
|
|
4965
|
+
const manifest = join9(directory, "package.json");
|
|
4665
4966
|
if (existsSync2(manifest) && declaresWorkspaces(manifest)) {
|
|
4666
4967
|
return directory;
|
|
4667
4968
|
}
|
|
@@ -4674,7 +4975,7 @@ function resolveWorkspacePath(env) {
|
|
|
4674
4975
|
}
|
|
4675
4976
|
function declaresWorkspaces(manifestPath) {
|
|
4676
4977
|
try {
|
|
4677
|
-
const manifest = JSON.parse(
|
|
4978
|
+
const manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
|
|
4678
4979
|
return manifest.workspaces !== void 0;
|
|
4679
4980
|
} catch {
|
|
4680
4981
|
return false;
|
|
@@ -4725,6 +5026,7 @@ AppModule = __decorateClass([
|
|
|
4725
5026
|
inject: [MORSE_CONFIG]
|
|
4726
5027
|
},
|
|
4727
5028
|
MorseRegistryLifecycle,
|
|
5029
|
+
ServerTerminalBackend,
|
|
4728
5030
|
MorseSessionFactory,
|
|
4729
5031
|
MorseGateway
|
|
4730
5032
|
]
|