@supanadit/morse-web 0.9.5 → 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 +451 -27
- 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-DSKZFA2H.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 firstLine2 = body.split("\n").find((line) => line.trim().length > 0);
|
|
1714
|
+
if (firstLine2 === void 0) {
|
|
1711
1715
|
return void 0;
|
|
1712
1716
|
}
|
|
1713
|
-
const trimmed =
|
|
1717
|
+
const trimmed = firstLine2.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";
|
|
@@ -3959,6 +4125,169 @@ async function readGitDiff(cwd, requested) {
|
|
|
3959
4125
|
);
|
|
3960
4126
|
return { path: rel, diff: diff ?? "" };
|
|
3961
4127
|
}
|
|
4128
|
+
async function readCommitFiles(cwd, requested) {
|
|
4129
|
+
const hash = requested.trim();
|
|
4130
|
+
if (!isCommitHash(hash)) {
|
|
4131
|
+
return { isRepo: false, hash, files: [] };
|
|
4132
|
+
}
|
|
4133
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
4134
|
+
if (inside?.trim() !== "true") {
|
|
4135
|
+
return { isRepo: false, hash, files: [] };
|
|
4136
|
+
}
|
|
4137
|
+
const raw = await gitText(
|
|
4138
|
+
["show", "--name-status", "--format=", "-z", "--find-renames", "--relative", hash],
|
|
4139
|
+
cwd
|
|
4140
|
+
);
|
|
4141
|
+
return { isRepo: true, hash, files: parseNameStatus(raw ?? "") };
|
|
4142
|
+
}
|
|
4143
|
+
async function readCommitDiff(cwd, hash, requested) {
|
|
4144
|
+
const rel = toPosix2(relative3(cwd, resolveWithin(cwd, requested)));
|
|
4145
|
+
if (!isCommitHash(hash.trim())) {
|
|
4146
|
+
return { path: rel, diff: "" };
|
|
4147
|
+
}
|
|
4148
|
+
const diff = await gitText(
|
|
4149
|
+
["show", "--format=", "--no-color", "--no-ext-diff", "-U3", "--relative", hash.trim(), "--", rel],
|
|
4150
|
+
cwd
|
|
4151
|
+
);
|
|
4152
|
+
return { path: rel, diff: diff ?? "" };
|
|
4153
|
+
}
|
|
4154
|
+
function isCommitHash(hash) {
|
|
4155
|
+
return /^[0-9a-fA-F]{4,40}$/.test(hash);
|
|
4156
|
+
}
|
|
4157
|
+
function parseNameStatus(raw) {
|
|
4158
|
+
const tokens = raw.split("\0");
|
|
4159
|
+
const files = [];
|
|
4160
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
4161
|
+
const status = tokens[index];
|
|
4162
|
+
if (!status) {
|
|
4163
|
+
continue;
|
|
4164
|
+
}
|
|
4165
|
+
const letter = status[0] ?? "";
|
|
4166
|
+
const rename = letter === "R" || letter === "C";
|
|
4167
|
+
const path = tokens[index + (rename ? 2 : 1)];
|
|
4168
|
+
index += rename ? 2 : 1;
|
|
4169
|
+
if (path) {
|
|
4170
|
+
files.push({ path: toPosix2(path), status: `${letter} ` });
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
return files;
|
|
4174
|
+
}
|
|
4175
|
+
async function stageGitPaths(cwd, requested) {
|
|
4176
|
+
const paths = resolvePaths(cwd, requested);
|
|
4177
|
+
if (paths.length > 0) {
|
|
4178
|
+
await gitRun(["add", "--", ...paths], cwd);
|
|
4179
|
+
}
|
|
4180
|
+
return readGitStatus(cwd);
|
|
4181
|
+
}
|
|
4182
|
+
async function unstageGitPaths(cwd, requested) {
|
|
4183
|
+
const paths = resolvePaths(cwd, requested);
|
|
4184
|
+
if (paths.length > 0) {
|
|
4185
|
+
const reset = await gitRun(["reset", "--quiet", "HEAD", "--", ...paths], cwd);
|
|
4186
|
+
if (!reset) {
|
|
4187
|
+
await gitRun(["rm", "--cached", "--quiet", "--", ...paths], cwd);
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
return readGitStatus(cwd);
|
|
4191
|
+
}
|
|
4192
|
+
async function readGitSync(cwd) {
|
|
4193
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
4194
|
+
if (inside?.trim() !== "true") {
|
|
4195
|
+
return { isRepo: false, ahead: 0, behind: 0 };
|
|
4196
|
+
}
|
|
4197
|
+
const [branch, upstream] = await Promise.all([
|
|
4198
|
+
gitText(["rev-parse", "--abbrev-ref", "HEAD"], cwd),
|
|
4199
|
+
gitText(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"], cwd)
|
|
4200
|
+
]);
|
|
4201
|
+
const name = branch?.trim() || void 0;
|
|
4202
|
+
const tracked = upstream?.trim() || void 0;
|
|
4203
|
+
if (tracked === void 0) {
|
|
4204
|
+
return { isRepo: true, branch: name, ahead: 0, behind: 0 };
|
|
4205
|
+
}
|
|
4206
|
+
const counts = await gitText(
|
|
4207
|
+
["rev-list", "--left-right", "--count", `${tracked}...HEAD`],
|
|
4208
|
+
cwd
|
|
4209
|
+
);
|
|
4210
|
+
const [behind, ahead] = parseCounts(counts ?? "");
|
|
4211
|
+
return { isRepo: true, branch: name, upstream: tracked, ahead, behind };
|
|
4212
|
+
}
|
|
4213
|
+
async function pullGit(cwd) {
|
|
4214
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
4215
|
+
if (inside?.trim() === "true") {
|
|
4216
|
+
await gitRun(["pull", "--no-edit"], cwd, 18e4);
|
|
4217
|
+
}
|
|
4218
|
+
return readGitSync(cwd);
|
|
4219
|
+
}
|
|
4220
|
+
async function pushGit(cwd) {
|
|
4221
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
4222
|
+
if (inside?.trim() === "true") {
|
|
4223
|
+
await gitRun(["push", "--follow-tags"], cwd, 18e4);
|
|
4224
|
+
}
|
|
4225
|
+
return readGitSync(cwd);
|
|
4226
|
+
}
|
|
4227
|
+
async function readGitBranches(cwd) {
|
|
4228
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
4229
|
+
if (inside?.trim() !== "true") {
|
|
4230
|
+
return { isRepo: false, local: [], remote: [], tags: [] };
|
|
4231
|
+
}
|
|
4232
|
+
const [current, local, remote, tags] = await Promise.all([
|
|
4233
|
+
gitText(["rev-parse", "--abbrev-ref", "HEAD"], cwd),
|
|
4234
|
+
gitText(["for-each-ref", "--format=%(refname:short)", "refs/heads"], cwd),
|
|
4235
|
+
gitText(["for-each-ref", "--format=%(refname:short)", "refs/remotes"], cwd),
|
|
4236
|
+
gitText(["for-each-ref", "--format=%(refname:short)", "refs/tags"], cwd)
|
|
4237
|
+
]);
|
|
4238
|
+
return {
|
|
4239
|
+
isRepo: true,
|
|
4240
|
+
current: current?.trim() || void 0,
|
|
4241
|
+
local: nonEmptyLines(local),
|
|
4242
|
+
remote: nonEmptyLines(remote).filter((name) => !name.endsWith("/HEAD")),
|
|
4243
|
+
tags: nonEmptyLines(tags)
|
|
4244
|
+
};
|
|
4245
|
+
}
|
|
4246
|
+
async function commitGit(cwd, message) {
|
|
4247
|
+
const text = message.trim();
|
|
4248
|
+
if (text.length === 0) {
|
|
4249
|
+
return { ok: false, message: "A commit needs a message." };
|
|
4250
|
+
}
|
|
4251
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
4252
|
+
if (inside?.trim() !== "true") {
|
|
4253
|
+
return { ok: false, message: "This project is not a git repository." };
|
|
4254
|
+
}
|
|
4255
|
+
const result = await gitExec(["commit", "-m", text], cwd, 6e4);
|
|
4256
|
+
return { ok: result.ok, message: result.ok ? void 0 : firstLine(result.output) };
|
|
4257
|
+
}
|
|
4258
|
+
async function checkoutGit(cwd, branch, create) {
|
|
4259
|
+
const name = branch.trim();
|
|
4260
|
+
if (!isBranchName(name)) {
|
|
4261
|
+
return { ok: false, message: "That is not a usable branch name." };
|
|
4262
|
+
}
|
|
4263
|
+
const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
4264
|
+
if (inside?.trim() !== "true") {
|
|
4265
|
+
return { ok: false, message: "This project is not a git repository." };
|
|
4266
|
+
}
|
|
4267
|
+
const result = await gitExec(
|
|
4268
|
+
create ? ["checkout", "-b", name] : ["checkout", name],
|
|
4269
|
+
cwd,
|
|
4270
|
+
6e4
|
|
4271
|
+
);
|
|
4272
|
+
return { ok: result.ok, message: result.ok ? void 0 : firstLine(result.output) };
|
|
4273
|
+
}
|
|
4274
|
+
function isBranchName(name) {
|
|
4275
|
+
return name.length > 0 && !name.startsWith("-") && !name.includes("..") && !name.includes("@{") && !name.endsWith("/") && !/\s/.test(name) && !/[~^:?*\[\\]/.test(name);
|
|
4276
|
+
}
|
|
4277
|
+
function firstLine(output) {
|
|
4278
|
+
const line = output.split("\n").find((candidate) => candidate.trim().length > 0)?.trim();
|
|
4279
|
+
if (line === void 0) {
|
|
4280
|
+
return void 0;
|
|
4281
|
+
}
|
|
4282
|
+
return line.length > 200 ? `${line.slice(0, 197)}\u2026` : line;
|
|
4283
|
+
}
|
|
4284
|
+
function nonEmptyLines(output) {
|
|
4285
|
+
return (output ?? "").split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
4286
|
+
}
|
|
4287
|
+
function parseCounts(raw) {
|
|
4288
|
+
const [behind, ahead] = raw.trim().split(/\s+/).map((value) => Number.parseInt(value, 10));
|
|
4289
|
+
return [Number.isFinite(behind) ? behind : 0, Number.isFinite(ahead) ? ahead : 0];
|
|
4290
|
+
}
|
|
3962
4291
|
function toPosix2(path) {
|
|
3963
4292
|
return path.split("\\").join("/");
|
|
3964
4293
|
}
|
|
@@ -4006,6 +4335,37 @@ function gitText(args, cwd) {
|
|
|
4006
4335
|
);
|
|
4007
4336
|
});
|
|
4008
4337
|
}
|
|
4338
|
+
function resolvePaths(cwd, requested) {
|
|
4339
|
+
return requested.filter((path) => typeof path === "string" && path.length > 0).map((path) => toPosix2(relative3(cwd, resolveWithin(cwd, path))));
|
|
4340
|
+
}
|
|
4341
|
+
async function gitRun(args, cwd, timeout = 5e3) {
|
|
4342
|
+
return (await gitExec(args, cwd, timeout)).ok;
|
|
4343
|
+
}
|
|
4344
|
+
function gitExec(args, cwd, timeout = 5e3) {
|
|
4345
|
+
return new Promise((resolve6) => {
|
|
4346
|
+
execFile(
|
|
4347
|
+
"git",
|
|
4348
|
+
args,
|
|
4349
|
+
{
|
|
4350
|
+
cwd,
|
|
4351
|
+
timeout,
|
|
4352
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
4353
|
+
windowsHide: true,
|
|
4354
|
+
env: {
|
|
4355
|
+
...process.env,
|
|
4356
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
4357
|
+
GIT_EDITOR: "true",
|
|
4358
|
+
GIT_MERGE_AUTOEDIT: "no"
|
|
4359
|
+
}
|
|
4360
|
+
},
|
|
4361
|
+
(error, stdout, stderr) => {
|
|
4362
|
+
resolve6(
|
|
4363
|
+
error === null ? { ok: true, output: stdout.trim() } : { ok: false, output: (stderr || error.message || "").trim() }
|
|
4364
|
+
);
|
|
4365
|
+
}
|
|
4366
|
+
);
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
4009
4369
|
|
|
4010
4370
|
// ../server/src/internal/workspace/workspace-index.ts
|
|
4011
4371
|
import { execFile as execFile2 } from "node:child_process";
|
|
@@ -4140,12 +4500,13 @@ function toPosix3(path) {
|
|
|
4140
4500
|
|
|
4141
4501
|
// ../server/src/internal/ws/session-factory.service.ts
|
|
4142
4502
|
var MorseSessionFactory = class {
|
|
4143
|
-
constructor(registry, transcripts, policy, logger, config) {
|
|
4503
|
+
constructor(registry, transcripts, policy, logger, config, terminal) {
|
|
4144
4504
|
this.registry = registry;
|
|
4145
4505
|
this.transcripts = transcripts;
|
|
4146
4506
|
this.policy = policy;
|
|
4147
4507
|
this.logger = logger;
|
|
4148
4508
|
this.config = config;
|
|
4509
|
+
this.terminal = terminal;
|
|
4149
4510
|
}
|
|
4150
4511
|
capabilities() {
|
|
4151
4512
|
return {
|
|
@@ -4167,6 +4528,10 @@ var MorseSessionFactory = class {
|
|
|
4167
4528
|
// The same reasoning for history: VS Code has Source Control, so the
|
|
4168
4529
|
// browser host is the one that answers `gitLog` for its own git panel.
|
|
4169
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,
|
|
4170
4535
|
// A browser cannot hand a dragged file's path to pi, so the host takes the
|
|
4171
4536
|
// bytes and writes them next to the session; the frontend then `@mentions`
|
|
4172
4537
|
// the path it gets back.
|
|
@@ -4205,6 +4570,8 @@ var MorseSessionFactory = class {
|
|
|
4205
4570
|
// A page load must not create a session; the first prompt opens one.
|
|
4206
4571
|
autoOpen: false,
|
|
4207
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,
|
|
4208
4575
|
agentHint: "Install the pi CLI and make sure it is on PATH, or start the server with MORSE_PI_PATH set."
|
|
4209
4576
|
});
|
|
4210
4577
|
}
|
|
@@ -4265,6 +4632,54 @@ var MorseSessionFactory = class {
|
|
|
4265
4632
|
}
|
|
4266
4633
|
return readGitDiff(cwd, path);
|
|
4267
4634
|
}
|
|
4635
|
+
case "gitStage": {
|
|
4636
|
+
const cwd = this.requireWritableCwd(context);
|
|
4637
|
+
return stageGitPaths(cwd, gitPaths(args));
|
|
4638
|
+
}
|
|
4639
|
+
case "gitUnstage": {
|
|
4640
|
+
const cwd = this.requireWritableCwd(context);
|
|
4641
|
+
return unstageGitPaths(cwd, gitPaths(args));
|
|
4642
|
+
}
|
|
4643
|
+
case "gitCommitFiles": {
|
|
4644
|
+
const cwd = this.requireWritableCwd(context);
|
|
4645
|
+
const hash = typeof args?.hash === "string" ? args.hash : "";
|
|
4646
|
+
return readCommitFiles(cwd, hash);
|
|
4647
|
+
}
|
|
4648
|
+
case "gitCommitDiff": {
|
|
4649
|
+
const cwd = this.requireWritableCwd(context);
|
|
4650
|
+
const hash = typeof args?.hash === "string" ? args.hash : "";
|
|
4651
|
+
const path = typeof args?.path === "string" ? args.path : "";
|
|
4652
|
+
if (path.length === 0) {
|
|
4653
|
+
throw new UnsupportedByHostError('gitCommitDiff needs a "path" argument.');
|
|
4654
|
+
}
|
|
4655
|
+
return readCommitDiff(cwd, hash, path);
|
|
4656
|
+
}
|
|
4657
|
+
case "gitSync": {
|
|
4658
|
+
const cwd = this.requireWritableCwd(context);
|
|
4659
|
+
return readGitSync(cwd);
|
|
4660
|
+
}
|
|
4661
|
+
case "gitPull": {
|
|
4662
|
+
const cwd = this.requireWritableCwd(context);
|
|
4663
|
+
return pullGit(cwd);
|
|
4664
|
+
}
|
|
4665
|
+
case "gitPush": {
|
|
4666
|
+
const cwd = this.requireWritableCwd(context);
|
|
4667
|
+
return pushGit(cwd);
|
|
4668
|
+
}
|
|
4669
|
+
case "gitCommit": {
|
|
4670
|
+
const cwd = this.requireWritableCwd(context);
|
|
4671
|
+
const message = typeof args?.message === "string" ? args.message : "";
|
|
4672
|
+
return commitGit(cwd, message);
|
|
4673
|
+
}
|
|
4674
|
+
case "gitBranches": {
|
|
4675
|
+
const cwd = this.requireWritableCwd(context);
|
|
4676
|
+
return readGitBranches(cwd);
|
|
4677
|
+
}
|
|
4678
|
+
case "gitCheckout": {
|
|
4679
|
+
const cwd = this.requireWritableCwd(context);
|
|
4680
|
+
const branch = typeof args?.branch === "string" ? args.branch : "";
|
|
4681
|
+
return checkoutGit(cwd, branch, args?.create === true);
|
|
4682
|
+
}
|
|
4268
4683
|
default:
|
|
4269
4684
|
throw new UnsupportedByHostError(`This host does not support "${command}".`);
|
|
4270
4685
|
}
|
|
@@ -4292,13 +4707,21 @@ var MorseSessionFactory = class {
|
|
|
4292
4707
|
}
|
|
4293
4708
|
};
|
|
4294
4709
|
MorseSessionFactory = __decorateClass([
|
|
4295
|
-
|
|
4296
|
-
__decorateParam(0,
|
|
4297
|
-
__decorateParam(1,
|
|
4298
|
-
__decorateParam(2,
|
|
4299
|
-
__decorateParam(3,
|
|
4300
|
-
__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))
|
|
4301
4717
|
], MorseSessionFactory);
|
|
4718
|
+
function gitPaths(args) {
|
|
4719
|
+
const paths = args?.paths;
|
|
4720
|
+
if (!Array.isArray(paths)) {
|
|
4721
|
+
return [];
|
|
4722
|
+
}
|
|
4723
|
+
return paths.filter((path) => typeof path === "string" && path.length > 0);
|
|
4724
|
+
}
|
|
4302
4725
|
|
|
4303
4726
|
// ../server/src/internal/ws/morse.gateway.ts
|
|
4304
4727
|
var MorseGateway = class {
|
|
@@ -4343,8 +4766,8 @@ var MorseGateway = class {
|
|
|
4343
4766
|
};
|
|
4344
4767
|
MorseGateway = __decorateClass([
|
|
4345
4768
|
WebSocketGateway({ path: DEFAULT_WS_PATH }),
|
|
4346
|
-
__decorateParam(0,
|
|
4347
|
-
__decorateParam(1,
|
|
4769
|
+
__decorateParam(0, Inject5(MorseSessionFactory)),
|
|
4770
|
+
__decorateParam(1, Inject5(MORSE_LOGGER))
|
|
4348
4771
|
], MorseGateway);
|
|
4349
4772
|
|
|
4350
4773
|
// ../server/src/app/config.ts
|
|
@@ -4476,6 +4899,7 @@ AppModule = __decorateClass([
|
|
|
4476
4899
|
inject: [MORSE_CONFIG]
|
|
4477
4900
|
},
|
|
4478
4901
|
MorseRegistryLifecycle,
|
|
4902
|
+
ServerTerminalBackend,
|
|
4479
4903
|
MorseSessionFactory,
|
|
4480
4904
|
MorseGateway
|
|
4481
4905
|
]
|