@supanadit/morse-web 0.10.0 → 0.11.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 +199 -24
- package/dist/server.mjs.map +4 -4
- package/dist/ui/3rdpartylicenses.txt +116 -0
- package/dist/ui/chunk-C9z992Mq.js +9 -0
- package/dist/ui/chunk-DA4X1qo9.js +1 -0
- package/dist/ui/chunk-DD8oHaM3.js +58 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/main-5C6SRPAD.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
|
|
|
@@ -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";
|
|
@@ -4334,12 +4500,13 @@ function toPosix3(path) {
|
|
|
4334
4500
|
|
|
4335
4501
|
// ../server/src/internal/ws/session-factory.service.ts
|
|
4336
4502
|
var MorseSessionFactory = class {
|
|
4337
|
-
constructor(registry, transcripts, policy, logger, config) {
|
|
4503
|
+
constructor(registry, transcripts, policy, logger, config, terminal) {
|
|
4338
4504
|
this.registry = registry;
|
|
4339
4505
|
this.transcripts = transcripts;
|
|
4340
4506
|
this.policy = policy;
|
|
4341
4507
|
this.logger = logger;
|
|
4342
4508
|
this.config = config;
|
|
4509
|
+
this.terminal = terminal;
|
|
4343
4510
|
}
|
|
4344
4511
|
capabilities() {
|
|
4345
4512
|
return {
|
|
@@ -4361,6 +4528,10 @@ var MorseSessionFactory = class {
|
|
|
4361
4528
|
// The same reasoning for history: VS Code has Source Control, so the
|
|
4362
4529
|
// browser host is the one that answers `gitLog` for its own git panel.
|
|
4363
4530
|
gitPanel: true,
|
|
4531
|
+
// VS Code already has an integrated terminal; the browser host has none, so
|
|
4532
|
+
// the bottom panel's terminal is this host's. It runs in the viewing
|
|
4533
|
+
// session's directory and is gated by `ProjectPolicy`, like a session.
|
|
4534
|
+
terminal: true,
|
|
4364
4535
|
// A browser cannot hand a dragged file's path to pi, so the host takes the
|
|
4365
4536
|
// bytes and writes them next to the session; the frontend then `@mentions`
|
|
4366
4537
|
// the path it gets back.
|
|
@@ -4399,6 +4570,8 @@ var MorseSessionFactory = class {
|
|
|
4399
4570
|
// A page load must not create a session; the first prompt opens one.
|
|
4400
4571
|
autoOpen: false,
|
|
4401
4572
|
onHostCommand: (command, args, context) => this.runHostCommand(command, args, context),
|
|
4573
|
+
// The bottom panel's shell: this host can spawn one on its own machine.
|
|
4574
|
+
terminal: this.terminal,
|
|
4402
4575
|
agentHint: "Install the pi CLI and make sure it is on PATH, or start the server with MORSE_PI_PATH set."
|
|
4403
4576
|
});
|
|
4404
4577
|
}
|
|
@@ -4534,12 +4707,13 @@ var MorseSessionFactory = class {
|
|
|
4534
4707
|
}
|
|
4535
4708
|
};
|
|
4536
4709
|
MorseSessionFactory = __decorateClass([
|
|
4537
|
-
|
|
4538
|
-
__decorateParam(0,
|
|
4539
|
-
__decorateParam(1,
|
|
4540
|
-
__decorateParam(2,
|
|
4541
|
-
__decorateParam(3,
|
|
4542
|
-
__decorateParam(4,
|
|
4710
|
+
Injectable3(),
|
|
4711
|
+
__decorateParam(0, Inject4(MORSE_SESSION_REGISTRY)),
|
|
4712
|
+
__decorateParam(1, Inject4(MORSE_TRANSCRIPT_STORE)),
|
|
4713
|
+
__decorateParam(2, Inject4(MORSE_PROJECT_POLICY)),
|
|
4714
|
+
__decorateParam(3, Inject4(MORSE_LOGGER)),
|
|
4715
|
+
__decorateParam(4, Inject4(MORSE_CONFIG)),
|
|
4716
|
+
__decorateParam(5, Inject4(ServerTerminalBackend))
|
|
4543
4717
|
], MorseSessionFactory);
|
|
4544
4718
|
function gitPaths(args) {
|
|
4545
4719
|
const paths = args?.paths;
|
|
@@ -4592,8 +4766,8 @@ var MorseGateway = class {
|
|
|
4592
4766
|
};
|
|
4593
4767
|
MorseGateway = __decorateClass([
|
|
4594
4768
|
WebSocketGateway({ path: DEFAULT_WS_PATH }),
|
|
4595
|
-
__decorateParam(0,
|
|
4596
|
-
__decorateParam(1,
|
|
4769
|
+
__decorateParam(0, Inject5(MorseSessionFactory)),
|
|
4770
|
+
__decorateParam(1, Inject5(MORSE_LOGGER))
|
|
4597
4771
|
], MorseGateway);
|
|
4598
4772
|
|
|
4599
4773
|
// ../server/src/app/config.ts
|
|
@@ -4725,6 +4899,7 @@ AppModule = __decorateClass([
|
|
|
4725
4899
|
inject: [MORSE_CONFIG]
|
|
4726
4900
|
},
|
|
4727
4901
|
MorseRegistryLifecycle,
|
|
4902
|
+
ServerTerminalBackend,
|
|
4728
4903
|
MorseSessionFactory,
|
|
4729
4904
|
MorseGateway
|
|
4730
4905
|
]
|