@supanadit/morse-web 0.5.0 → 0.7.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 CHANGED
@@ -75,7 +75,7 @@ function hasKnownType(value, allowed) {
75
75
  }
76
76
 
77
77
  // ../protocol/dist/version.js
78
- var PROTOCOL_VERSION = 17;
78
+ var PROTOCOL_VERSION = 18;
79
79
  var FRONTEND_MANIFEST_FILE = "webview.manifest.json";
80
80
  var DEFAULT_WS_PATH = "/ws";
81
81
 
@@ -1108,14 +1108,14 @@ var PiRpcClient = class {
1108
1108
  throw this.failed ?? new AgentUnavailableError("The pi agent is not running.");
1109
1109
  }
1110
1110
  const id = `morse-${this.nextId++}`;
1111
- return new Promise((resolve5, reject) => {
1111
+ return new Promise((resolve6, reject) => {
1112
1112
  const timer = setTimeout(() => {
1113
1113
  this.pending.delete(id);
1114
1114
  reject(new AgentProtocolError(`pi did not answer "${command.type}" within ${timeoutMs} ms.`));
1115
1115
  }, timeoutMs);
1116
1116
  this.pending.set(id, {
1117
1117
  command: command.type,
1118
- resolve: (data) => resolve5(data),
1118
+ resolve: (data) => resolve6(data),
1119
1119
  reject,
1120
1120
  timer
1121
1121
  });
@@ -1134,14 +1134,14 @@ var PiRpcClient = class {
1134
1134
  return;
1135
1135
  }
1136
1136
  let exited = false;
1137
- await new Promise((resolve5) => {
1137
+ await new Promise((resolve6) => {
1138
1138
  const finish = () => {
1139
1139
  exited = true;
1140
- resolve5();
1140
+ resolve6();
1141
1141
  };
1142
1142
  child.once("exit", finish);
1143
1143
  child.stdin.end();
1144
- setTimeout(resolve5, SHUTDOWN_GRACE_MS).unref?.();
1144
+ setTimeout(resolve6, SHUTDOWN_GRACE_MS).unref?.();
1145
1145
  });
1146
1146
  if (!exited) {
1147
1147
  child.kill();
@@ -2612,6 +2612,7 @@ function noticeMessage(level, text, at) {
2612
2612
 
2613
2613
  // ../host-runtime/dist/session-controller.js
2614
2614
  var HISTORY_PAGE_SIZE = 50;
2615
+ var UNTITLED_SESSION = "New session";
2615
2616
  var HostSessionController = class {
2616
2617
  options;
2617
2618
  summaries = /* @__PURE__ */ new Map();
@@ -3322,7 +3323,7 @@ var HostSessionController = class {
3322
3323
  }
3323
3324
  sessions.push({
3324
3325
  id: key,
3325
- title: state?.sessionTitle ?? this.derivedTitle(key) ?? "New session",
3326
+ title: state?.sessionTitle ?? this.derivedTitle(key) ?? UNTITLED_SESSION,
3326
3327
  cwd,
3327
3328
  updatedAt: Date.now(),
3328
3329
  messageCount: 0
@@ -3409,7 +3410,12 @@ var HostSessionController = class {
3409
3410
  };
3410
3411
  const state = this.activeKey === void 0 ? void 0 : this.options.services.registry.stateOf(this.activeKey);
3411
3412
  if (state) {
3412
- return toSessionViewState(state, meta);
3413
+ const view = toSessionViewState(state, meta);
3414
+ if (view.sessionTitle === void 0 && this.activeKey !== void 0) {
3415
+ const catalog = this.summaries.get(this.activeKey)?.title;
3416
+ view.sessionTitle = catalog !== void 0 && catalog !== UNTITLED_SESSION ? catalog : this.derivedTitle(this.activeKey);
3417
+ }
3418
+ return view;
3413
3419
  }
3414
3420
  return draftSessionViewState(this.draftWorkspace ?? this.options.services.registry.defaultWorkspace, meta, this.draftCatalog, this.draftModel, this.draftThinking);
3415
3421
  }
@@ -3798,10 +3804,57 @@ function shortcutRoots(options) {
3798
3804
  return roots;
3799
3805
  }
3800
3806
 
3807
+ // ../server/src/internal/workspace/file-store.ts
3808
+ import { open as open2, stat as stat4 } from "node:fs/promises";
3809
+ import { isAbsolute as isAbsolute5, relative as relative2, resolve as resolve5 } from "node:path";
3810
+ var MAX_PREVIEW_BYTES = 512 * 1024;
3811
+ var SNIFF_BYTES = 8 * 1024;
3812
+ async function readWorkspaceFile(cwd, requested) {
3813
+ const target = resolveWithin(cwd, requested);
3814
+ const info = await stat4(target).catch(() => void 0);
3815
+ if (info === void 0) {
3816
+ throw new UnsupportedByHostError(`No such file: ${requested}`);
3817
+ }
3818
+ if (info.isDirectory()) {
3819
+ throw new UnsupportedByHostError(`${requested} is a directory, not a file.`);
3820
+ }
3821
+ const handle = await open2(target, "r");
3822
+ try {
3823
+ const length = Math.min(info.size, MAX_PREVIEW_BYTES);
3824
+ const buffer = Buffer.alloc(length);
3825
+ const { bytesRead } = await handle.read(buffer, 0, length, 0);
3826
+ const slice = buffer.subarray(0, bytesRead);
3827
+ const binary = slice.subarray(0, Math.min(bytesRead, SNIFF_BYTES)).includes(0);
3828
+ return {
3829
+ path: toPosix(relative2(cwd, target)),
3830
+ content: binary ? "" : slice.toString("utf8"),
3831
+ size: info.size,
3832
+ truncated: info.size > bytesRead,
3833
+ binary
3834
+ };
3835
+ } finally {
3836
+ await handle.close();
3837
+ }
3838
+ }
3839
+ function resolveWithin(cwd, requested) {
3840
+ if (isAbsolute5(requested)) {
3841
+ throw new UnsupportedByHostError("A preview only opens files inside the project.");
3842
+ }
3843
+ const target = resolve5(cwd, requested);
3844
+ const rel = relative2(cwd, target);
3845
+ if (rel.length === 0 || rel.startsWith("..") || isAbsolute5(rel)) {
3846
+ throw new UnsupportedByHostError("A preview only opens files inside the project.");
3847
+ }
3848
+ return target;
3849
+ }
3850
+ function toPosix(path) {
3851
+ return path.split("\\").join("/");
3852
+ }
3853
+
3801
3854
  // ../server/src/internal/workspace/workspace-index.ts
3802
3855
  import { execFile } from "node:child_process";
3803
3856
  import { readdir as readdir4 } from "node:fs/promises";
3804
- import { join as join7, relative as relative2 } from "node:path";
3857
+ import { join as join7, relative as relative3 } from "node:path";
3805
3858
  var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set([
3806
3859
  "node_modules",
3807
3860
  "dist",
@@ -3885,12 +3938,12 @@ async function gitFileList(cwd) {
3885
3938
  }
3886
3939
  }
3887
3940
  function runGit(args, cwd) {
3888
- return new Promise((resolve5) => {
3941
+ return new Promise((resolve6) => {
3889
3942
  execFile(
3890
3943
  "git",
3891
3944
  args,
3892
3945
  { cwd, timeout: 5e3, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
3893
- (error, stdout) => resolve5(error ? void 0 : stdout)
3946
+ (error, stdout) => resolve6(error ? void 0 : stdout)
3894
3947
  );
3895
3948
  });
3896
3949
  }
@@ -3921,11 +3974,11 @@ async function walk(root, directory, files) {
3921
3974
  continue;
3922
3975
  }
3923
3976
  if (entry.isFile() || entry.isSymbolicLink()) {
3924
- files.push(toPosix(relative2(root, full)));
3977
+ files.push(toPosix2(relative3(root, full)));
3925
3978
  }
3926
3979
  }
3927
3980
  }
3928
- function toPosix(path) {
3981
+ function toPosix2(path) {
3929
3982
  return path.split("\\").join("/");
3930
3983
  }
3931
3984
 
@@ -3952,6 +4005,9 @@ var MorseSessionFactory = class {
3952
4005
  // The server can walk the active session's directory, so the composer's
3953
4006
  // `@` picker works here too — the same list VS Code offers for a workspace.
3954
4007
  filePicker: true,
4008
+ // A browser has no editor of its own, so this host reads files for the
4009
+ // frontend's Explorer and preview tabs. VS Code keeps its native ones.
4010
+ filePreview: true,
3955
4011
  // A browser cannot hand a dragged file's path to pi, so the host takes the
3956
4012
  // bytes and writes them next to the session; the frontend then `@mentions`
3957
4013
  // the path it gets back.
@@ -4018,6 +4074,14 @@ var MorseSessionFactory = class {
4018
4074
  const files = await workspaceFiles(cwd, this.logger);
4019
4075
  return { files };
4020
4076
  }
4077
+ case "readFile": {
4078
+ const cwd = this.requireWritableCwd(context);
4079
+ const path = typeof args?.path === "string" ? args.path : "";
4080
+ if (path.length === 0) {
4081
+ throw new UnsupportedByHostError('readFile needs a "path" argument.');
4082
+ }
4083
+ return readWorkspaceFile(cwd, path);
4084
+ }
4021
4085
  case "uploadFile": {
4022
4086
  const cwd = this.requireWritableCwd(context);
4023
4087
  const saved = await saveUpload(cwd, args ?? {}, this.config.uploadDir);
@@ -4107,7 +4171,7 @@ MorseGateway = __decorateClass([
4107
4171
  ], MorseGateway);
4108
4172
 
4109
4173
  // ../server/src/app/config.ts
4110
- import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute5, join as join8 } from "node:path";
4174
+ import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute6, join as join8 } from "node:path";
4111
4175
  import { existsSync as existsSync2, readFileSync } from "node:fs";
4112
4176
  import { fileURLToPath } from "node:url";
4113
4177
  function loadConfig(env = process.env) {
@@ -4118,7 +4182,7 @@ function loadConfig(env = process.env) {
4118
4182
  const monorepoUiDir = join8(moduleDir, "..", "..", "..", "ui-angular", "dist");
4119
4183
  const defaultUiDir = existsSync2(join8(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
4120
4184
  const defaultHost = "127.0.0.1";
4121
- const uiDir = configuredUiDir ? isAbsolute5(configuredUiDir) ? configuredUiDir : join8(process.cwd(), configuredUiDir) : defaultUiDir;
4185
+ const uiDir = configuredUiDir ? isAbsolute6(configuredUiDir) ? configuredUiDir : join8(process.cwd(), configuredUiDir) : defaultUiDir;
4122
4186
  return {
4123
4187
  host: env.MORSE_HOST ?? defaultHost,
4124
4188
  port: parsePositiveInt(env.MORSE_PORT, 4399),