paseo-room 0.15.1 → 0.16.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/index.js CHANGED
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "paseo-room",
18
- version: "0.15.1",
18
+ version: "0.16.0",
19
19
  description: "One CLI that configures Codex, Claude Code, and Pi role homes in $HOME and registers them with your local Paseo daemon.",
20
20
  keywords: [
21
21
  "paseo",
@@ -3182,14 +3182,20 @@ function applyEvent(state, event) {
3182
3182
  return;
3183
3183
  }
3184
3184
  }
3185
- function project(projectId, events) {
3186
- const state = emptyProjectState(projectId);
3187
- for (const event of events) {
3185
+ function foldInto(state, events, from = 0) {
3186
+ for (let index = from; index < events.length; index += 1) {
3187
+ const event = events[index];
3188
+ if (event === void 0) continue;
3188
3189
  const problem = checkEvent(state, event);
3189
- if (problem !== void 0) return { state, violations: [{ eventId: event.id, type: event.type, message: problem }] };
3190
+ if (problem !== void 0) return { eventId: event.id, type: event.type, message: problem };
3190
3191
  applyEvent(state, event);
3191
3192
  }
3192
- return { state, violations: [] };
3193
+ return void 0;
3194
+ }
3195
+ function project(projectId, events) {
3196
+ const state = emptyProjectState(projectId);
3197
+ const violation = foldInto(state, events);
3198
+ return { state, violations: violation === void 0 ? [] : [violation] };
3193
3199
  }
3194
3200
  function reclaimCheck(state, assignmentId) {
3195
3201
  const refused = (code2, message) => ({ ok: false, code: code2, message });
@@ -3779,7 +3785,7 @@ var init_schema = __esm({
3779
3785
 
3780
3786
  // src/runtime-plugin/server/store/publish.ts
3781
3787
  import { randomBytes as randomBytes2 } from "crypto";
3782
- import { chmod as chmod2, link, lstat as lstat4, mkdir as mkdir2, open, readdir as readdir3, unlink as unlink2 } from "fs/promises";
3788
+ import { chmod as chmod2, link, lstat as lstat4, mkdir as mkdir2, open, unlink as unlink2 } from "fs/promises";
3783
3789
  import { join as join13 } from "path";
3784
3790
  function code(error) {
3785
3791
  return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
@@ -3847,10 +3853,6 @@ async function publishOnce(directory, name, content, hook) {
3847
3853
  if (path === "") throw new AlreadyPublishedError(join13(directory, name));
3848
3854
  return path;
3849
3855
  }
3850
- async function staleTemporaries(directory) {
3851
- const names = await readdir3(directory);
3852
- return names.filter((name) => name.startsWith(TEMPORARY_PREFIX)).sort();
3853
- }
3854
3856
  var TEMPORARY_PREFIX, PRIVATE_DIR_MODE, PRIVATE_FILE_MODE, PublishUnsupportedError, AlreadyPublishedError, UNSUPPORTED;
3855
3857
  var init_publish = __esm({
3856
3858
  "src/runtime-plugin/server/store/publish.ts"() {
@@ -3878,49 +3880,43 @@ var init_publish = __esm({
3878
3880
 
3879
3881
  // src/runtime-plugin/server/store/project.ts
3880
3882
  import { randomBytes as randomBytes3, randomUUID } from "crypto";
3881
- import { mkdir as mkdir3, readdir as readdir4, readFile as readFile4, rename as rename2, rm as rm3, writeFile as writeFile2 } from "fs/promises";
3883
+ import { mkdir as mkdir3, readdir as readdir3, readFile as readFile4, rename as rename2, rm as rm3, writeFile as writeFile2 } from "fs/promises";
3882
3884
  import { basename as basename2, join as join14 } from "path";
3883
3885
  import { z as z12 } from "zod";
3884
3886
  function runtimeRoot(roomHome) {
3885
3887
  return join14(roomHome, "runtime", RUNTIME_LAYOUT_VERSION);
3886
3888
  }
3887
- async function readJsonFiles(directory, names) {
3888
- const files = /* @__PURE__ */ new Map();
3889
- const pending = names.values();
3890
- const reader = async () => {
3891
- for (const name of pending) {
3892
- try {
3893
- files.set(name, { value: JSON.parse(await readFile4(join14(directory, name), "utf8")) });
3894
- } catch (error) {
3895
- files.set(name, { error: error instanceof Error ? error.message : String(error) });
3896
- }
3897
- }
3898
- };
3899
- await Promise.all(Array.from({ length: Math.min(REPLAY_READS_IN_FLIGHT, names.length) }, reader));
3900
- return files;
3901
- }
3902
- async function readEventFiles(directory, names) {
3903
- let known = READS.get(directory);
3904
- if (known === void 0) {
3905
- known = /* @__PURE__ */ new Map();
3906
- READS.set(directory, known);
3889
+ async function readChecked(path) {
3890
+ try {
3891
+ const read = readEvent(JSON.parse(await readFile4(path, "utf8")));
3892
+ return read.ok ? { event: read.event } : { reason: read.reason, detail: read.detail };
3893
+ } catch (error) {
3894
+ return { reason: "unreadable", detail: error instanceof Error ? error.message : String(error) };
3907
3895
  }
3896
+ }
3897
+ async function checkedFiles(directory, names) {
3898
+ const known = CHECKED.get(directory) ?? /* @__PURE__ */ new Map();
3899
+ CHECKED.set(directory, known);
3908
3900
  const present = new Set(names);
3909
3901
  for (const name of known.keys()) if (!present.has(name)) known.delete(name);
3910
- const missing = names.filter((name) => !known.has(name));
3911
- if (missing.length > 0) {
3912
- const read = readJsonFiles(directory, missing);
3913
- for (const name of missing) {
3914
- const file = read.then((files2) => files2.get(name) ?? { error: "The event file was not read." });
3915
- known.set(name, file);
3916
- void file.then((result) => {
3917
- if ("error" in result && known.get(name) === file) known.delete(name);
3918
- });
3919
- }
3920
- }
3921
- const files = /* @__PURE__ */ new Map();
3922
- for (const name of names) files.set(name, await (known.get(name) ?? Promise.resolve({ error: "The event file was not read." })));
3923
- return files;
3902
+ const missing = names.filter((name) => !known.has(name)).map((name) => {
3903
+ let settle;
3904
+ const result = new Promise((resolve5) => {
3905
+ settle = resolve5;
3906
+ });
3907
+ known.set(name, result);
3908
+ void result.then((found) => {
3909
+ if ("reason" in found && found.reason === "unreadable" && known.get(name) === result) known.delete(name);
3910
+ });
3911
+ return { name, settle };
3912
+ });
3913
+ const queue = missing.values();
3914
+ const reader = async () => {
3915
+ for (const { name, settle } of queue) settle(await readChecked(join14(directory, name)));
3916
+ };
3917
+ void Promise.all(Array.from({ length: Math.min(REPLAY_READS_IN_FLIGHT, missing.length) }, reader));
3918
+ const results = await Promise.all(names.map((name) => known.get(name) ?? Promise.resolve(NOT_READ)));
3919
+ return new Map(names.map((name, index) => [name, results[index] ?? NOT_READ]));
3924
3920
  }
3925
3921
  function eventName(sequence) {
3926
3922
  return `${String(sequence).padStart(12, "0")}.json`;
@@ -3929,7 +3925,7 @@ function slug(root) {
3929
3925
  const name = basename2(root).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
3930
3926
  return name === "" ? "project" : name;
3931
3927
  }
3932
- var RUNTIME_LAYOUT_VERSION, EVENT_FILE, MAX_SEQUENCE_ATTEMPTS, REPLAY_READS_IN_FLIGHT, metaSchema, READS, ProjectStore;
3928
+ var RUNTIME_LAYOUT_VERSION, EVENT_FILE, MAX_SEQUENCE_ATTEMPTS, REPLAY_READS_IN_FLIGHT, metaSchema, NOT_READ, CHECKED, ProjectStore;
3933
3929
  var init_project = __esm({
3934
3930
  "src/runtime-plugin/server/store/project.ts"() {
3935
3931
  "use strict";
@@ -3946,7 +3942,8 @@ var init_project = __esm({
3946
3942
  canonicalRoot: z12.string().min(1),
3947
3943
  gitCommonDir: z12.string().min(1)
3948
3944
  });
3949
- READS = /* @__PURE__ */ new Map();
3945
+ NOT_READ = { reason: "unreadable", detail: "The event file was not read." };
3946
+ CHECKED = /* @__PURE__ */ new Map();
3950
3947
  ProjectStore = class _ProjectStore {
3951
3948
  constructor(directory, meta, now) {
3952
3949
  this.directory = directory;
@@ -4003,7 +4000,7 @@ var init_project = __esm({
4003
4000
  const projects = join14(root, "projects");
4004
4001
  let names;
4005
4002
  try {
4006
- names = await readdir4(projects);
4003
+ names = await readdir3(projects);
4007
4004
  } catch {
4008
4005
  return [];
4009
4006
  }
@@ -4018,29 +4015,25 @@ var init_project = __esm({
4018
4015
  }
4019
4016
  /** Reads every event strictly. Any unreadable file pauses the project; nothing is moved. */
4020
4017
  async replay() {
4021
- const names = (await readdir4(this.eventsDirectory)).sort();
4022
- const files = await readEventFiles(this.eventsDirectory, names.filter((name) => EVENT_FILE.test(name)));
4018
+ const names = (await readdir3(this.eventsDirectory)).sort();
4019
+ const eventNames = names.filter((name) => EVENT_FILE.test(name));
4020
+ const files = await checkedFiles(this.eventsDirectory, eventNames);
4023
4021
  const problems = [];
4024
4022
  const events = [];
4025
4023
  const ids = /* @__PURE__ */ new Map();
4026
4024
  for (const name of names) {
4027
- if (name.startsWith(".tmp-")) continue;
4025
+ if (name.startsWith(TEMPORARY_PREFIX)) continue;
4028
4026
  const match = EVENT_FILE.exec(name);
4029
4027
  if (!match) {
4030
4028
  problems.push({ file: name, reason: "unexpected-file", detail: "Not a runtime event file name." });
4031
4029
  continue;
4032
4030
  }
4033
- const file = files.get(name) ?? { error: "The event file was not read." };
4034
- if ("error" in file) {
4035
- problems.push({ file: name, reason: "unreadable", detail: file.error });
4036
- continue;
4037
- }
4038
- const read = readEvent(file.value);
4039
- if (!read.ok) {
4040
- problems.push({ file: name, reason: read.reason, detail: read.detail });
4031
+ const file = files.get(name) ?? NOT_READ;
4032
+ if ("reason" in file) {
4033
+ problems.push({ file: name, reason: file.reason, detail: file.detail });
4041
4034
  continue;
4042
4035
  }
4043
- const event = read.event;
4036
+ const event = file.event;
4044
4037
  if (event.sequence !== Number(match[1])) {
4045
4038
  problems.push({ file: name, reason: "sequence-mismatch", detail: `File name does not match sequence ${String(event.sequence)}.` });
4046
4039
  continue;
@@ -4063,14 +4056,14 @@ var init_project = __esm({
4063
4056
  for (; expected < event.sequence; expected += 1) gaps.push(expected);
4064
4057
  expected = event.sequence + 1;
4065
4058
  }
4066
- const highest = names.map((name) => EVENT_FILE.exec(name)?.[1]).filter((value) => value !== void 0).map(Number);
4067
- this.lastSequence = Math.max(this.lastSequence, 0, ...highest);
4059
+ const highest = Number(EVENT_FILE.exec(eventNames.at(-1) ?? "")?.[1] ?? 0);
4060
+ this.lastSequence = Math.max(this.lastSequence, highest);
4068
4061
  return {
4069
4062
  status: problems.length === 0 ? "ok" : "paused",
4070
4063
  events,
4071
4064
  problems,
4072
4065
  gaps,
4073
- staleTemporaries: await staleTemporaries(this.eventsDirectory)
4066
+ staleTemporaries: names.filter((name) => name.startsWith(TEMPORARY_PREFIX))
4074
4067
  };
4075
4068
  }
4076
4069
  /**
@@ -4123,7 +4116,7 @@ var init_project = __esm({
4123
4116
  }
4124
4117
  /** Moves one named event file aside. Only an explicit operator action calls this. */
4125
4118
  async quarantine(file) {
4126
- if (!EVENT_FILE.test(file) && !file.startsWith(".tmp-")) throw new Error(`${file} is not a runtime event file.`);
4119
+ if (!EVENT_FILE.test(file) && !file.startsWith(TEMPORARY_PREFIX)) throw new Error(`${file} is not a runtime event file.`);
4127
4120
  await ensurePrivateDirectory(this.quarantineDirectory);
4128
4121
  await rename2(join14(this.eventsDirectory, file), join14(this.quarantineDirectory, file));
4129
4122
  }
@@ -4133,11 +4126,11 @@ var init_project = __esm({
4133
4126
 
4134
4127
  // src/runtime-state.ts
4135
4128
  import { existsSync } from "fs";
4136
- import { readdir as readdir5 } from "fs/promises";
4129
+ import { readdir as readdir4 } from "fs/promises";
4137
4130
  import { join as join15 } from "path";
4138
4131
  async function inspectRuntimeState(roomHome) {
4139
4132
  const root = runtimeRoot(roomHome);
4140
- const names = await readdir5(join15(root, "projects")).catch(() => []);
4133
+ const names = await readdir4(join15(root, "projects")).catch(() => []);
4141
4134
  const blockers = [];
4142
4135
  const stores = [];
4143
4136
  let retained = 0;
@@ -4894,12 +4887,12 @@ init_layout();
4894
4887
  init_result();
4895
4888
  init_schema();
4896
4889
  init_project();
4897
- import { copyFile, mkdir as mkdir4, readdir as readdir6, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
4890
+ import { copyFile, mkdir as mkdir4, readdir as readdir5, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
4898
4891
  import { join as join17, resolve as resolve4 } from "path";
4899
4892
  var LIMITATION = "Briefs, answers, reports and gate commands are stored as written and cannot be proven secret-free; review the export before sharing it.";
4900
4893
  async function isEmptyOrAbsent(path) {
4901
4894
  try {
4902
- return (await readdir6(path)).length === 0;
4895
+ return (await readdir5(path)).length === 0;
4903
4896
  } catch {
4904
4897
  return true;
4905
4898
  }
@@ -4909,7 +4902,7 @@ async function exportRuntime(options = {}) {
4909
4902
  const root = runtimeRoot(layout.roomHome);
4910
4903
  let projects;
4911
4904
  try {
4912
- projects = (await readdir6(join17(root, "projects"))).sort();
4905
+ projects = (await readdir5(join17(root, "projects"))).sort();
4913
4906
  } catch {
4914
4907
  projects = [];
4915
4908
  }
@@ -4935,7 +4928,7 @@ async function exportRuntime(options = {}) {
4935
4928
  }
4936
4929
  const entry = { projectId: meta.projectId ?? name, canonicalRoot: meta.canonicalRoot ?? "unknown", events: 0, omitted: [], gateOutputs: 0 };
4937
4930
  copies.push([join17(source, "meta.json"), join17(target, "meta.json")]);
4938
- for (const file of (await readdir6(join17(source, "events")).catch(() => [])).sort()) {
4931
+ for (const file of (await readdir5(join17(source, "events")).catch(() => [])).sort()) {
4939
4932
  if (file.startsWith(".tmp-")) continue;
4940
4933
  let valid2;
4941
4934
  try {
@@ -4948,7 +4941,7 @@ async function exportRuntime(options = {}) {
4948
4941
  copies.push([join17(source, "events", file), join17(target, "events", file)]);
4949
4942
  } else entry.omitted.push(file);
4950
4943
  }
4951
- for (const file of (await readdir6(join17(source, "gates")).catch(() => [])).sort()) {
4944
+ for (const file of (await readdir5(join17(source, "gates")).catch(() => [])).sort()) {
4952
4945
  if (file.endsWith(".result.json")) copies.push([join17(source, "gates", file), join17(target, "gates", file)]);
4953
4946
  else if (file.endsWith(".log") && options.includeGateOutput === true) {
4954
4947
  entry.gateOutputs += 1;