@supanadit/morse-web 0.6.0 → 0.8.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 = 19;
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,190 @@ function shortcutRoots(options) {
3798
3804
  return roots;
3799
3805
  }
3800
3806
 
3801
- // ../server/src/internal/workspace/workspace-index.ts
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
+
3854
+ // ../server/src/internal/workspace/git-log.ts
3802
3855
  import { execFile } from "node:child_process";
3856
+ import { relative as relative3 } from "node:path";
3857
+ var DEFAULT_MAX_COMMITS = 250;
3858
+ var HARD_MAX_COMMITS = 500;
3859
+ var FIELD = "";
3860
+ var RECORD = "";
3861
+ var PRETTY_FORMAT = ["%H", "%h", "%P", "%D", "%an", "%aI", "%s"].join(FIELD) + RECORD;
3862
+ async function readGitLog(cwd, requestedMax, requestedSkip) {
3863
+ const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
3864
+ if (inside?.trim() !== "true") {
3865
+ return { isRepo: false, commits: [] };
3866
+ }
3867
+ const max = clampMax(requestedMax);
3868
+ const skip = clampSkip(requestedSkip);
3869
+ const [root, branch, raw] = await Promise.all([
3870
+ gitText(["rev-parse", "--show-toplevel"], cwd),
3871
+ gitText(["rev-parse", "--abbrev-ref", "HEAD"], cwd),
3872
+ gitText(
3873
+ [
3874
+ "log",
3875
+ "--all",
3876
+ "--date-order",
3877
+ `--skip=${skip}`,
3878
+ `--max-count=${max}`,
3879
+ `--pretty=format:${PRETTY_FORMAT}`
3880
+ ],
3881
+ cwd
3882
+ )
3883
+ ]);
3884
+ return {
3885
+ isRepo: true,
3886
+ root: root?.trim() || cwd,
3887
+ branch: branch?.trim() || void 0,
3888
+ commits: parseCommits(raw ?? "")
3889
+ };
3890
+ }
3891
+ function clampMax(requested) {
3892
+ if (requested === void 0 || !Number.isFinite(requested)) {
3893
+ return DEFAULT_MAX_COMMITS;
3894
+ }
3895
+ return Math.max(1, Math.min(HARD_MAX_COMMITS, Math.floor(requested)));
3896
+ }
3897
+ async function readGitStatus(cwd) {
3898
+ const inside = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
3899
+ if (inside?.trim() !== "true") {
3900
+ return { isRepo: false, files: [] };
3901
+ }
3902
+ const [prefix, raw] = await Promise.all([
3903
+ gitText(["rev-parse", "--show-prefix"], cwd),
3904
+ gitText(["status", "--porcelain=v1", "-z", "--untracked-files=all"], cwd)
3905
+ ]);
3906
+ return { isRepo: true, files: parseStatus(raw ?? "", prefix?.trim() ?? "") };
3907
+ }
3908
+ function parseStatus(raw, root) {
3909
+ const records = raw.split("\0");
3910
+ const files = [];
3911
+ for (let index = 0; index < records.length; index += 1) {
3912
+ const record = records[index];
3913
+ if (!record || record.length < 4) {
3914
+ continue;
3915
+ }
3916
+ const status = record.slice(0, 2);
3917
+ if (status[0] === "R" || status[0] === "C") {
3918
+ index += 1;
3919
+ }
3920
+ const full = record.slice(3);
3921
+ if (root.length > 0 && !full.startsWith(root)) {
3922
+ continue;
3923
+ }
3924
+ const path = full.slice(root.length);
3925
+ if (path.length > 0) {
3926
+ files.push({ path: toPosix2(path), status });
3927
+ }
3928
+ }
3929
+ return files;
3930
+ }
3931
+ async function readGitDiff(cwd, requested) {
3932
+ const rel = toPosix2(relative3(cwd, resolveWithin(cwd, requested)));
3933
+ const diff = await gitText(
3934
+ ["diff", "--no-color", "--no-ext-diff", "-U3", "HEAD", "--", rel],
3935
+ cwd
3936
+ );
3937
+ return { path: rel, diff: diff ?? "" };
3938
+ }
3939
+ function toPosix2(path) {
3940
+ return path.split("\\").join("/");
3941
+ }
3942
+ function clampSkip(requested) {
3943
+ if (requested === void 0 || !Number.isFinite(requested)) {
3944
+ return 0;
3945
+ }
3946
+ return Math.max(0, Math.floor(requested));
3947
+ }
3948
+ function parseCommits(raw) {
3949
+ const commits = [];
3950
+ for (const record of raw.split(RECORD)) {
3951
+ const line = record.replace(/^\n+/, "");
3952
+ if (line.trim().length === 0) {
3953
+ continue;
3954
+ }
3955
+ const fields = line.split(FIELD);
3956
+ const [hash, shortHash, parents, refs, author, date, subject] = fields;
3957
+ if (!hash || !shortHash || !date) {
3958
+ continue;
3959
+ }
3960
+ commits.push({
3961
+ hash,
3962
+ shortHash,
3963
+ parents: (parents ?? "").split(" ").filter((parent) => parent.length > 0),
3964
+ refs: parseRefs(refs ?? ""),
3965
+ author: author ?? "",
3966
+ date,
3967
+ subject: subject ?? ""
3968
+ });
3969
+ }
3970
+ return commits;
3971
+ }
3972
+ function parseRefs(raw) {
3973
+ const refs = raw.split(",").map((ref) => ref.trim()).filter((ref) => ref.length > 0);
3974
+ return refs;
3975
+ }
3976
+ function gitText(args, cwd) {
3977
+ return new Promise((resolve6) => {
3978
+ execFile(
3979
+ "git",
3980
+ args,
3981
+ { cwd, timeout: 5e3, maxBuffer: 16 * 1024 * 1024, windowsHide: true },
3982
+ (error, stdout) => resolve6(error ? void 0 : stdout)
3983
+ );
3984
+ });
3985
+ }
3986
+
3987
+ // ../server/src/internal/workspace/workspace-index.ts
3988
+ import { execFile as execFile2 } from "node:child_process";
3803
3989
  import { readdir as readdir4 } from "node:fs/promises";
