@ariobarin/glossa 0.2.2 → 0.2.3

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.
Files changed (2) hide show
  1. package/dist/app.js +122 -22
  2. package/package.json +1 -1
package/dist/app.js CHANGED
@@ -39380,6 +39380,7 @@ function containsRestrictedAuthenticationData(value) {
39380
39380
 
39381
39381
  // ../protocol/dist/index.js
39382
39382
  var MAX_TEXT_BYTES = 1024 * 1024;
39383
+ var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
39383
39384
  var MAX_EDIT_DIFF_BYTES = 128 * 1024;
39384
39385
  var MAX_EDIT_OPERATIONS = 100;
39385
39386
  var MAX_COMMAND_OUTPUT_BYTES = 12 * 1024;
@@ -39428,6 +39429,13 @@ var readFileJobSchema = readFileRequestSchema.extend({
39428
39429
  type: external_exports.literal("read_file"),
39429
39430
  requestId: external_exports.string().uuid()
39430
39431
  });
39432
+ var viewImageRequestSchema = external_exports.object({
39433
+ path: relativePathSchema
39434
+ }).strict();
39435
+ var viewImageJobSchema = viewImageRequestSchema.extend({
39436
+ type: external_exports.literal("view_image"),
39437
+ requestId: external_exports.string().uuid()
39438
+ });
39431
39439
  var structuredReadTimeoutSchema = external_exports.number().int().min(1).max(MAX_STRUCTURED_READ_TIMEOUT_MS);
39432
39440
  var listFilesCursorSchema = external_exports.string().max(4096).describe("Opaque cursor returned by an earlier list_files result.");
39433
39441
  var listFilesRequestSchema = external_exports.object({
@@ -39570,6 +39578,7 @@ var cancelCommandJobSchema = cancelCommandRequestSchema.extend({
39570
39578
  });
39571
39579
  var workerJobSchema = external_exports.discriminatedUnion("type", [
39572
39580
  readFileJobSchema,
39581
+ viewImageJobSchema,
39573
39582
  listFilesJobSchema,
39574
39583
  searchTextJobSchema,
39575
39584
  readFileRangeJobSchema,
@@ -50491,6 +50500,18 @@ var PathPolicy = class _PathPolicy {
50491
50500
  function sha256(content) {
50492
50501
  return createHash("sha256").update(content).digest("hex");
50493
50502
  }
50503
+ function imageMimeType(content) {
50504
+ if (content.byteLength >= 8 && content[0] === 137 && content[1] === 80 && content[2] === 78 && content[3] === 71 && content[4] === 13 && content[5] === 10 && content[6] === 26 && content[7] === 10) {
50505
+ return "image/png";
50506
+ }
50507
+ if (content.byteLength >= 3 && content[0] === 255 && content[1] === 216 && content[2] === 255) {
50508
+ return "image/jpeg";
50509
+ }
50510
+ if (content.byteLength >= 12 && content[0] === 82 && content[1] === 73 && content[2] === 70 && content[3] === 70 && content[8] === 87 && content[9] === 69 && content[10] === 66 && content[11] === 80) {
50511
+ return "image/webp";
50512
+ }
50513
+ return void 0;
50514
+ }
50494
50515
  function isPathWithin(root, candidate) {
50495
50516
  const relative = path7.relative(root, candidate);
50496
50517
  return relative === "" || !relative.startsWith(`..${path7.sep}`) && relative !== ".." && !path7.isAbsolute(relative);
@@ -50898,6 +50919,43 @@ var FileService = class {
50898
50919
  const target = await this.policy.resolveExisting(relativePath);
50899
50920
  return await this.#readResolvedText(target);
50900
50921
  }
50922
+ async readImage(relativePath) {
50923
+ const target = await this.policy.resolveExisting(relativePath);
50924
+ const targetStat = await stat3(target);
50925
+ if (!targetStat.isFile()) {
50926
+ throw new WorkerError("not_file", "The requested path is not a file.");
50927
+ }
50928
+ if (targetStat.size > MAX_IMAGE_BYTES) {
50929
+ throw new WorkerError(
50930
+ "image_too_large",
50931
+ "The image exceeds the 4 MiB image limit."
50932
+ );
50933
+ }
50934
+ const content = await this.#readFileBytes(
50935
+ target,
50936
+ MAX_IMAGE_BYTES,
50937
+ targetStat.size
50938
+ );
50939
+ if (content.byteLength > MAX_IMAGE_BYTES) {
50940
+ throw new WorkerError(
50941
+ "image_too_large",
50942
+ "The image exceeds the 4 MiB image limit."
50943
+ );
50944
+ }
50945
+ const mimeType = imageMimeType(content);
50946
+ if (!mimeType) {
50947
+ throw new WorkerError(
50948
+ "unsupported_image",
50949
+ "Only PNG, JPEG, and WebP images are supported."
50950
+ );
50951
+ }
50952
+ return {
50953
+ data: content.toString("base64"),
50954
+ mimeType,
50955
+ sha256: sha256(content),
50956
+ bytes: content.byteLength
50957
+ };
50958
+ }
50901
50959
  async listFiles(options = {}) {
50902
50960
  const deadlineAt = this.#scanDeadline(options.timeoutMs);
50903
50961
  const startPath = options.path ?? ".";
@@ -51589,6 +51647,7 @@ function restrictedDataError2() {
51589
51647
  function jobInputContainsRestrictedData(job) {
51590
51648
  switch (job.type) {
51591
51649
  case "read_file":
51650
+ case "view_image":
51592
51651
  return containsRestrictedAuthenticationData(job.path);
51593
51652
  case "list_files":
51594
51653
  return containsRestrictedAuthenticationData({
@@ -51679,6 +51738,9 @@ var LocalWorker = class _LocalWorker {
51679
51738
  case "read_file":
51680
51739
  value = await this.files.readText(job.path);
51681
51740
  break;
51741
+ case "view_image":
51742
+ value = await this.files.readImage(job.path);
51743
+ break;
51682
51744
  case "list_files":
51683
51745
  value = await this.files.listFiles({
51684
51746
  ...job.path ? { path: job.path } : {},
@@ -51792,13 +51854,17 @@ var DEFAULT_HEARTBEAT_MS = 15e3;
51792
51854
  var CAPACITY_REFRESH_POLL_MS = 1;
51793
51855
  var MAX_CONCURRENT_JOBS = 5;
51794
51856
  var WORKER_TOKEN_PATTERN = /^glw_[A-Za-z0-9_-]{43}$/;
51795
- var WORKER_CAPABILITIES = {
51857
+ var LEGACY_WORKER_CAPABILITIES = {
51796
51858
  commandProgress: true,
51797
51859
  concurrentJobs: true,
51798
51860
  structuredReads: true,
51799
51861
  structuredMutations: true,
51800
51862
  commandOutputRanges: true
51801
51863
  };
51864
+ var WORKER_CAPABILITIES = {
51865
+ ...LEGACY_WORKER_CAPABILITIES,
51866
+ imageReads: true
51867
+ };
51802
51868
  var DeviceRejectedError = class extends Error {
51803
51869
  constructor() {
51804
51870
  super("The relay rejected the device credential.");
@@ -51827,14 +51893,18 @@ function requiredWorkerToken(value) {
51827
51893
  }
51828
51894
  return value;
51829
51895
  }
51830
- function acceptsCurrentCapabilities(value) {
51896
+ function acceptsCapabilities(value, requireImageReads) {
51831
51897
  if (typeof value !== "object" || value === null) return false;
51832
51898
  if (!("capabilities" in value)) return false;
51833
51899
  const capabilities = value.capabilities;
51834
51900
  if (typeof capabilities !== "object" || capabilities === null) return false;
51835
- return Object.keys(WORKER_CAPABILITIES).every(
51901
+ const required2 = Object.keys(LEGACY_WORKER_CAPABILITIES);
51902
+ if (!required2.every(
51836
51903
  (capability) => capabilities[capability] === true
51837
- );
51904
+ )) {
51905
+ return false;
51906
+ }
51907
+ return !requireImageReads || capabilities.imageReads === true;
51838
51908
  }
51839
51909
  function jobLane(job) {
51840
51910
  switch (job.type) {
@@ -51844,6 +51914,7 @@ function jobLane(job) {
51844
51914
  case "cancel_command":
51845
51915
  return "cancel";
51846
51916
  case "read_file":
51917
+ case "view_image":
51847
51918
  case "list_files":
51848
51919
  case "search_text":
51849
51920
  case "read_file_range":
@@ -51857,7 +51928,7 @@ function jobLane(job) {
51857
51928
  return "mutation";
51858
51929
  }
51859
51930
  }
51860
- function acceptedJobTypes(counts, total) {
51931
+ function acceptedJobTypes(counts, total, imageReads) {
51861
51932
  if (total >= MAX_CONCURRENT_JOBS) return [];
51862
51933
  const accepted = [];
51863
51934
  if (counts.status < 1) {
@@ -51865,7 +51936,9 @@ function acceptedJobTypes(counts, total) {
51865
51936
  }
51866
51937
  if (counts.cancel < 1) accepted.push("cancel_command");
51867
51938
  if (counts.read < 2) {
51868
- accepted.push("read_file", "list_files", "search_text", "read_file_range");
51939
+ accepted.push("read_file");
51940
+ if (imageReads) accepted.push("view_image");
51941
+ accepted.push("list_files", "search_text", "read_file_range");
51869
51942
  }
51870
51943
  if (counts.mutation < 1) {
51871
51944
  accepted.push(
@@ -51979,28 +52052,45 @@ var RemoteWorker = class {
51979
52052
  }
51980
52053
  }
51981
52054
  async #register() {
52055
+ const registrationBody = (capabilities) => ({
52056
+ workerId: this.#workerId,
52057
+ accessProfile: this.#accessProfile,
52058
+ ...this.#workerVersion ? { workerVersion: this.#workerVersion } : {},
52059
+ ...this.#workspaceLabel ? { workspaceLabel: this.#workspaceLabel } : {},
52060
+ capabilities
52061
+ });
51982
52062
  let response;
52063
+ let imageReads = true;
51983
52064
  try {
51984
- response = await this.#post("/device/register", {
51985
- workerId: this.#workerId,
51986
- accessProfile: this.#accessProfile,
51987
- ...this.#workerVersion ? { workerVersion: this.#workerVersion } : {},
51988
- ...this.#workspaceLabel ? { workspaceLabel: this.#workspaceLabel } : {},
51989
- capabilities: WORKER_CAPABILITIES
51990
- });
52065
+ response = await this.#post(
52066
+ "/device/register",
52067
+ registrationBody(WORKER_CAPABILITIES)
52068
+ );
51991
52069
  } catch (error46) {
51992
- if (error46 instanceof RelayResponseError && error46.status === 400) {
51993
- throw new RelayProtocolUnsupportedError();
52070
+ if (!(error46 instanceof RelayResponseError) || error46.status !== 400) {
52071
+ throw error46;
52072
+ }
52073
+ imageReads = false;
52074
+ try {
52075
+ response = await this.#post(
52076
+ "/device/register",
52077
+ registrationBody(LEGACY_WORKER_CAPABILITIES)
52078
+ );
52079
+ } catch (legacyError) {
52080
+ if (legacyError instanceof RelayResponseError && legacyError.status === 400) {
52081
+ throw new RelayProtocolUnsupportedError();
52082
+ }
52083
+ throw legacyError;
51994
52084
  }
51995
- throw error46;
51996
52085
  }
51997
52086
  const value = await response.json();
51998
- if (typeof value !== "object" || value === null || !("generation" in value) || typeof value.generation !== "string" || !("workerId" in value) || value.workerId !== this.#workerId || !("workerToken" in value) || !("accessProfile" in value) || value.accessProfile !== this.#accessProfile || !acceptsCurrentCapabilities(value) || this.#workspaceLabel !== void 0 && (!("workspaceLabel" in value) || value.workspaceLabel !== this.#workspaceLabel)) {
52087
+ if (typeof value !== "object" || value === null || !("generation" in value) || typeof value.generation !== "string" || !("workerId" in value) || value.workerId !== this.#workerId || !("workerToken" in value) || !("accessProfile" in value) || value.accessProfile !== this.#accessProfile || !acceptsCapabilities(value, imageReads) || this.#workspaceLabel !== void 0 && (!("workspaceLabel" in value) || value.workspaceLabel !== this.#workspaceLabel)) {
51999
52088
  throw new RelayProtocolUnsupportedError();
52000
52089
  }
52001
52090
  return {
52002
52091
  generation: value.generation,
52003
- workerToken: requiredWorkerToken(value.workerToken)
52092
+ workerToken: requiredWorkerToken(value.workerToken),
52093
+ imageReads
52004
52094
  };
52005
52095
  }
52006
52096
  async #pollGeneration(session) {
@@ -52076,7 +52166,11 @@ var RemoteWorker = class {
52076
52166
  try {
52077
52167
  while (!this.#signal.aborted) {
52078
52168
  if (failure !== void 0) throw failure;
52079
- const acceptedTypes = acceptedJobTypes(counts, inFlight.size);
52169
+ const acceptedTypes = acceptedJobTypes(
52170
+ counts,
52171
+ inFlight.size,
52172
+ session.imageReads
52173
+ );
52080
52174
  if (acceptedTypes.length === 0) {
52081
52175
  await Promise.race(inFlight);
52082
52176
  continue;
@@ -52102,7 +52196,11 @@ var RemoteWorker = class {
52102
52196
  }
52103
52197
  if (failure !== void 0) throw failure;
52104
52198
  observedCapacityVersion = capacityVersion;
52105
- const refreshedTypes = acceptedJobTypes(counts, inFlight.size);
52199
+ const refreshedTypes = acceptedJobTypes(
52200
+ counts,
52201
+ inFlight.size,
52202
+ session.imageReads
52203
+ );
52106
52204
  const newlyAcceptedTypes = refreshedTypes.filter(
52107
52205
  (type) => !acceptedTypes.includes(type)
52108
52206
  );
@@ -52221,6 +52319,7 @@ function activitySafeJob(job) {
52221
52319
  if (!containsRestrictedAuthenticationData(job)) return job;
52222
52320
  switch (job.type) {
52223
52321
  case "read_file":
52322
+ case "view_image":
52224
52323
  return { ...job, path: "[restricted input blocked]" };
52225
52324
  case "list_files":
52226
52325
  return {
@@ -52484,7 +52583,7 @@ function initialHudState(workspace) {
52484
52583
  notice: void 0
52485
52584
  };
52486
52585
  }
52487
- var MAX_STORED_ACTIVITIES = 999;
52586
+ var MAX_STORED_ACTIVITIES = 9999;
52488
52587
  var MAX_STORED_ACTIVITY_TARGET_CHARS = 512;
52489
52588
  function truncate(value, width) {
52490
52589
  if (width <= 0) return "";
@@ -52561,6 +52660,7 @@ function assertNever2(_value) {
52561
52660
  function summarizeJob(job) {
52562
52661
  switch (job.type) {
52563
52662
  case "read_file":
52663
+ case "view_image":
52564
52664
  return pathSummary(job.path);
52565
52665
  case "list_files":
52566
52666
  return pathSummary(workspacePath(job.path), [
@@ -54127,7 +54227,7 @@ async function acquireWorkspaceLease(root, options = {}) {
54127
54227
  }
54128
54228
 
54129
54229
  // src/main.ts
54130
- var VERSION = "0.2.2";
54230
+ var VERSION = "0.2.3";
54131
54231
  var DISTRIBUTION = "npm";
54132
54232
  var HELP = `Glossa ${VERSION}
54133
54233
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ariobarin/glossa",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "private": false,
5
5
  "description": "Connect an explicitly exposed local coding workspace to the Glossa MCP relay.",
6
6
  "type": "module",