flavor-code 1.3.2 → 1.3.4

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.
@@ -8025,7 +8025,7 @@ function toModelTool(tool) {
8025
8025
 
8026
8026
  // src/harness/journal.ts
8027
8027
  import { createHash as createHash6, randomUUID as randomUUID8 } from "crypto";
8028
- import { closeSync, constants as constants2, existsSync as existsSync2, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync as realpathSync2, statSync, truncateSync, writeSync } from "fs";
8028
+ import { closeSync, constants as constants2, existsSync as existsSync2, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync as realpathSync2, renameSync, statSync, truncateSync, unlinkSync, writeSync } from "fs";
8029
8029
  import { join as join8, relative as relative6, resolve as resolve8, sep as sep6 } from "path";
8030
8030
  import { z as z15 } from "zod";
8031
8031
 
@@ -8036,8 +8036,10 @@ function harnessInvariantViolations(records) {
8036
8036
  const tools = /* @__PURE__ */ new Set();
8037
8037
  const models = /* @__PURE__ */ new Set();
8038
8038
  const turns = /* @__PURE__ */ new Set();
8039
+ const firstSequence = records[0]?.sequence ?? 1;
8039
8040
  records.forEach((record3, index) => {
8040
- if (record3.sequence !== index + 1) violations.push(`sequence ${record3.sequence} is not ${index + 1}`);
8041
+ const expectedSequence = firstSequence + index;
8042
+ if (record3.sequence !== expectedSequence) violations.push(`sequence ${record3.sequence} is not ${expectedSequence}`);
8041
8043
  const id = typeof record3.payload.id === "string" ? record3.payload.id : "";
8042
8044
  if (record3.type === "queue.admitted") {
8043
8045
  if (queues.has(id)) violations.push(`queue ${id} was admitted twice`);
@@ -8053,13 +8055,13 @@ function harnessInvariantViolations(records) {
8053
8055
  if (!tools.delete(id)) violations.push(`${record3.type} references unknown tool ${id}`);
8054
8056
  } else if (record3.type === "model.requested") {
8055
8057
  if (models.has(id)) violations.push(`model ${id} was requested twice`);
8056
- if (!Array.isArray(record3.payload.messages)) violations.push(`model ${id} has no replayable messages`);
8058
+ if (!isHash(record3.payload.messagesHash)) violations.push(`model ${id} has no valid messages hash`);
8057
8059
  models.add(id);
8058
8060
  } else if (record3.type === "model.completed") {
8059
8061
  if (!models.delete(id)) violations.push(`model.completed references unknown model ${id}`);
8060
8062
  } else if (record3.type === "turn.started") {
8061
8063
  if (turns.has(id)) violations.push(`turn ${id} was started twice`);
8062
- if (!("prompt" in record3.payload)) violations.push(`turn ${id} has no replayable prompt`);
8064
+ if (!isHash(record3.payload.promptHash)) violations.push(`turn ${id} has no valid prompt hash`);
8063
8065
  turns.add(id);
8064
8066
  } else if (record3.type === "turn.completed" || record3.type === "turn.interrupted") {
8065
8067
  if (!turns.delete(id)) violations.push(`${record3.type} references unknown turn ${id}`);
@@ -8067,6 +8069,9 @@ function harnessInvariantViolations(records) {
8067
8069
  });
8068
8070
  return violations;
8069
8071
  }
8072
+ function isHash(value) {
8073
+ return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
8074
+ }
8070
8075
  function assertHarnessInvariants(records) {
8071
8076
  const violations = harnessInvariantViolations(records);
8072
8077
  if (violations.length > 0) throw new Error(`Harness invariant violation: ${violations.join("; ")}`);
@@ -8147,7 +8152,7 @@ var HarnessJournal = class {
8147
8152
  }
8148
8153
  startTurn(config, prompt) {
8149
8154
  const id = randomUUID8();
8150
- this.#append("turn.started", { id, config: structuredClone(config), configHash: hashJson(config), prompt: structuredClone(prompt), promptHash: hashJson(prompt) });
8155
+ this.#append("turn.started", { id, configHash: hashJson(config), promptHash: hashJson(prompt) });
8151
8156
  return id;
8152
8157
  }
8153
8158
  completeTurn(id) {
@@ -8158,7 +8163,14 @@ var HarnessJournal = class {
8158
8163
  }
8159
8164
  startModel(input) {
8160
8165
  const id = randomUUID8();
8161
- this.#append("model.requested", { id, ...structuredClone(input), messagesHash: hashJson(input.messages) });
8166
+ this.#append("model.requested", {
8167
+ id,
8168
+ agent: input.agent,
8169
+ model: input.model,
8170
+ iteration: input.iteration,
8171
+ attempt: input.attempt,
8172
+ messagesHash: hashJson(input.messages)
8173
+ });
8162
8174
  return id;
8163
8175
  }
8164
8176
  completeModel(id, completed, error) {
@@ -8166,11 +8178,11 @@ var HarnessJournal = class {
8166
8178
  }
8167
8179
  startTool(tool, input, retrySafe) {
8168
8180
  const id = randomUUID8();
8169
- this.#append("tool.started", { id, tool, input: structuredClone(input), inputHash: hashJson(input), retrySafe });
8181
+ this.#append("tool.started", { id, tool, inputHash: hashJson(input), retrySafe });
8170
8182
  return id;
8171
8183
  }
8172
8184
  completeTool(id, result) {
8173
- this.#append("tool.completed", { id, result: structuredClone(result) });
8185
+ this.#append("tool.completed", { id, resultHash: hashJson(result) });
8174
8186
  }
8175
8187
  interruptTool(id, reason) {
8176
8188
  this.#append("tool.interrupted", { id, reason });
@@ -8247,47 +8259,153 @@ var HarnessJournal = class {
8247
8259
  const committed = raw.endsWith("\n") ? raw : raw.slice(0, Math.max(0, raw.lastIndexOf("\n") + 1));
8248
8260
  if (committed.length !== raw.length) truncateSync(this.#path, Buffer.byteLength(committed));
8249
8261
  const lines = committed.split("\n").filter(Boolean);
8250
- let previousHash = null;
8262
+ let previousHash;
8263
+ let expectedSequence;
8251
8264
  for (const line of lines) {
8252
8265
  const record3 = JournalRecordSchema.parse(JSON.parse(line));
8253
- if (record3.sequence !== this.#records.length + 1) throw new Error("Harness journal sequence is not contiguous");
8266
+ expectedSequence ??= record3.sequence;
8267
+ previousHash ??= record3.previousHash;
8268
+ if (record3.sequence !== expectedSequence) throw new Error("Harness journal sequence is not contiguous");
8254
8269
  if (record3.previousHash !== previousHash) throw new Error("Harness journal hash chain is broken");
8255
8270
  if (record3.hash !== recordHash(record3)) throw new Error("Harness journal record hash is invalid");
8256
8271
  this.#records.push(record3);
8257
8272
  previousHash = record3.hash;
8273
+ expectedSequence += 1;
8258
8274
  }
8259
- this.#sequence = this.#records.length;
8260
- this.#lastHash = previousHash;
8275
+ this.#sequence = this.#records.at(-1)?.sequence ?? 0;
8276
+ this.#lastHash = previousHash ?? null;
8261
8277
  assertHarnessInvariants(this.#records);
8278
+ const hasRotatedPrefix = (this.#records[0]?.sequence ?? 1) !== 1;
8279
+ if (metadata.size > this.#maxBytes * 0.75 || hasRotatedPrefix || this.#records.some(hasLegacyPayload)) this.#compact();
8262
8280
  }
8263
8281
  #append(type, payload) {
8264
8282
  const normalizedPayload = jsonSafeRecord(payload);
8283
+ let record3 = this.#createRecord(type, normalizedPayload);
8284
+ let line = `${JSON.stringify(record3)}
8285
+ `;
8286
+ let priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8287
+ if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) {
8288
+ this.#compact();
8289
+ record3 = this.#createRecord(type, normalizedPayload);
8290
+ line = `${JSON.stringify(record3)}
8291
+ `;
8292
+ priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8293
+ if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) {
8294
+ throw new Error(`Harness journal event exceeds the ${this.#maxBytes} byte bounded journal capacity`);
8295
+ }
8296
+ }
8297
+ const fd = openSync(this.#path, constants2.O_CREAT | constants2.O_APPEND | constants2.O_WRONLY, 384);
8298
+ try {
8299
+ writeSync(fd, line, void 0, "utf8");
8300
+ fsyncSync(fd);
8301
+ } finally {
8302
+ closeSync(fd);
8303
+ }
8304
+ this.#records.push(record3);
8305
+ this.#sequence = record3.sequence;
8306
+ this.#lastHash = record3.hash;
8307
+ }
8308
+ #createRecord(type, payload) {
8265
8309
  const unsigned = {
8266
8310
  version: HARNESS_JOURNAL_VERSION,
8267
8311
  sequence: this.#sequence + 1,
8268
8312
  id: randomUUID8(),
8269
8313
  timestamp: this.#now(),
8270
8314
  type,
8271
- payload: normalizedPayload,
8315
+ payload,
8272
8316
  previousHash: this.#lastHash
8273
8317
  };
8274
- const record3 = JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8275
- const line = `${JSON.stringify(record3)}
8276
- `;
8277
- const priorBytes = existsSync2(this.#path) ? statSync(this.#path).size : 0;
8278
- if (priorBytes + Buffer.byteLength(line) > this.#maxBytes) throw new Error(`Harness journal exceeds ${this.#maxBytes} bytes`);
8279
- const fd = openSync(this.#path, constants2.O_CREAT | constants2.O_APPEND | constants2.O_WRONLY, 384);
8318
+ return JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8319
+ }
8320
+ #compact() {
8321
+ const queues = /* @__PURE__ */ new Map();
8322
+ const claims = /* @__PURE__ */ new Map();
8323
+ const tools = /* @__PURE__ */ new Map();
8324
+ const models = /* @__PURE__ */ new Map();
8325
+ const turns = /* @__PURE__ */ new Map();
8326
+ let savepoint;
8327
+ for (const record3 of this.#records) {
8328
+ const id = typeof record3.payload.id === "string" ? record3.payload.id : "";
8329
+ if (record3.type === "queue.admitted") queues.set(id, record3);
8330
+ else if (record3.type === "queue.claimed") claims.set(id, record3);
8331
+ else if (record3.type === "queue.released") claims.delete(id);
8332
+ else if (record3.type === "queue.acked") {
8333
+ queues.delete(id);
8334
+ claims.delete(id);
8335
+ } else if (record3.type === "tool.started") tools.set(id, record3);
8336
+ else if (record3.type === "tool.completed" || record3.type === "tool.interrupted") tools.delete(id);
8337
+ else if (record3.type === "model.requested") models.set(id, record3);
8338
+ else if (record3.type === "model.completed") models.delete(id);
8339
+ else if (record3.type === "turn.started") turns.set(id, record3);
8340
+ else if (record3.type === "turn.completed" || record3.type === "turn.interrupted") turns.delete(id);
8341
+ else if (record3.type === "savepoint.created") savepoint = record3;
8342
+ }
8343
+ const retained = [
8344
+ ...queues.values(),
8345
+ ...claims.values(),
8346
+ ...tools.values(),
8347
+ ...models.values(),
8348
+ ...turns.values(),
8349
+ ...savepoint === void 0 ? [] : [savepoint]
8350
+ ].sort((left, right) => left.sequence - right.sequence);
8351
+ const compacted = [];
8352
+ let previousHash = null;
8353
+ for (const source of retained) {
8354
+ const unsigned = {
8355
+ version: HARNESS_JOURNAL_VERSION,
8356
+ sequence: compacted.length + 1,
8357
+ id: randomUUID8(),
8358
+ timestamp: source.timestamp,
8359
+ type: source.type,
8360
+ payload: compactPayload(source),
8361
+ previousHash
8362
+ };
8363
+ const record3 = JournalRecordSchema.parse({ ...unsigned, hash: hashJson(unsigned) });
8364
+ compacted.push(record3);
8365
+ previousHash = record3.hash;
8366
+ }
8367
+ const contents = compacted.map((record3) => `${JSON.stringify(record3)}
8368
+ `).join("");
8369
+ if (Buffer.byteLength(contents) > this.#maxBytes) return;
8370
+ const temporary = `${this.#path}.compact-${process.pid}-${randomUUID8()}`;
8371
+ const fd = openSync(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
8280
8372
  try {
8281
- writeSync(fd, line, void 0, "utf8");
8373
+ writeSync(fd, contents, void 0, "utf8");
8282
8374
  fsyncSync(fd);
8283
8375
  } finally {
8284
8376
  closeSync(fd);
8285
8377
  }
8286
- this.#records.push(record3);
8287
- this.#sequence = record3.sequence;
8288
- this.#lastHash = record3.hash;
8378
+ try {
8379
+ renameSync(temporary, this.#path);
8380
+ } catch (error) {
8381
+ if (existsSync2(temporary)) unlinkSync(temporary);
8382
+ throw error;
8383
+ }
8384
+ this.#records = compacted;
8385
+ this.#sequence = compacted.length;
8386
+ this.#lastHash = previousHash;
8289
8387
  }
8290
8388
  };
8389
+ function compactPayload(record3) {
8390
+ const payload = record3.payload;
8391
+ if (record3.type === "queue.admitted") return payload;
8392
+ if (record3.type === "queue.claimed") return { id: payload.id };
8393
+ if (record3.type === "tool.started") return pick(payload, ["id", "tool", "inputHash", "retrySafe"]);
8394
+ if (record3.type === "model.requested") return pick(payload, ["id", "agent", "model", "iteration", "attempt", "messagesHash"]);
8395
+ if (record3.type === "turn.started") return pick(payload, ["id", "configHash", "promptHash"]);
8396
+ if (record3.type === "savepoint.created") return pick(payload, ["id", "phase", "configHash"]);
8397
+ return payload;
8398
+ }
8399
+ function hasLegacyPayload(record3) {
8400
+ if (record3.type === "turn.started") return "config" in record3.payload || "prompt" in record3.payload;
8401
+ if (record3.type === "model.requested") return "messages" in record3.payload;
8402
+ if (record3.type === "tool.started") return "input" in record3.payload;
8403
+ if (record3.type === "tool.completed") return "result" in record3.payload;
8404
+ return false;
8405
+ }
8406
+ function pick(source, keys) {
8407
+ return Object.fromEntries(keys.flatMap((key) => key in source ? [[key, source[key]]] : []));
8408
+ }
8291
8409
  function jsonSafeRecord(value) {
8292
8410
  const json = JSON.stringify(value, (_key, item) => typeof item === "bigint" ? `${item}n` : item);
8293
8411
  return JSON.parse(json);
@@ -10294,7 +10412,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
10294
10412
 
10295
10413
  // src/init/project.ts
10296
10414
  import { lstat as lstat4, mkdir as mkdir9, readdir as readdir4, readFile as readFile9, realpath as realpath7, writeFile as writeFile8 } from "fs/promises";
10297
- import { copyFileSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
10415
+ import { copyFileSync, mkdirSync as mkdirSync2, readdirSync } from "fs";
10298
10416
  import { basename as basename4, dirname as dirname9, isAbsolute as isAbsolute7, join as join12, relative as relative8, sep as sep8 } from "path";
10299
10417
  import { fileURLToPath } from "url";
10300
10418
  var GENERATED_START = "<!-- flavor-code:start -->";
@@ -10747,22 +10865,6 @@ async function createExampleFlavorConfig(cwd) {
10747
10865
  };
10748
10866
  await writeFile8(configPath, JSON.stringify(example, null, 2) + "\n");
10749
10867
  }
10750
- var CODEISLAND_SRC_DIR = join12(dirname9(fileURLToPath(import.meta.url)), "codeisland");
10751
- var CODEISLAND_FILES = ["flavor-plugin.json", "activate.mjs", "bridge.mjs"];
10752
- async function copyCodeIslandPlugin(cwd) {
10753
- const pluginDir = join12(cwd, ".flavor", "plugins", "codeisland");
10754
- try {
10755
- await lstat4(pluginDir);
10756
- return;
10757
- } catch (error) {
10758
- if (!isMissingPathError(error)) throw error;
10759
- }
10760
- await mkdir9(pluginDir, { recursive: true, mode: 448 });
10761
- for (const name of CODEISLAND_FILES) {
10762
- const content = readFileSync2(join12(CODEISLAND_SRC_DIR, name), "utf8");
10763
- await writeFile8(join12(pluginDir, name), content);
10764
- }
10765
- }
10766
10868
  var ASTGRAPH_SRC_DIR = join12(dirname9(fileURLToPath(import.meta.url)), "astgraph");
10767
10869
  async function copyAstgraphPlugin(cwd) {
10768
10870
  const pluginDir = join12(cwd, ".flavor", "plugins", "astgraph");
@@ -10785,7 +10887,6 @@ function copyDirectory(from, to) {
10785
10887
  }
10786
10888
  async function initializeFlavor(cwd) {
10787
10889
  await ensureFlavorDirectories(cwd);
10788
- await copyCodeIslandPlugin(cwd);
10789
10890
  await copyAstgraphPlugin(cwd);
10790
10891
  await createExampleFlavorConfig(cwd);
10791
10892
  const facts = await inspectProject(cwd);
@@ -13686,7 +13787,7 @@ import {
13686
13787
  } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
13687
13788
 
13688
13789
  // src/utils/version.ts
13689
- import { readFileSync as readFileSync3 } from "fs";
13790
+ import { readFileSync as readFileSync2 } from "fs";
13690
13791
  import { dirname as dirname13, join as join20 } from "path";
13691
13792
  import { fileURLToPath as fileURLToPath2 } from "url";
13692
13793
  var cached;
@@ -13695,7 +13796,7 @@ function packageVersion() {
13695
13796
  let directory = dirname13(fileURLToPath2(import.meta.url));
13696
13797
  for (; ; ) {
13697
13798
  try {
13698
- const manifest = JSON.parse(readFileSync3(join20(directory, "package.json"), "utf8"));
13799
+ const manifest = JSON.parse(readFileSync2(join20(directory, "package.json"), "utf8"));
13699
13800
  if (typeof manifest.version === "string" && manifest.version.length > 0) {
13700
13801
  cached = manifest.version;
13701
13802
  return cached;
@@ -24309,7 +24410,10 @@ async function createProductionRuntime(options) {
24309
24410
  globalPluginDirs: [join31(home, ".flavor-code", "plugins")],
24310
24411
  projectPluginDirs: [join31(workspace, ".flavor", "plugins")],
24311
24412
  config,
24312
- sandbox: options.pluginSandbox ?? true,
24413
+ // Existing project plugins (including astgraph and superharness) use Node.js
24414
+ // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
24415
+ // must stay opt-in until equivalent mediated capabilities are available.
24416
+ sandbox: options.pluginSandbox ?? false,
24313
24417
  registrations: {
24314
24418
  command(name, handler, description) {
24315
24419
  if (typeof handler !== "function") throw new Error(`Plugin command "${name}" must be a function.`);