3804
- import { join as join7, relative as relative2 } from "node:path";
3990
+ import { join as join7, relative as relative4 } from "node:path";
3805
3991
  var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set([
3806
3992
  "node_modules",
3807
3993
  "dist",
@@ -3822,15 +4008,15 @@ var MAX_INDEXED_FILES = 5e3;
3822
4008
  var INDEX_TTL_MS = 6e4;
3823
4009
  var indexes = /* @__PURE__ */ new Map();
3824
4010
  var inFlight = /* @__PURE__ */ new Map();
3825
- async function workspaceFiles(cwd, logger) {
3826
- const { files, source } = await cachedIndex(cwd);
4011
+ async function workspaceFiles(cwd, logger, options = {}) {
4012
+ const { files, source } = await cachedIndex(cwd, options.fresh === true);
3827
4013
  logger.info(`File picker: ${files.length} entries via ${source} in ${cwd}`);
3828
4014
  return files;
3829
4015
  }
3830
- async function cachedIndex(cwd) {
4016
+ async function cachedIndex(cwd, fresh) {
3831
4017
  const now = Date.now();
3832
4018
  const cached = indexes.get(cwd);
3833
- if (cached && now - cached.at < INDEX_TTL_MS) {
4019
+ if (!fresh && cached && now - cached.at < INDEX_TTL_MS) {
3834
4020
  return cached;
3835
4021
  }
3836
4022
  const pending = inFlight.get(cwd);
@@ -3885,12 +4071,12 @@ async function gitFileList(cwd) {
3885
4071
  }
3886
4072
  }
3887
4073
  function runGit(args, cwd) {
3888
- return new Promise((resolve5) => {
3889
- execFile(
4074
+ return new Promise((resolve6) => {
4075
+ execFile2(
3890
4076
  "git",
3891
4077
  args,
3892
4078
  { cwd, timeout: 5e3, maxBuffer: 8 * 1024 * 1024, windowsHide: true },
3893
- (error, stdout) => resolve5(error ? void 0 : stdout)
4079
+ (error, stdout) => resolve6(error ? void 0 : stdout)
3894
4080
  );
3895
4081
  });
3896
4082
  }
@@ -3921,11 +4107,11 @@ async function walk(root, directory, files) {
3921
4107
  continue;
3922
4108
  }
3923
4109
  if (entry.isFile() || entry.isSymbolicLink()) {
3924
- files.push(toPosix(relative2(root, full)));
4110
+ files.push(toPosix3(relative4(root, full)));
3925
4111
  }
3926
4112
  }
3927
4113
  }
3928
- function toPosix(path) {
4114
+ function toPosix3(path) {
3929
4115
  return path.split("\\").join("/");
3930
4116
  }
3931
4117
 
@@ -3952,6 +4138,12 @@ var MorseSessionFactory = class {
3952
4138
  // The server can walk the active session's directory, so the composer's
3953
4139
  // `@` picker works here too — the same list VS Code offers for a workspace.
3954
4140
  filePicker: true,
4141
+ // A browser has no editor of its own, so this host reads files for the
4142
+ // frontend's Explorer and preview tabs. VS Code keeps its native ones.
4143
+ filePreview: true,
4144
+ // The same reasoning for history: VS Code has Source Control, so the
4145
+ // browser host is the one that answers `gitLog` for its own git panel.
4146
+ gitPanel: true,
3955
4147
  // A browser cannot hand a dragged file's path to pi, so the host takes the
3956
4148
  // bytes and writes them next to the session; the frontend then `@mentions`
3957
4149
  // the path it gets back.
@@ -4015,15 +4207,41 @@ var MorseSessionFactory = class {
4015
4207
  }
4016
4208
  case "listFiles": {
4017
4209
  const cwd = this.requireWritableCwd(context);
4018
- const files = await workspaceFiles(cwd, this.logger);
4210
+ const files = await workspaceFiles(cwd, this.logger, { fresh: args?.fresh === true });
4019
4211
  return { files };
4020
4212
  }
4213
+ case "readFile": {
4214
+ const cwd = this.requireWritableCwd(context);
4215
+ const path = typeof args?.path === "string" ? args.path : "";
4216
+ if (path.length === 0) {
4217
+ throw new UnsupportedByHostError('readFile needs a "path" argument.');
4218
+ }
4219
+ return readWorkspaceFile(cwd, path);
4220
+ }
4021
4221
  case "uploadFile": {
4022
4222
  const cwd = this.requireWritableCwd(context);
4023
4223
  const saved = await saveUpload(cwd, args ?? {}, this.config.uploadDir);
4024
4224
  this.logger.info(`Upload stored: ${saved.path} (${saved.bytes} bytes)`);
4025
4225
  return saved;
4026
4226
  }
4227
+ case "gitLog": {
4228
+ const cwd = this.requireWritableCwd(context);
4229
+ const max = typeof args?.max === "number" ? args.max : void 0;
4230
+ const skip = typeof args?.skip === "number" ? args.skip : void 0;
4231
+ return readGitLog(cwd, max, skip);
4232
+ }
4233
+ case "gitStatus": {
4234
+ const cwd = this.requireWritableCwd(context);
4235
+ return readGitStatus(cwd);
4236
+ }
4237
+ case "gitDiff": {
4238
+ const cwd = this.requireWritableCwd(context);
4239
+ const path = typeof args?.path === "string" ? args.path : "";
4240
+ if (path.length === 0) {
4241
+ throw new UnsupportedByHostError('gitDiff needs a "path" argument.');
4242
+ }
4243
+ return readGitDiff(cwd, path);
4244
+ }
4027
4245
  default:
4028
4246
  throw new UnsupportedByHostError(`This host does not support "${command}".`);
4029
4247
  }
@@ -4107,7 +4325,7 @@ MorseGateway = __decorateClass([
4107
4325
  ], MorseGateway);
4108
4326
 
4109
4327
  // ../server/src/app/config.ts
4110
- import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute5, join as join8 } from "node:path";
4328
+ import { basename as basename4, dirname as dirname2, isAbsolute as isAbsolute6, join as join8 } from "node:path";
4111
4329
  import { existsSync as existsSync2, readFileSync } from "node:fs";
4112
4330
  import { fileURLToPath } from "node:url";
4113
4331
  function loadConfig(env = process.env) {
@@ -4118,7 +4336,7 @@ function loadConfig(env = process.env) {
4118
4336
  const monorepoUiDir = join8(moduleDir, "..", "..", "..", "ui-angular", "dist");
4119
4337
  const defaultUiDir = existsSync2(join8(bundledUiDir, "index.html")) ? bundledUiDir : monorepoUiDir;
4120
4338
  const defaultHost = "127.0.0.1";
4121
- const uiDir = configuredUiDir ? isAbsolute5(configuredUiDir) ? configuredUiDir : join8(process.cwd(), configuredUiDir) : defaultUiDir;
4339
+ const uiDir = configuredUiDir ? isAbsolute6(configuredUiDir) ? configuredUiDir : join8(process.cwd(), configuredUiDir) : defaultUiDir;
4122
4340
  return {
4123
4341
  host: env.MORSE_HOST ?? defaultHost,
4124
4342
  port: parsePositiveInt(env.MORSE_PORT, 4399),