flavor-code 1.3.4 → 1.3.7

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.
@@ -3517,6 +3517,8 @@ var FlavorSession = class {
3517
3517
  this.#activeSubmission = submission;
3518
3518
  this.#interrupted = false;
3519
3519
  let outcome = "completed";
3520
+ let assistantSummary = "";
3521
+ let deliverables = [];
3520
3522
  try {
3521
3523
  const decision = await this.#services.hooks.emit({
3522
3524
  version: 1,
@@ -3543,6 +3545,8 @@ var FlavorSession = class {
3543
3545
  ...submission.initialUserMessage === void 0 ? {} : { initialUserMessage: submission.initialUserMessage }
3544
3546
  })) {
3545
3547
  this.#services.output(event);
3548
+ if (event.type === "text") assistantSummary = `${assistantSummary}${event.text}`.slice(-2e3);
3549
+ if (event.type === "deliverables") deliverables = event.files.slice(0, 100);
3546
3550
  if (event.type === "error") outcome = "failed";
3547
3551
  }
3548
3552
  if (controller.signal.aborted) outcome = "cancelled";
@@ -3567,7 +3571,15 @@ var FlavorSession = class {
3567
3571
  this.#activeSubmission = void 0;
3568
3572
  if (submission.coWorkPlanningKey !== void 0) this.#finishActivePlanning(submission.coWorkPlanningKey);
3569
3573
  try {
3570
- await this.#services.hooks.emit({ version: 1, type: "Stop", payload: { outcome } });
3574
+ await this.#services.hooks.emit({
3575
+ version: 1,
3576
+ type: "Stop",
3577
+ payload: {
3578
+ outcome,
3579
+ ...assistantSummary.trim() ? { summary: assistantSummary.trim() } : {},
3580
+ ...deliverables.length === 0 ? {} : { deliverables }
3581
+ }
3582
+ });
3571
3583
  } catch (error) {
3572
3584
  try {
3573
3585
  this.#services.output({ type: "error", error: { code: "unknown", message: message(error) } });
@@ -5006,9 +5018,9 @@ async function ensureOnce(options) {
5006
5018
 
5007
5019
  // src/production.ts
5008
5020
  import { readFile as readFile22 } from "fs/promises";
5009
- import { createHash as createHash17, randomUUID as randomUUID18 } from "crypto";
5010
- import { homedir as homedir2, release as osRelease, tmpdir as tmpdir3, version as osVersion } from "os";
5011
- import { basename as basename6, join as join24, relative as relative20, resolve as resolve27, sep as sep20 } from "path";
5021
+ import { createHash as createHash18, randomUUID as randomUUID20 } from "crypto";
5022
+ import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
5023
+ import { basename as basename6, join as join25, relative as relative20, resolve as resolve27, sep as sep20 } from "path";
5012
5024
 
5013
5025
  // src/agent/subagents.ts
5014
5026
  import { z as z5 } from "zod";
@@ -6973,6 +6985,7 @@ var AgentLoop = class {
6973
6985
  attempt,
6974
6986
  messages: modelRequest.messages
6975
6987
  });
6988
+ const modelStartedAt = Date.now();
6976
6989
  yield { type: "model-start", id: modelCallId };
6977
6990
  try {
6978
6991
  for await (const event of adapter.stream(modelRequest)) {
@@ -7031,6 +7044,13 @@ var AgentLoop = class {
7031
7044
  completed,
7032
7045
  agent: this.#options.agent,
7033
7046
  providerError,
7047
+ durationMs: Math.max(0, Date.now() - modelStartedAt),
7048
+ ...usage === void 0 ? {} : {
7049
+ inputTokens: usage.inputTokens,
7050
+ outputTokens: usage.outputTokens,
7051
+ ...usage.cacheReadTokens === void 0 ? {} : { cacheReadTokens: usage.cacheReadTokens },
7052
+ ...usage.cacheCreationTokens === void 0 ? {} : { cacheCreationTokens: usage.cacheCreationTokens }
7053
+ },
7034
7054
  ...terminalError === void 0 ? {} : {
7035
7055
  errorCode: terminalError.code,
7036
7056
  errorMessage: terminalError.message
@@ -7484,11 +7504,18 @@ var DEFAULT_TOOL_OUTPUT_LIMITS = Object.freeze({
7484
7504
  perTurnChars: 2e5
7485
7505
  });
7486
7506
  var PreToolUsePayload = z9.object({
7507
+ // Optional for backwards-compatible hook updatedInput payloads; runtime
7508
+ // emissions always populate it.
7509
+ toolCallId: z9.string().optional(),
7487
7510
  tool: z9.string(),
7488
7511
  input: z9.unknown(),
7489
7512
  agent: z9.enum(["main", "subagent"])
7490
7513
  });
7491
- var PermissionRequestPayload = PreToolUsePayload.extend({ reason: z9.string().optional() });
7514
+ var PermissionRequestPayload = PreToolUsePayload.extend({
7515
+ reason: z9.string().optional(),
7516
+ toolCategory: z9.string(),
7517
+ allowAlways: z9.boolean()
7518
+ });
7492
7519
  var PostToolUsePayload = PreToolUsePayload.extend({ output: z9.unknown() });
7493
7520
  var PostToolUseFailurePayload = PreToolUsePayload.extend({
7494
7521
  error: z9.object({ code: z9.string(), message: z9.string() })
@@ -7605,30 +7632,31 @@ var ToolRuntime = class {
7605
7632
  const tool = this.#tools.get(call.name);
7606
7633
  if (tool === void 0) return { ok: false, error: { code: "unknown_tool", message: `Unknown tool: ${call.name}` } };
7607
7634
  const signal = context.signal ?? new AbortController().signal;
7635
+ const toolCallId = call.id ?? randomUUID7();
7608
7636
  const validation = this.validate(call);
7609
7637
  if (!validation.ok) {
7610
- return this.#fail(call.name, call.input, context.agent, validation.error.code, validation.error.message);
7638
+ return this.#fail(toolCallId, call.name, call.input, context.agent, validation.error.code, validation.error.message);
7611
7639
  }
7612
7640
  let input = validation.input;
7613
7641
  try {
7614
7642
  const pre = await this.#hooks.emit({
7615
7643
  version: 1,
7616
7644
  type: "PreToolUse",
7617
- payload: { tool: tool.name, input, agent: context.agent }
7645
+ payload: { toolCallId, tool: tool.name, input, agent: context.agent }
7618
7646
  });
7619
7647
  if (pre.updatedInput !== void 0) {
7620
7648
  const payload = PreToolUsePayload.parse(pre.updatedInput);
7621
7649
  if (payload.tool !== tool.name || payload.agent !== context.agent) {
7622
- return this.#fail(tool.name, input, context.agent, "invalid_input", "PreToolUse cannot change the tool or agent");
7650
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_input", "PreToolUse cannot change the tool or agent");
7623
7651
  }
7624
7652
  try {
7625
7653
  input = tool.inputSchema.parse(payload.input);
7626
7654
  } catch (error) {
7627
- return this.#fail(tool.name, payload.input, context.agent, "invalid_input", message(error));
7655
+ return this.#fail(toolCallId, tool.name, payload.input, context.agent, "invalid_input", message(error));
7628
7656
  }
7629
7657
  }
7630
7658
  if (pre.decision === "deny") {
7631
- return this.#fail(tool.name, input, context.agent, "hook_denied", pre.reason ?? "Tool use denied by hook");
7659
+ return this.#fail(toolCallId, tool.name, input, context.agent, "hook_denied", pre.reason ?? "Tool use denied by hook");
7632
7660
  }
7633
7661
  const request = {
7634
7662
  agent: context.agent,
@@ -7639,13 +7667,13 @@ var ToolRuntime = class {
7639
7667
  let permission = this.#permissions.decide(request);
7640
7668
  const nonCacheableApproval = request.allowAlways === false || permission.allowAlways === false;
7641
7669
  if (permission.decision === "deny") {
7642
- return this.#fail(tool.name, input, context.agent, "permission_denied", permission.reason ?? "Tool use denied");
7670
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", permission.reason ?? "Tool use denied");
7643
7671
  }
7644
7672
  if (permission.decision === "ask" && context.agent === "main" && !nonCacheableApproval && this.#permissions.mode === "auto" && this.#permissions.profile === "standard" && this.#classify !== void 0) {
7645
7673
  try {
7646
7674
  const classified = await this.#classify(request, signal);
7647
7675
  if (classified.decision === "deny") {
7648
- return this.#fail(tool.name, input, context.agent, "permission_denied", classified.reason ?? "Auto classifier denied tool use");
7676
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", classified.reason ?? "Auto classifier denied tool use");
7649
7677
  }
7650
7678
  if (classified.decision === "allow") permission = classified;
7651
7679
  else {
@@ -7662,13 +7690,21 @@ var ToolRuntime = class {
7662
7690
  const requestDecision = await this.#hooks.emit({
7663
7691
  version: 1,
7664
7692
  type: "PermissionRequest",
7665
- payload: { tool: tool.name, input: tool.permissionInput?.(input) ?? input, agent: context.agent, reason }
7693
+ payload: {
7694
+ toolCallId,
7695
+ tool: tool.name,
7696
+ input: tool.permissionInput?.(input) ?? input,
7697
+ agent: context.agent,
7698
+ reason,
7699
+ toolCategory: getToolCategory(tool.name),
7700
+ allowAlways
7701
+ }
7666
7702
  });
7667
7703
  if (requestDecision.decision === "deny") {
7668
- return this.#fail(tool.name, input, context.agent, "permission_denied", requestDecision.reason ?? "Permission request denied");
7704
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", requestDecision.reason ?? "Permission request denied");
7669
7705
  }
7670
7706
  if (requestDecision.updatedInput !== void 0) {
7671
- return this.#fail(tool.name, input, context.agent, "invalid_input", "PermissionRequest cannot modify an already-authorized tool call");
7707
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_input", "PermissionRequest cannot modify an already-authorized tool call");
7672
7708
  }
7673
7709
  if (this.#hooks.hasListeners("PermissionRequest") && requestDecision.decision === "allow") {
7674
7710
  const ctx = requestDecision.additionalContext ?? "";
@@ -7677,18 +7713,18 @@ var ToolRuntime = class {
7677
7713
  if (allowAlways && category !== "destructive") this.#alwaysAllowed.add(category);
7678
7714
  }
7679
7715
  } else if (context.agent !== "main" && this.#permissions.mode !== "bubble") {
7680
- return this.#fail(tool.name, input, context.agent, "approval_required", reason);
7716
+ return this.#fail(toolCallId, tool.name, input, context.agent, "approval_required", reason);
7681
7717
  } else if (signal.aborted) {
7682
7718
  throw signal.reason;
7683
7719
  } else {
7684
7720
  const category = getToolCategory(tool.name);
7685
7721
  if (allowAlways && this.#alwaysAllowed.has(category)) {
7686
7722
  } else if (this.#approve === void 0) {
7687
- return this.#fail(tool.name, input, context.agent, "permission_denied", reason);
7723
+ return this.#fail(toolCallId, tool.name, input, context.agent, "permission_denied", reason);
7688
7724
  } else {
7689
7725
  const decision = await this.#approve({ ...request, reason, ...allowAlways ? {} : { allowAlways: false } }, signal);
7690
7726
  if (decision === "deny") {
7691
- return this.#fail(tool.name, input, context.agent, "user_denied", reason);
7727
+ return this.#fail(toolCallId, tool.name, input, context.agent, "user_denied", reason);
7692
7728
  }
7693
7729
  if (decision === "always" && allowAlways && category !== "destructive") {
7694
7730
  this.#alwaysAllowed.add(category);
@@ -7702,7 +7738,7 @@ var ToolRuntime = class {
7702
7738
  try {
7703
7739
  rawOutput = tool.outputSchema === void 0 ? executed : tool.outputSchema.parse(executed);
7704
7740
  } catch (error) {
7705
- return this.#fail(tool.name, input, context.agent, "invalid_output", message(error));
7741
+ return this.#fail(toolCallId, tool.name, input, context.agent, "invalid_output", message(error));
7706
7742
  }
7707
7743
  const declaredPresentation = tool.presentResult?.(rawOutput, input);
7708
7744
  const presentation = declaredPresentation ?? getToolPresentation(rawOutput);
@@ -7713,7 +7749,7 @@ var ToolRuntime = class {
7713
7749
  await this.#hooks.emit({
7714
7750
  version: 1,
7715
7751
  type: "PostToolUse",
7716
- payload: { tool: tool.name, input, agent: context.agent, output }
7752
+ payload: { toolCallId, tool: tool.name, input, agent: context.agent, output }
7717
7753
  });
7718
7754
  return {
7719
7755
  ok: true,
@@ -7723,7 +7759,7 @@ var ToolRuntime = class {
7723
7759
  ...additionalContext === void 0 || additionalContext.length === 0 ? {} : { additionalContext }
7724
7760
  };
7725
7761
  } catch (error) {
7726
- return this.#fail(tool.name, input, context.agent, "tool_error", message(error));
7762
+ return this.#fail(toolCallId, tool.name, input, context.agent, "tool_error", message(error));
7727
7763
  }
7728
7764
  }
7729
7765
  async #limitOutput(tool, output) {
@@ -7771,13 +7807,13 @@ var ToolRuntime = class {
7771
7807
  const limited = await this.#limitOutput(tool, content);
7772
7808
  return typeof limited === "string" ? limited : serializeToolOutput(limited);
7773
7809
  }
7774
- async #fail(tool, input, agent, code, errorMessage2) {
7810
+ async #fail(toolCallId, tool, input, agent, code, errorMessage2) {
7775
7811
  const result = { ok: false, error: { code, message: errorMessage2 } };
7776
7812
  try {
7777
7813
  await this.#hooks.emit({
7778
7814
  version: 1,
7779
7815
  type: "PostToolUseFailure",
7780
- payload: { tool, input, agent, error: result.error }
7816
+ payload: { toolCallId, tool, input, agent, error: result.error }
7781
7817
  });
7782
7818
  } catch {
7783
7819
  }
@@ -8398,6 +8434,7 @@ function isWithin2(root, target) {
8398
8434
 
8399
8435
  // src/hooks/bus.ts
8400
8436
  import { spawn as spawn4 } from "child_process";
8437
+ import { randomUUID as randomUUID9 } from "crypto";
8401
8438
 
8402
8439
  // src/hooks/types.ts
8403
8440
  import { z as z11 } from "zod";
@@ -8464,6 +8501,11 @@ var DEFAULT_MAX_OUTPUT_BYTES = 1048576;
8464
8501
  var HookBus = class {
8465
8502
  #handlers = /* @__PURE__ */ new Map();
8466
8503
  #payloadSchemas = /* @__PURE__ */ new Map();
8504
+ #options;
8505
+ #sequence = 0;
8506
+ constructor(options = {}) {
8507
+ this.#options = options;
8508
+ }
8467
8509
  registerPayloadSchema(type, schema) {
8468
8510
  const registrations = this.#payloadSchemas.get(type) ?? [];
8469
8511
  const registration = { schema };
@@ -8500,7 +8542,9 @@ var HookBus = class {
8500
8542
  async emit(rawEvent, externalSignal) {
8501
8543
  externalSignal?.throwIfAborted();
8502
8544
  let event = HookEventSchema.parse(rawEvent);
8503
- event = { ...event, payload: this.#validatePayload(event.type, event.payload) };
8545
+ const validatedPayload = this.#validatePayload(event.type, event.payload);
8546
+ const envelope = this.#eventEnvelope();
8547
+ event = { ...event, payload: decoratePayload(validatedPayload, envelope) };
8504
8548
  let aggregate2 = { decision: "allow" };
8505
8549
  for (const registered of this.#handlers.get(event.type) ?? []) {
8506
8550
  externalSignal?.throwIfAborted();
@@ -8515,8 +8559,8 @@ var HookBus = class {
8515
8559
  externalSignal?.throwIfAborted();
8516
8560
  if (decision.updatedInput !== void 0) {
8517
8561
  const payload = this.#validatePayload(event.type, decision.updatedInput);
8518
- event = HookEventSchema.parse({ ...event, payload });
8519
- aggregate2 = { ...aggregate2, updatedInput: event.payload };
8562
+ event = HookEventSchema.parse({ ...event, payload: decoratePayload(payload, envelope) });
8563
+ aggregate2 = { ...aggregate2, updatedInput: payload };
8520
8564
  }
8521
8565
  if (decision.additionalContext !== void 0) {
8522
8566
  aggregate2 = {
@@ -8533,6 +8577,15 @@ var HookBus = class {
8533
8577
  const schema = this.#payloadSchemas.get(type)?.at(-1)?.schema;
8534
8578
  return schema ? schema.parse(payload) : zodRecord(payload);
8535
8579
  }
8580
+ #eventEnvelope() {
8581
+ if (this.#options.eventContext === void 0) return void 0;
8582
+ return {
8583
+ ...this.#options.eventContext(),
8584
+ eventId: (this.#options.eventId ?? randomUUID9)(),
8585
+ sequence: ++this.#sequence,
8586
+ timestamp: (this.#options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))()
8587
+ };
8588
+ }
8536
8589
  async #invoke(registered, event, externalSignal) {
8537
8590
  const timeoutSignal = AbortSignal.timeout(registered.timeoutMs);
8538
8591
  const signal = externalSignal === void 0 ? timeoutSignal : AbortSignal.any([timeoutSignal, externalSignal]);
@@ -8544,6 +8597,9 @@ var HookBus = class {
8544
8597
  return HookDecisionSchema.parse(await Promise.race([invocation, timeout]));
8545
8598
  }
8546
8599
  };
8600
+ function decoratePayload(payload, envelope) {
8601
+ return envelope === void 0 ? payload : { ...payload, ...envelope };
8602
+ }
8547
8603
  function runShellHandler(descriptor, event, signal) {
8548
8604
  return new Promise((resolve28, reject) => {
8549
8605
  const child = spawn4(descriptor.command, [...descriptor.args ?? []], {
@@ -8736,19 +8792,155 @@ function createIncidentReporter(options) {
8736
8792
  };
8737
8793
  }
8738
8794
 
8795
+ // src/island/control-server.ts
8796
+ import { createHash as createHash7, randomUUID as randomUUID10 } from "crypto";
8797
+ import { chmod as chmod3, unlink as unlink4 } from "fs/promises";
8798
+ import { tmpdir as tmpdir2 } from "os";
8799
+ import { join as join9 } from "path";
8800
+ import { createServer as createServer2 } from "net";
8801
+ var MAX_REQUEST_BYTES = 64 * 1024;
8802
+ var SOCKET_TIMEOUT_MS = 1e4;
8803
+ function islandControlEndpoint(sessionId, platform = process.platform) {
8804
+ const digest = createHash7("sha256").update(`${process.pid}\0${sessionId}`).digest("hex").slice(0, 20);
8805
+ if (platform === "win32") return `\\\\.\\pipe\\flavor-island-control-${digest}`;
8806
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
8807
+ return join9(tmpdir2(), `flavor-island-control-${uid}-${digest}.sock`);
8808
+ }
8809
+ async function createIslandControlServer(options) {
8810
+ const platform = options.platform ?? process.platform;
8811
+ const endpoint = options.endpoint ?? islandControlEndpoint(options.sessionId, platform);
8812
+ const token = options.token ?? randomUUID10();
8813
+ const capabilities = [
8814
+ "abort",
8815
+ "steer",
8816
+ "follow_up",
8817
+ ...options.focus === void 0 ? [] : ["focus"]
8818
+ ];
8819
+ if (platform !== "win32") await unlink4(endpoint).catch(() => void 0);
8820
+ const sockets = /* @__PURE__ */ new Set();
8821
+ const server = createServer2((socket) => {
8822
+ sockets.add(socket);
8823
+ socket.setTimeout(SOCKET_TIMEOUT_MS, () => socket.destroy());
8824
+ socket.on("close", () => sockets.delete(socket));
8825
+ socket.on("error", () => void 0);
8826
+ let buffer = Buffer.alloc(0);
8827
+ let handled = false;
8828
+ socket.on("data", (chunk) => {
8829
+ if (handled) return;
8830
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
8831
+ if (buffer.length + bytes.length > MAX_REQUEST_BYTES) {
8832
+ handled = true;
8833
+ write(socket, { ok: false, error: "request_too_large" });
8834
+ return;
8835
+ }
8836
+ buffer = Buffer.concat([buffer, bytes]);
8837
+ const newline = buffer.indexOf(10);
8838
+ if (newline < 0) return;
8839
+ handled = true;
8840
+ const line = buffer.subarray(0, newline).toString("utf8");
8841
+ void dispatch(line, socket, options, token);
8842
+ });
8843
+ });
8844
+ await new Promise((resolve28, reject) => {
8845
+ const onError = (error) => {
8846
+ server.removeListener("listening", onListening);
8847
+ reject(error);
8848
+ };
8849
+ const onListening = () => {
8850
+ server.removeListener("error", onError);
8851
+ resolve28();
8852
+ };
8853
+ server.once("error", onError);
8854
+ server.once("listening", onListening);
8855
+ server.listen(endpoint);
8856
+ });
8857
+ if (platform !== "win32") await chmod3(endpoint, 384).catch(() => void 0);
8858
+ let closed = false;
8859
+ return {
8860
+ endpoint,
8861
+ token,
8862
+ capabilities,
8863
+ async close() {
8864
+ if (closed) return;
8865
+ closed = true;
8866
+ for (const socket of sockets) socket.destroy();
8867
+ await closeServer(server);
8868
+ if (platform !== "win32") await unlink4(endpoint).catch(() => void 0);
8869
+ }
8870
+ };
8871
+ }
8872
+ async function dispatch(line, socket, options, token) {
8873
+ let request;
8874
+ try {
8875
+ const parsed = JSON.parse(line);
8876
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("invalid request");
8877
+ request = parsed;
8878
+ } catch {
8879
+ write(socket, { ok: false, error: "invalid_json" });
8880
+ return;
8881
+ }
8882
+ if (request.token !== token) {
8883
+ write(socket, { ok: false, error: "unauthorized" });
8884
+ return;
8885
+ }
8886
+ const command = request.command;
8887
+ try {
8888
+ if (command === "abort") {
8889
+ write(socket, { ok: true, result: options.session.interrupt() });
8890
+ } else if (command === "steer" || command === "follow_up") {
8891
+ const message2 = typeof request.message === "string" ? request.message.trim() : "";
8892
+ if (!message2 || message2.length > 1e5) {
8893
+ write(socket, { ok: false, error: "invalid_message" });
8894
+ return;
8895
+ }
8896
+ if (command === "steer") options.session.steer(message2);
8897
+ else options.session.followUp(message2);
8898
+ write(socket, { ok: true });
8899
+ } else if (command === "focus") {
8900
+ if (options.focus === void 0) {
8901
+ write(socket, { ok: false, error: "focus_unavailable" });
8902
+ return;
8903
+ }
8904
+ await options.focus();
8905
+ write(socket, { ok: true });
8906
+ } else {
8907
+ write(socket, { ok: false, error: "unknown_command" });
8908
+ }
8909
+ } catch (error) {
8910
+ write(socket, { ok: false, error: error instanceof Error ? error.message : String(error) });
8911
+ }
8912
+ }
8913
+ function write(socket, payload) {
8914
+ try {
8915
+ socket.end(`${JSON.stringify(payload)}
8916
+ `);
8917
+ } catch {
8918
+ socket.destroy();
8919
+ }
8920
+ }
8921
+ function closeServer(server) {
8922
+ return new Promise((resolve28) => {
8923
+ try {
8924
+ server.close(() => resolve28());
8925
+ } catch {
8926
+ resolve28();
8927
+ }
8928
+ });
8929
+ }
8930
+
8739
8931
  // src/evolve/service.ts
8740
8932
  import { spawn as spawn5 } from "child_process";
8741
8933
  import { writeFile as writeFile8 } from "fs/promises";
8742
- import { join as join11 } from "path";
8934
+ import { join as join12 } from "path";
8743
8935
  import { z as z14 } from "zod";
8744
8936
 
8745
8937
  // src/evolve/loader.ts
8746
8938
  import { cp, mkdir as mkdir8, mkdtemp, readdir as readdir4, rm as rm2, stat as stat4, writeFile as writeFile6 } from "fs/promises";
8747
- import { tmpdir as tmpdir2 } from "os";
8748
- import { join as join9, resolve as resolve10 } from "path";
8939
+ import { tmpdir as tmpdir3 } from "os";
8940
+ import { join as join10, resolve as resolve10 } from "path";
8749
8941
 
8750
8942
  // src/plugins/host.ts
8751
- import { createHash as createHash7 } from "crypto";
8943
+ import { createHash as createHash8 } from "crypto";
8752
8944
  import { lstatSync, realpathSync as realpathSync3 } from "fs";
8753
8945
  import { lstat as lstat5, open as open5, readFile as readFile7, readdir as readdir3, realpath as realpath4, stat as stat3, writeFile as writeFile5 } from "fs/promises";
8754
8946
  import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolve9, sep as sep8 } from "path";
@@ -9109,7 +9301,7 @@ var PluginHost = class {
9109
9301
  const manifest = PluginManifestSchema.parse(rawManifest);
9110
9302
  const entry2 = resolveContained(root, manifest.main, "Plugin entry");
9111
9303
  const entrySnapshot = await snapshotEntry(root, entry2);
9112
- const fingerprint2 = createHash7("sha256").update(JSON.stringify(manifest)).update(await readFile7(entrySnapshot.physicalPath)).digest("hex");
9304
+ const fingerprint2 = createHash8("sha256").update(JSON.stringify(manifest)).update(await readFile7(entrySnapshot.physicalPath)).digest("hex");
9113
9305
  return { manifest, root, source, entry: entry2, entrySnapshot, fingerprint: fingerprint2 };
9114
9306
  } catch (error) {
9115
9307
  this.#diagnose(diagnosticLabel, error, inputRoot);
@@ -9468,8 +9660,8 @@ function compare(left, right) {
9468
9660
  }
9469
9661
 
9470
9662
  // src/evolve/loader.ts
9471
- var PLUGIN_DIR = join9(".flavor", "plugins");
9472
- var VERSIONS_DIR = join9(".flavor", "plugins", ".versions");
9663
+ var PLUGIN_DIR = join10(".flavor", "plugins");
9664
+ var VERSIONS_DIR = join10(".flavor", "plugins", ".versions");
9473
9665
  function sanitizePluginName(tool) {
9474
9666
  const base = String(tool ?? "fix").replace(/[^a-z0-9-]/gi, "-").toLowerCase();
9475
9667
  return base.startsWith("fix-") ? base : `fix-${base}`;
@@ -9513,8 +9705,8 @@ export function activate(ctx) {
9513
9705
  async function scaffoldFixPlugin(workspace, name) {
9514
9706
  const dir = fixPluginDir(workspace, name);
9515
9707
  await mkdir8(dir, { recursive: true });
9516
- const manifestPath = join9(dir, "flavor-plugin.json");
9517
- const entryPath = join9(dir, "index.js");
9708
+ const manifestPath = join10(dir, "flavor-plugin.json");
9709
+ const entryPath = join10(dir, "index.js");
9518
9710
  try {
9519
9711
  await writeFile6(manifestPath, SCAFFOLD_MANIFEST(name), { encoding: "utf8", flag: "wx" });
9520
9712
  await writeFile6(entryPath, SCAFFOLD_ENTRY(name), { encoding: "utf8", flag: "wx" });
@@ -9534,9 +9726,9 @@ async function verifyFixPlugin(workspace, name) {
9534
9726
  return { ok: false, provided: [], tools: [], commands: [], error: `Plugin "${name}" not found.` };
9535
9727
  }
9536
9728
  const collected = { tools: [], commands: [] };
9537
- const sandbox = await mkdtemp(join9(tmpdir2(), "flavor-evolve-verify-"));
9729
+ const sandbox = await mkdtemp(join10(tmpdir3(), "flavor-evolve-verify-"));
9538
9730
  try {
9539
- await cp(pluginRoot, join9(sandbox, name), { recursive: true });
9731
+ await cp(pluginRoot, join10(sandbox, name), { recursive: true });
9540
9732
  const host = new PluginHost({
9541
9733
  projectPluginDirs: [sandbox],
9542
9734
  sandbox: true,
@@ -9587,7 +9779,7 @@ async function verifyFixPlugin(workspace, name) {
9587
9779
  }
9588
9780
  async function snapshotFixPlugin(workspace, name) {
9589
9781
  const pluginRoot = fixPluginDir(workspace, name);
9590
- const snapshotRoot = join9(versionsDirFor(workspace, name), timestamp());
9782
+ const snapshotRoot = join10(versionsDirFor(workspace, name), timestamp());
9591
9783
  await mkdir8(snapshotRoot, { recursive: true });
9592
9784
  await cp(pluginRoot, snapshotRoot, { recursive: true });
9593
9785
  return snapshotRoot;
@@ -9608,7 +9800,7 @@ async function revertFixPlugin(workspace, name) {
9608
9800
  }
9609
9801
  const snapshots = entries.filter((entry2) => !entry2.startsWith(".")).sort();
9610
9802
  if (snapshots.length === 0) throw new Error(`Plugin "${name}" has no snapshot to restore.`);
9611
- const latest = join9(versionsRoot, snapshots[snapshots.length - 1]);
9803
+ const latest = join10(versionsRoot, snapshots[snapshots.length - 1]);
9612
9804
  await rm2(pluginRoot, { recursive: true, force: true });
9613
9805
  await mkdir8(pluginRoot, { recursive: true });
9614
9806
  await cp(latest, pluginRoot, { recursive: true });
@@ -9616,14 +9808,14 @@ async function revertFixPlugin(workspace, name) {
9616
9808
  }
9617
9809
 
9618
9810
  // src/evolve/store.ts
9619
- import { createHash as createHash8 } from "crypto";
9811
+ import { createHash as createHash9 } from "crypto";
9620
9812
  import { appendFile, mkdir as mkdir9, readFile as readFile8, rm as rm3, writeFile as writeFile7 } from "fs/promises";
9621
- import { dirname as dirname11, join as join10 } from "path";
9813
+ import { dirname as dirname11, join as join11 } from "path";
9622
9814
  function normalizeError(message2) {
9623
9815
  return String(message2 ?? "").replace(/\s+/g, " ").replace(/"[^"]*"/g, '"\u2026"').replace(/`[^`]*`/g, '"\u2026"').trim().slice(0, 160);
9624
9816
  }
9625
9817
  function fingerprint(tool, errorCode, error) {
9626
- return createHash8("sha1").update(`${tool}::${errorCode ?? ""}::${normalizeError(error)}`).digest("hex").slice(0, 12);
9818
+ return createHash9("sha1").update(`${tool}::${errorCode ?? ""}::${normalizeError(error)}`).digest("hex").slice(0, 12);
9627
9819
  }
9628
9820
  function argKeys(args) {
9629
9821
  if (args === null || typeof args !== "object") return [];
@@ -9691,12 +9883,12 @@ var EvolveStore = class {
9691
9883
  #queue = Promise.resolve();
9692
9884
  constructor(options) {
9693
9885
  const { workspace, maxSignals = 400, maxRules = 20 } = options;
9694
- this.dir = join10(workspace, ".flavor", "evolve");
9695
- this.signalsFile = join10(this.dir, "signals.jsonl");
9696
- this.reflectionsFile = join10(this.dir, "reflections.jsonl");
9697
- this.doneFile = join10(this.dir, "done.json");
9698
- this.verifiedFile = join10(this.dir, "verified.json");
9699
- this.rulesFile = join10(this.dir, "rules.json");
9886
+ this.dir = join11(workspace, ".flavor", "evolve");
9887
+ this.signalsFile = join11(this.dir, "signals.jsonl");
9888
+ this.reflectionsFile = join11(this.dir, "reflections.jsonl");
9889
+ this.doneFile = join11(this.dir, "done.json");
9890
+ this.verifiedFile = join11(this.dir, "verified.json");
9891
+ this.rulesFile = join11(this.dir, "rules.json");
9700
9892
  this.maxSignals = maxSignals;
9701
9893
  this.maxRules = maxRules;
9702
9894
  }
@@ -10235,7 +10427,7 @@ function createEvolveService(options) {
10235
10427
  if (suggestion === void 0) return `No open suggestion with id "${suggestionId}". Use /evolve suggest to list them.`;
10236
10428
  const name = sanitizePluginName(suggestion.tool);
10237
10429
  const dir = await scaffoldFixPlugin(workspace, name);
10238
- await writeFile8(join11(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
10430
+ await writeFile8(join12(dir, "PLAN.md"), buildPlan(suggestion, name, "See the repeated failure below; describe your fix in PLAN.md."), "utf8");
10239
10431
  await snapshotFixPlugin(workspace, name);
10240
10432
  return [
10241
10433
  `suggestion ${suggestion.id}: ${suggestion.tool} x${suggestion.count} \u2014 ${suggestion.error}`,
@@ -10326,7 +10518,7 @@ ${result.stderr.slice(-4e3)}` : ""}`;
10326
10518
  }
10327
10519
  const name = sanitizePluginName(suggestion.tool);
10328
10520
  const dir = await scaffoldFixPlugin(workspace, name);
10329
- await writeFile8(join11(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
10521
+ await writeFile8(join12(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
10330
10522
  await snapshotFixPlugin(workspace, name);
10331
10523
  return [
10332
10524
  `Scaffolded fix plugin at ${dir} for suggestion [${suggestion.id}] (${suggestion.tool} x${suggestion.count}).`,
@@ -10747,9 +10939,9 @@ ${evidence.summary}`;
10747
10939
  };
10748
10940
 
10749
10941
  // src/goal/orchestrator.ts
10750
- import { createHash as createHash10 } from "crypto";
10942
+ import { createHash as createHash11 } from "crypto";
10751
10943
  import { writeFile as writeFile9, mkdir as mkdir10 } from "fs/promises";
10752
- import { join as join13 } from "path";
10944
+ import { join as join14 } from "path";
10753
10945
 
10754
10946
  // src/goal/types.ts
10755
10947
  import { z as z16 } from "zod";
@@ -10912,9 +11104,9 @@ function extractJson(text) {
10912
11104
  }
10913
11105
 
10914
11106
  // src/goal/classifier.ts
10915
- import { createHash as createHash9 } from "crypto";
11107
+ import { createHash as createHash10 } from "crypto";
10916
11108
  import { readFile as readFile9 } from "fs/promises";
10917
- import { join as join12 } from "path";
11109
+ import { join as join13 } from "path";
10918
11110
  function buildSkepticPrompt(evidence, plan, index) {
10919
11111
  const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
10920
11112
  return [
@@ -11087,11 +11279,11 @@ function deduplicateGaps(gaps) {
11087
11279
  }
11088
11280
  function computeFingerprint(gaps) {
11089
11281
  const content = gaps.map((g) => `${g.criterion}:${g.description}`).sort().join("\n");
11090
- return createHash9("sha256").update(content).digest("hex").slice(0, 16);
11282
+ return createHash10("sha256").update(content).digest("hex").slice(0, 16);
11091
11283
  }
11092
- async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash9("sha256").update(objective).digest("hex"), hostVerification) {
11284
+ async function collectEvidence(workspace, objective, finalResponse, priorGaps, contractHash2 = createHash10("sha256").update(objective).digest("hex"), hostVerification) {
11093
11285
  const workspaceState = await collectWorkspaceState(workspace);
11094
- const planPath = join12(workspace, ".flavor", "goal-plan.md");
11286
+ const planPath = join13(workspace, ".flavor", "goal-plan.md");
11095
11287
  let planFile = null;
11096
11288
  try {
11097
11289
  planFile = await readFile9(planPath, "utf8");
@@ -11118,7 +11310,7 @@ async function collectWorkspaceState(workspace) {
11118
11310
  return {
11119
11311
  changedFiles: [],
11120
11312
  status: ["not-a-git-worktree"],
11121
- diffHash: createHash9("sha256").update("not-a-git-worktree").digest("hex")
11313
+ diffHash: createHash10("sha256").update("not-a-git-worktree").digest("hex")
11122
11314
  };
11123
11315
  }
11124
11316
  const status = statusResult.stdout.split(/\r?\n/).map((line) => line.trimEnd()).filter(Boolean);
@@ -11135,7 +11327,7 @@ async function collectWorkspaceState(workspace) {
11135
11327
  return {
11136
11328
  changedFiles,
11137
11329
  status,
11138
- diffHash: createHash9("sha256").update(diff.stdout).update("\nUNTRACKED\n").update(untracked).digest("hex")
11330
+ diffHash: createHash10("sha256").update(diff.stdout).update("\nUNTRACKED\n").update(untracked).digest("hex")
11139
11331
  };
11140
11332
  }
11141
11333
 
@@ -11267,7 +11459,7 @@ var GoalOrchestrator = class {
11267
11459
  type: "not_achieved",
11268
11460
  gaps,
11269
11461
  summary: hostVerification.summary,
11270
- fingerprint: createHash10("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
11462
+ fingerprint: createHash11("sha256").update(hostVerification.summary).digest("hex").slice(0, 16)
11271
11463
  };
11272
11464
  }
11273
11465
  } else try {
@@ -11341,11 +11533,11 @@ var GoalOrchestrator = class {
11341
11533
  }
11342
11534
  };
11343
11535
  function contractHash(objective, plan) {
11344
- return createHash10("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
11536
+ return createHash11("sha256").update(JSON.stringify({ objective, plan })).digest("hex");
11345
11537
  }
11346
11538
  async function writePlanFile(workspace, plan) {
11347
- const dir = join13(workspace, ".flavor");
11348
- const planPath = join13(dir, "goal-plan.md");
11539
+ const dir = join14(workspace, ".flavor");
11540
+ const planPath = join14(dir, "goal-plan.md");
11349
11541
  await mkdir10(dir, { recursive: true });
11350
11542
  const criteriaText = plan.criteria.map((c) => `- [${c.type}] AC-${c.id}: ${c.description}`).join("\n");
11351
11543
  const content = [
@@ -11421,9 +11613,9 @@ function formatPriorGaps(gaps) {
11421
11613
 
11422
11614
  // src/goal/store.ts
11423
11615
  import { constants as constants2 } from "fs";
11424
- import { randomUUID as randomUUID9 } from "crypto";
11616
+ import { randomUUID as randomUUID11 } from "crypto";
11425
11617
  import { lstat as lstat6, mkdir as mkdir11, open as open6, rename as rename4, rm as rm4 } from "fs/promises";
11426
- import { dirname as dirname12, join as join14, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
11618
+ import { dirname as dirname12, join as join15, relative as relative9, resolve as resolve11, sep as sep9 } from "path";
11427
11619
  var DEFAULT_MAX_BYTES = 1024 * 1024;
11428
11620
  var GoalStore = class {
11429
11621
  #workspace;
@@ -11431,7 +11623,7 @@ var GoalStore = class {
11431
11623
  #maxBytes;
11432
11624
  constructor(options) {
11433
11625
  this.#workspace = resolve11(options.workspace);
11434
- this.#root = join14(this.#workspace, ".flavor", "goals");
11626
+ this.#root = join15(this.#workspace, ".flavor", "goals");
11435
11627
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
11436
11628
  }
11437
11629
  async save(input) {
@@ -11439,13 +11631,13 @@ var GoalStore = class {
11439
11631
  await assertNoSymlink(this.#workspace, dirname12(this.#root));
11440
11632
  await mkdir11(this.#root, { recursive: true, mode: 448 });
11441
11633
  await assertNoSymlink(this.#workspace, this.#root);
11442
- const target = join14(this.#root, `${state.id}.json`);
11634
+ const target = join15(this.#root, `${state.id}.json`);
11443
11635
  const body = `${JSON.stringify(state, null, 2)}
11444
11636
  `;
11445
11637
  if (Buffer.byteLength(body) > this.#maxBytes) {
11446
11638
  throw new Error(`Goal state exceeds maximum size of ${this.#maxBytes} bytes`);
11447
11639
  }
11448
- const temporary = join14(this.#root, `.${state.id}.${process.pid}.${randomUUID9()}.tmp`);
11640
+ const temporary = join15(this.#root, `.${state.id}.${process.pid}.${randomUUID11()}.tmp`);
11449
11641
  let handle;
11450
11642
  try {
11451
11643
  handle = await open6(temporary, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, 384);
@@ -11486,7 +11678,7 @@ function isCode3(error, code) {
11486
11678
 
11487
11679
  // src/loop/isolation.ts
11488
11680
  import { appendFile as appendFile2, mkdir as mkdir12, readFile as readFile10 } from "fs/promises";
11489
- import { join as join15, resolve as resolve12 } from "path";
11681
+ import { join as join16, resolve as resolve12 } from "path";
11490
11682
  var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
11491
11683
  var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
11492
11684
  function inferGoalIntent(goal) {
@@ -11509,8 +11701,8 @@ async function prepareLoopWorkspace(options) {
11509
11701
  return { kind: "needs_human", reason: "The workspace has uncommitted changes that a new worktree would omit." };
11510
11702
  }
11511
11703
  options.signal?.throwIfAborted();
11512
- const worktrees = join15(root, ".worktrees");
11513
- const path = join15(worktrees, `loop-${options.loopId}`);
11704
+ const worktrees = join16(root, ".worktrees");
11705
+ const path = join16(worktrees, `loop-${options.loopId}`);
11514
11706
  const branch = `loop/${options.loopId}`;
11515
11707
  try {
11516
11708
  await ensureWorktreeDirectoryIgnored(root);
@@ -11540,8 +11732,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
11540
11732
  const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
11541
11733
  if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
11542
11734
  const gitDirectory = resolve12(root, commonDirectory.stdout.trim());
11543
- const infoDirectory = join15(gitDirectory, "info");
11544
- const excludePath = join15(infoDirectory, "exclude");
11735
+ const infoDirectory = join16(gitDirectory, "info");
11736
+ const excludePath = join16(infoDirectory, "exclude");
11545
11737
  await mkdir12(infoDirectory, { recursive: true });
11546
11738
  let existing = "";
11547
11739
  try {
@@ -11557,8 +11749,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
11557
11749
  // src/loop/store.ts
11558
11750
  import { constants as constants3 } from "fs";
11559
11751
  import { lstat as lstat7, mkdir as mkdir13, open as open7, readFile as readFile11, realpath as realpath5, rename as rename5, rm as rm5, stat as stat5 } from "fs/promises";
11560
- import { dirname as dirname13, join as join16, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
11561
- import { randomUUID as randomUUID10 } from "crypto";
11752
+ import { dirname as dirname13, join as join17, relative as relative10, resolve as resolve13, sep as sep10 } from "path";
11753
+ import { randomUUID as randomUUID12 } from "crypto";
11562
11754
  var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
11563
11755
  var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
11564
11756
  var LoopStore = class {
@@ -11568,7 +11760,7 @@ var LoopStore = class {
11568
11760
  #appendTails = /* @__PURE__ */ new Map();
11569
11761
  constructor(options) {
11570
11762
  this.#workspace = resolve13(options.workspace);
11571
- this.#root = join16(this.#workspace, ".flavor", "loops");
11763
+ this.#root = join17(this.#workspace, ".flavor", "loops");
11572
11764
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
11573
11765
  }
11574
11766
  async create(state) {
@@ -11585,11 +11777,11 @@ var LoopStore = class {
11585
11777
  const state = LoopStateSchema.parse(input);
11586
11778
  await this.#assertWorkspace(state.workspace);
11587
11779
  const directory = await this.#prepare(state.loopId);
11588
- const target = join16(directory, "state.json");
11780
+ const target = join17(directory, "state.json");
11589
11781
  const body = `${JSON.stringify(state, null, 2)}
11590
11782
  `;
11591
11783
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Loop state exceeds maximum size of ${this.#maxBytes} bytes`);
11592
- const temporary = join16(directory, `.state.${process.pid}.${randomUUID10()}.tmp`);
11784
+ const temporary = join17(directory, `.state.${process.pid}.${randomUUID12()}.tmp`);
11593
11785
  let handle;
11594
11786
  try {
11595
11787
  handle = await open7(temporary, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
@@ -11605,7 +11797,7 @@ var LoopStore = class {
11605
11797
  }
11606
11798
  async load(loopId) {
11607
11799
  const directory = await this.#safeExisting(loopId);
11608
- const target = join16(directory, "state.json");
11800
+ const target = join17(directory, "state.json");
11609
11801
  try {
11610
11802
  const metadata = await stat5(target);
11611
11803
  if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
@@ -11615,7 +11807,7 @@ var LoopStore = class {
11615
11807
  return state;
11616
11808
  } catch (error) {
11617
11809
  if (isCode4(error, "ENOENT")) throw new Error(`Loop "${loopId}" was not found`);
11618
- const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID10().slice(0, 8)}`;
11810
+ const quarantine = `${target}.corrupt-${Date.now()}-${randomUUID12().slice(0, 8)}`;
11619
11811
  await rename5(target, quarantine).catch(() => void 0);
11620
11812
  throw new Error(`Loop "${loopId}" is corrupt and was quarantined: ${message(error)}`);
11621
11813
  }
@@ -11625,7 +11817,7 @@ var LoopStore = class {
11625
11817
  const previous = this.#appendTails.get(event.loopId) ?? Promise.resolve();
11626
11818
  const next = previous.catch(() => void 0).then(async () => {
11627
11819
  const directory = await this.#prepare(event.loopId);
11628
- const path = join16(directory, "events.jsonl");
11820
+ const path = join17(directory, "events.jsonl");
11629
11821
  const handle = await open7(path, constants3.O_CREAT | constants3.O_APPEND | constants3.O_WRONLY, 384);
11630
11822
  try {
11631
11823
  await handle.writeFile(`${JSON.stringify(event)}
@@ -11642,7 +11834,7 @@ var LoopStore = class {
11642
11834
  await assertNoSymlink2(this.#workspace, dirname13(this.#root));
11643
11835
  await mkdir13(this.#root, { recursive: true, mode: 448 });
11644
11836
  await assertNoSymlink2(this.#workspace, this.#root);
11645
- const directory = join16(this.#root, loopId);
11837
+ const directory = join17(this.#root, loopId);
11646
11838
  await mkdir13(directory, { recursive: true, mode: 448 });
11647
11839
  await assertNoSymlink2(this.#workspace, directory);
11648
11840
  return directory;
@@ -11650,7 +11842,7 @@ var LoopStore = class {
11650
11842
  async #safeExisting(loopId) {
11651
11843
  validateId(loopId);
11652
11844
  await assertNoSymlink2(this.#workspace, this.#root);
11653
- const directory = join16(this.#root, loopId);
11845
+ const directory = join17(this.#root, loopId);
11654
11846
  await assertNoSymlink2(this.#workspace, directory);
11655
11847
  return directory;
11656
11848
  }
@@ -11699,7 +11891,7 @@ function isCode4(error, code) {
11699
11891
 
11700
11892
  // src/loop/verifier.ts
11701
11893
  import { readFile as readFile12 } from "fs/promises";
11702
- import { join as join17 } from "path";
11894
+ import { join as join18 } from "path";
11703
11895
 
11704
11896
  // src/tools/shell.ts
11705
11897
  import { spawn as spawn6, spawnSync } from "child_process";
@@ -12257,13 +12449,13 @@ var SCRIPT_PRIORITY = ["test", "typecheck", "lint", "build", "smoke:install"];
12257
12449
  async function inferVerificationPlan(workspace) {
12258
12450
  const commands = [];
12259
12451
  const seen = /* @__PURE__ */ new Set();
12260
- const packageJson = await optionalJson(join17(workspace, "package.json"));
12452
+ const packageJson = await optionalJson(join18(workspace, "package.json"));
12261
12453
  const scripts = isRecord2(packageJson?.scripts) ? packageJson.scripts : {};
12262
12454
  for (const name of SCRIPT_PRIORITY) {
12263
12455
  if (typeof scripts[name] !== "string" || isUnconditionalPassThrough(scripts[name])) continue;
12264
12456
  add(commands, seen, npmScript(name));
12265
12457
  }
12266
- const flavor = await optionalText(join17(workspace, "FLAVOR.md"));
12458
+ const flavor = await optionalText(join18(workspace, "FLAVOR.md"));
12267
12459
  if (flavor !== void 0) {
12268
12460
  for (const match of flavor.matchAll(/^- `([^`]+)`\s*$/gm)) {
12269
12461
  const parsed = parseTrustedCommand(match[1] ?? "");
@@ -12966,7 +13158,7 @@ var ModelRegistry = class {
12966
13158
  };
12967
13159
 
12968
13160
  // src/mcp/client.ts
12969
- import { createHash as createHash11 } from "crypto";
13161
+ import { createHash as createHash12 } from "crypto";
12970
13162
  import { z as z19 } from "zod";
12971
13163
  var McpManager = class {
12972
13164
  #workspace;
@@ -13133,7 +13325,7 @@ function mcpToolName(serverName, remoteName) {
13133
13325
  const safe = remoteName.replace(/[^A-Za-z0-9_-]/g, "_") || "tool";
13134
13326
  const direct = `${prefix}${safe}`;
13135
13327
  if (direct.length <= 64 && safe === remoteName) return direct;
13136
- const suffix = `__${createHash11("sha256").update(remoteName).digest("hex").slice(0, 8)}`;
13328
+ const suffix = `__${createHash12("sha256").update(remoteName).digest("hex").slice(0, 8)}`;
13137
13329
  return `${prefix}${safe.slice(0, Math.max(1, 64 - prefix.length - suffix.length))}${suffix}`;
13138
13330
  }
13139
13331
  function mcpErrorText(content) {
@@ -13199,14 +13391,14 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
13199
13391
  }
13200
13392
 
13201
13393
  // src/auth/oauth.ts
13202
- import { randomBytes as randomBytes2, createHash as createHash13 } from "crypto";
13203
- import { createServer as createServer2 } from "http";
13394
+ import { randomBytes as randomBytes2, createHash as createHash14 } from "crypto";
13395
+ import { createServer as createServer3 } from "http";
13204
13396
  import { URL as URL2, URLSearchParams } from "url";
13205
13397
 
13206
13398
  // src/auth/oauth-config.ts
13207
- import { createHash as createHash12 } from "crypto";
13399
+ import { createHash as createHash13 } from "crypto";
13208
13400
  function oauthCredentialId(tokenUrl, clientId) {
13209
- const digest = createHash12("sha256").update(`${tokenUrl}\0${clientId}`, "utf8").digest("base64url");
13401
+ const digest = createHash13("sha256").update(`${tokenUrl}\0${clientId}`, "utf8").digest("base64url");
13210
13402
  return `oauth:${digest}`;
13211
13403
  }
13212
13404
  function parseOAuthLlmConfig(value) {
@@ -13383,7 +13575,7 @@ var OAuthCallbackAuthProvider = class {
13383
13575
  serverResolve = resolve28;
13384
13576
  serverReject = reject;
13385
13577
  });
13386
- const server = createServer2((req, res) => {
13578
+ const server = createServer3((req, res) => {
13387
13579
  const url = new URL2(req.url ?? "/", `http://${this.#callbackHost}:${server.address().port}`);
13388
13580
  if (url.searchParams.has("error")) {
13389
13581
  const error = url.searchParams.get("error") ?? "access_denied";
@@ -13463,7 +13655,7 @@ var OAuthCallbackAuthProvider = class {
13463
13655
  return randomBytes2(32).toString("base64url");
13464
13656
  }
13465
13657
  #computeCodeChallenge(verifier) {
13466
- return createHash13("sha256").update(verifier).digest("base64url");
13658
+ return createHash14("sha256").update(verifier).digest("base64url");
13467
13659
  }
13468
13660
  #generateState() {
13469
13661
  return randomBytes2(32).toString("base64url");
@@ -13574,7 +13766,7 @@ function isStoredToken(value) {
13574
13766
 
13575
13767
  // src/permissions/policy.ts
13576
13768
  import { readFile as readFile15 } from "fs/promises";
13577
- import { join as join18, resolve as resolve16 } from "path";
13769
+ import { join as join19, resolve as resolve16 } from "path";
13578
13770
  import { z as z20 } from "zod";
13579
13771
  var DecisionSchema2 = z20.enum(["allow", "ask", "deny"]);
13580
13772
  var PrefixSchema = z20.array(z20.string().min(1).max(1024)).min(1).max(64);
@@ -13611,9 +13803,9 @@ var CompiledPermissionPolicy = class {
13611
13803
  async function loadPermissionPolicy(options) {
13612
13804
  const sources = [
13613
13805
  ...options.managedPath === void 0 ? [] : [{ tier: "managed", path: resolve16(options.managedPath) }],
13614
- { tier: "user", path: join18(resolve16(options.home), ".flavor-code", "permissions.json") },
13615
- { tier: "project", path: join18(resolve16(options.workspace), ".flavor", "permissions.json") },
13616
- { tier: "local", path: join18(resolve16(options.workspace), ".flavor", "permissions.local.json") }
13806
+ { tier: "user", path: join19(resolve16(options.home), ".flavor-code", "permissions.json") },
13807
+ { tier: "project", path: join19(resolve16(options.workspace), ".flavor", "permissions.json") },
13808
+ { tier: "local", path: join19(resolve16(options.workspace), ".flavor", "permissions.local.json") }
13617
13809
  ];
13618
13810
  const rules = [];
13619
13811
  for (const source of sources) {
@@ -14382,8 +14574,8 @@ function createSkillResourceTool(registry) {
14382
14574
  // src/session/store.ts
14383
14575
  import { constants as constants5 } from "fs";
14384
14576
  import { lstat as lstat9, mkdir as mkdir14, open as open8, readFile as readFile16, readdir as readdir6, realpath as realpath7, rename as rename6, rm as rm6, stat as stat6 } from "fs/promises";
14385
- import { basename as basename3, dirname as dirname15, join as join19, relative as relative13, resolve as resolve18, sep as sep13 } from "path";
14386
- import { randomUUID as randomUUID11 } from "crypto";
14577
+ import { basename as basename3, dirname as dirname15, join as join20, relative as relative13, resolve as resolve18, sep as sep13 } from "path";
14578
+ import { randomUUID as randomUUID13 } from "crypto";
14387
14579
  import { z as z22 } from "zod";
14388
14580
  var SESSION_VERSION = 4;
14389
14581
  var DEFAULT_MAX_SESSION_BYTES = 5 * 1024 * 1024;
@@ -14509,7 +14701,7 @@ var SessionStore = class {
14509
14701
  #maxSessions;
14510
14702
  constructor(options) {
14511
14703
  this.#workspace = resolve18(options.workspace);
14512
- this.#sessions = join19(this.#workspace, ".flavor", "sessions");
14704
+ this.#sessions = join20(this.#workspace, ".flavor", "sessions");
14513
14705
  this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
14514
14706
  if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
14515
14707
  this.#maxSessions = options.maxSessions ?? 50;
@@ -14541,7 +14733,7 @@ var SessionStore = class {
14541
14733
  const body = `${lines.join("\n")}
14542
14734
  `;
14543
14735
  if (Buffer.byteLength(body) > this.#maxBytes) throw new Error(`Session exceeds maximum size of ${this.#maxBytes} bytes`);
14544
- const temporary = join19(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID11()}.tmp`);
14736
+ const temporary = join20(this.#sessions, `.${document.sessionId}.${process.pid}.${randomUUID13()}.tmp`);
14545
14737
  let handle;
14546
14738
  try {
14547
14739
  handle = await open8(temporary, constants5.O_CREAT | constants5.O_EXCL | constants5.O_WRONLY, 384);
@@ -14658,7 +14850,7 @@ var SessionStore = class {
14658
14850
  const metadata = await lstat9(path);
14659
14851
  if (!metadata.isFile()) throw new Error("Session path is not a regular file");
14660
14852
  await rm6(path);
14661
- await rm6(join19(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
14853
+ await rm6(join20(this.#sessions, `${sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
14662
14854
  } catch (error) {
14663
14855
  if (!isCode7(error, "ENOENT")) throw error;
14664
14856
  }
@@ -14675,7 +14867,7 @@ var SessionStore = class {
14675
14867
  const excess = entries.slice(this.#maxSessions);
14676
14868
  for (const entry2 of excess) {
14677
14869
  await rm6(this.#path(entry2.sessionId), { force: true }).catch(() => void 0);
14678
- await rm6(join19(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
14870
+ await rm6(join20(this.#sessions, `${entry2.sessionId}.events.jsonl`), { force: true }).catch(() => void 0);
14679
14871
  }
14680
14872
  }
14681
14873
  async #assertWorkspace(stored) {
@@ -14697,7 +14889,7 @@ var SessionStore = class {
14697
14889
  if (!isWithin5(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
14698
14890
  }
14699
14891
  async #quarantine(path) {
14700
- const target = `${path}.corrupt-${Date.now()}-${randomUUID11().slice(0, 8)}`;
14892
+ const target = `${path}.corrupt-${Date.now()}-${randomUUID13().slice(0, 8)}`;
14701
14893
  await rename6(path, target);
14702
14894
  return target;
14703
14895
  }
@@ -14917,9 +15109,9 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
14917
15109
 
14918
15110
  // src/sleep/organizer.ts
14919
15111
  import { constants as constants6 } from "fs";
14920
- import { randomUUID as randomUUID12 } from "crypto";
15112
+ import { randomUUID as randomUUID14 } from "crypto";
14921
15113
  import { lstat as lstat10, mkdir as mkdir15, open as open9, readdir as readdir7, realpath as realpath8, rename as rename7, rm as rm7 } from "fs/promises";
14922
- import { isAbsolute as isAbsolute8, join as join20, relative as relative14, resolve as resolve19, sep as sep14 } from "path";
15114
+ import { isAbsolute as isAbsolute8, join as join21, relative as relative14, resolve as resolve19, sep as sep14 } from "path";
14923
15115
  import { z as z23 } from "zod";
14924
15116
  var DateKeySchema = z23.string().regex(/^\d{4}-\d{2}-\d{2}$/);
14925
15117
  var ReviewSchema = z23.object({
@@ -14974,7 +15166,7 @@ var ProjectSleepOrganizer = class {
14974
15166
  #generate;
14975
15167
  constructor(options) {
14976
15168
  this.#workspace = resolve19(options.workspace);
14977
- this.#sleepDirectory = join20(this.#workspace, ".flavor", "sleep");
15169
+ this.#sleepDirectory = join21(this.#workspace, ".flavor", "sleep");
14978
15170
  this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
14979
15171
  this.#generate = options.generate;
14980
15172
  }
@@ -14983,7 +15175,7 @@ var ProjectSleepOrganizer = class {
14983
15175
  signal.throwIfAborted();
14984
15176
  await this.#prepareDirectory();
14985
15177
  if (await this.#hasReport(date)) return { status: "exists", date };
14986
- const lockPath = join20(this.#sleepDirectory, `.${date}.lock`);
15178
+ const lockPath = join21(this.#sleepDirectory, `.${date}.lock`);
14987
15179
  let lock;
14988
15180
  try {
14989
15181
  lock = await open9(lockPath, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
@@ -15018,7 +15210,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
15018
15210
  }
15019
15211
  if (review === void 0) throw lastError;
15020
15212
  const filename2 = `${date}-${filenameSummary(review.title)}.md`;
15021
- const path = join20(this.#sleepDirectory, filename2);
15213
+ const path = join21(this.#sleepDirectory, filename2);
15022
15214
  const markdown = renderSleepReport(date, review, documents);
15023
15215
  await this.#atomicWrite(path, markdown);
15024
15216
  return { status: "written", date, path, sessionCount: documents.length };
@@ -15060,7 +15252,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
15060
15252
  });
15061
15253
  }
15062
15254
  async #prepareDirectory() {
15063
- const flavorDirectory = join20(this.#workspace, ".flavor");
15255
+ const flavorDirectory = join21(this.#workspace, ".flavor");
15064
15256
  await assertNotSymlink(flavorDirectory);
15065
15257
  await mkdir15(flavorDirectory, { recursive: true, mode: 448 });
15066
15258
  await assertNotSymlink(this.#sleepDirectory);
@@ -15074,7 +15266,7 @@ Previous output was invalid. Repair it and return strict JSON only.`;
15074
15266
  return (await readdir7(this.#sleepDirectory, { withFileTypes: true })).some((entry2) => entry2.isFile() && entry2.name.startsWith(prefix) && entry2.name.endsWith(".md"));
15075
15267
  }
15076
15268
  async #atomicWrite(path, content) {
15077
- const temporary = join20(this.#sleepDirectory, `.${randomUUID12()}.tmp`);
15269
+ const temporary = join21(this.#sleepDirectory, `.${randomUUID14()}.tmp`);
15078
15270
  let handle;
15079
15271
  try {
15080
15272
  handle = await open9(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
@@ -15450,7 +15642,7 @@ function computeSessionStats(documents) {
15450
15642
  import { constants as constants7 } from "fs";
15451
15643
  import { mkdir as mkdir16, open as open10, readFile as readFile17, realpath as realpath9, rename as rename8, rm as rm8, stat as stat7 } from "fs/promises";
15452
15644
  import { dirname as dirname16, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve20, sep as sep15 } from "path";
15453
- import { randomUUID as randomUUID13 } from "crypto";
15645
+ import { randomUUID as randomUUID15 } from "crypto";
15454
15646
  import { z as z24 } from "zod";
15455
15647
 
15456
15648
  // src/tools/file-diff.ts
@@ -15803,7 +15995,7 @@ function createPathGuard(workspace) {
15803
15995
  async function atomicWrite(path, content, signal, expectedVersion) {
15804
15996
  abortIfNeeded(signal);
15805
15997
  await mkdir16(dirname16(path), { recursive: true });
15806
- const temporary = `${path}.${randomUUID13()}.tmp`;
15998
+ const temporary = `${path}.${randomUUID15()}.tmp`;
15807
15999
  try {
15808
16000
  const handle = await open10(temporary, constants7.O_CREAT | constants7.O_EXCL | constants7.O_WRONLY, 384);
15809
16001
  try {
@@ -17200,18 +17392,40 @@ function hookAnswersFromUpdatedInput(updatedInput, questions) {
17200
17392
  var QuestionBridge = class {
17201
17393
  #pending;
17202
17394
  #onChange;
17203
- constructor(onChange) {
17395
+ #relay;
17396
+ #active = false;
17397
+ constructor(onChange, relay) {
17204
17398
  this.#onChange = onChange;
17399
+ this.#relay = relay;
17205
17400
  }
17206
17401
  get pending() {
17207
17402
  return this.#pending?.questions;
17208
17403
  }
17209
17404
  ask(questions, signal) {
17210
- if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
17405
+ if (this.#active) return Promise.reject(new Error("A question is already pending"));
17211
17406
  if (signal.aborted) return Promise.reject(signal.reason);
17407
+ this.#active = true;
17408
+ if (this.#relay !== void 0) return this.#askRelayed(questions, signal);
17409
+ return this.#askLocally(questions, signal);
17410
+ }
17411
+ async #askRelayed(questions, signal) {
17412
+ try {
17413
+ const answers = await this.#relay(questions, signal);
17414
+ if (answers !== void 0) {
17415
+ this.#active = false;
17416
+ return answers;
17417
+ }
17418
+ return await this.#askLocally(questions, signal);
17419
+ } catch (error) {
17420
+ this.#active = false;
17421
+ throw error;
17422
+ }
17423
+ }
17424
+ #askLocally(questions, signal) {
17212
17425
  return new Promise((resolve28, reject) => {
17213
17426
  const onAbort = () => {
17214
17427
  this.#pending = void 0;
17428
+ this.#active = false;
17215
17429
  reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
17216
17430
  };
17217
17431
  signal.addEventListener("abort", onAbort, { once: true });
@@ -17220,12 +17434,14 @@ var QuestionBridge = class {
17220
17434
  resolve: (answers) => {
17221
17435
  signal.removeEventListener("abort", onAbort);
17222
17436
  this.#pending = void 0;
17437
+ this.#active = false;
17223
17438
  this.#onChange?.();
17224
17439
  resolve28(answers);
17225
17440
  },
17226
17441
  reject: (error) => {
17227
17442
  signal.removeEventListener("abort", onAbort);
17228
17443
  this.#pending = void 0;
17444
+ this.#active = false;
17229
17445
  this.#onChange?.();
17230
17446
  reject(error);
17231
17447
  }
@@ -17311,9 +17527,9 @@ function createTodoWriteTool() {
17311
17527
  }
17312
17528
 
17313
17529
  // src/tools/managed.ts
17314
- import { randomUUID as randomUUID14 } from "crypto";
17315
- import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile19, readdir as readdir8, unlink as unlink4, writeFile as writeFile10 } from "fs/promises";
17316
- import { dirname as dirname18, join as join21, resolve as resolve22 } from "path";
17530
+ import { randomUUID as randomUUID16 } from "crypto";
17531
+ import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile19, readdir as readdir8, unlink as unlink5, writeFile as writeFile10 } from "fs/promises";
17532
+ import { dirname as dirname18, join as join22, resolve as resolve22 } from "path";
17317
17533
  import { z as z31 } from "zod";
17318
17534
  var MANAGED_TOOL_VERSION = 1;
17319
17535
  var MAX_RECORD_BYTES = 256 * 1024;
@@ -17356,8 +17572,8 @@ var ManagedToolStore = class {
17356
17572
  #diagnostics = [];
17357
17573
  constructor(options) {
17358
17574
  this.#workspace = resolve22(options.workspace);
17359
- this.#projectRoot = join21(this.#workspace, ".flavor", "tools");
17360
- this.#globalRoot = join21(resolve22(options.home), ".flavor-code", "tools");
17575
+ this.#projectRoot = join22(this.#workspace, ".flavor", "tools");
17576
+ this.#globalRoot = join22(resolve22(options.home), ".flavor-code", "tools");
17361
17577
  this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
17362
17578
  }
17363
17579
  get diagnostics() {
@@ -17404,7 +17620,7 @@ var ManagedToolStore = class {
17404
17620
  version: MANAGED_TOOL_VERSION,
17405
17621
  createdAt: this.#now()
17406
17622
  });
17407
- const path = join21(this.directory(input.scope), filename(input.name));
17623
+ const path = join22(this.directory(input.scope), filename(input.name));
17408
17624
  const definition = compileDefinition(record, input.scope, this.#workspace);
17409
17625
  await writeExclusive(path, `${JSON.stringify(record, void 0, 2)}
17410
17626
  `);
@@ -17420,7 +17636,7 @@ var ManagedToolStore = class {
17420
17636
  const entry2 = matches[0];
17421
17637
  const info = await lstat11(entry2.path);
17422
17638
  if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Managed tool path is no longer a regular file: ${entry2.path}`);
17423
- await unlink4(entry2.path);
17639
+ await unlink5(entry2.path);
17424
17640
  this.#entries.delete(scopedKey(entry2.scope, entry2.record.name));
17425
17641
  return summary(entry2, false);
17426
17642
  }
@@ -17429,7 +17645,7 @@ var ManagedToolStore = class {
17429
17645
  if (!parsed.success) return [];
17430
17646
  const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
17431
17647
  if (matches.length > 0) return matches.map((entry2) => entry2.path);
17432
- return [join21(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
17648
+ return [join22(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
17433
17649
  }
17434
17650
  async #loadDirectory(scope2) {
17435
17651
  const root = this.directory(scope2);
@@ -17442,7 +17658,7 @@ var ManagedToolStore = class {
17442
17658
  return;
17443
17659
  }
17444
17660
  for (const name of names) {
17445
- const path = join21(root, name);
17661
+ const path = join22(root, name);
17446
17662
  try {
17447
17663
  const info = await lstat11(path);
17448
17664
  if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
@@ -17598,13 +17814,13 @@ ${sourceUrl}`
17598
17814
  }
17599
17815
  async function writeExclusive(path, content) {
17600
17816
  await mkdir17(dirname18(path), { recursive: true });
17601
- const temporary = `${path}.${randomUUID14()}.tmp`;
17817
+ const temporary = `${path}.${randomUUID16()}.tmp`;
17602
17818
  try {
17603
17819
  await writeFile10(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
17604
17820
  await link2(temporary, path);
17605
17821
  } finally {
17606
17822
  try {
17607
- await unlink4(temporary);
17823
+ await unlink5(temporary);
17608
17824
  } catch {
17609
17825
  }
17610
17826
  }
@@ -17988,7 +18204,7 @@ function buildWarningMessages(report, languageTag) {
17988
18204
  }
17989
18205
 
17990
18206
  // src/hallucination/sliding-window.ts
17991
- import { createHash as createHash14 } from "crypto";
18207
+ import { createHash as createHash15 } from "crypto";
17992
18208
  var SlidingWindow = class {
17993
18209
  #windowSize;
17994
18210
  #threshold;
@@ -18004,7 +18220,7 @@ var SlidingWindow = class {
18004
18220
  }
18005
18221
  hash(toolName, params) {
18006
18222
  const sorted = JSON.stringify(params, sortedKeys(params));
18007
- return createHash14("sha256").update(`${toolName}:${sorted}`).digest("hex");
18223
+ return createHash15("sha256").update(`${toolName}:${sorted}`).digest("hex");
18008
18224
  }
18009
18225
  push(toolName, params) {
18010
18226
  const hash = this.hash(toolName, params);
@@ -18359,9 +18575,9 @@ ${total}`;
18359
18575
  }
18360
18576
 
18361
18577
  // src/memory/store.ts
18362
- import { createHash as createHash15 } from "crypto";
18578
+ import { createHash as createHash16 } from "crypto";
18363
18579
  import { readFile as readFile20, rm as rm9 } from "fs/promises";
18364
- import { resolve as resolve23, join as join22, relative as relative17, sep as sep17 } from "path";
18580
+ import { resolve as resolve23, join as join23, relative as relative17, sep as sep17 } from "path";
18365
18581
 
18366
18582
  // src/memory/similarity.ts
18367
18583
  function normalizeForSimilarity(value) {
@@ -18464,9 +18680,9 @@ var MemoryStore = class {
18464
18680
  if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
18465
18681
  if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
18466
18682
  this.workspace = resolve23(options.workspace);
18467
- this.#memoryRoot = join22(this.workspace, ".flavor", "memory");
18468
- this.path = join22(this.#memoryRoot, "MEMORY.md");
18469
- this.#behaviorPath = join22(this.#memoryRoot, "behavior.json");
18683
+ this.#memoryRoot = join23(this.workspace, ".flavor", "memory");
18684
+ this.path = join23(this.#memoryRoot, "MEMORY.md");
18685
+ this.#behaviorPath = join23(this.#memoryRoot, "behavior.json");
18470
18686
  this.#maxEntries = options.maxEntries;
18471
18687
  this.#maxEntryChars = options.maxEntryChars;
18472
18688
  }
@@ -18845,7 +19061,7 @@ function sanitizeTopicKey(value) {
18845
19061
  return value.normalize("NFKC").toLocaleLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
18846
19062
  }
18847
19063
  function memoryId(type, content) {
18848
- return createHash15("sha256").update(`${type}\0${normalizeMemoryContent(content).toLocaleLowerCase()}`, "utf8").digest("hex").slice(0, 12);
19064
+ return createHash16("sha256").update(`${type}\0${normalizeMemoryContent(content).toLocaleLowerCase()}`, "utf8").digest("hex").slice(0, 12);
18849
19065
  }
18850
19066
  function assertTaskId(taskId) {
18851
19067
  if (!TASK_ID.test(taskId)) throw new Error(`Invalid memory task id: ${taskId}`);
@@ -19161,7 +19377,7 @@ function createExecutionEnvironment(workspace, config) {
19161
19377
 
19162
19378
  // src/ide/client.ts
19163
19379
  import { readFile as readFile21, readdir as readdir9, stat as stat8 } from "fs/promises";
19164
- import { basename as basename5, join as join23, relative as relative18, resolve as resolve24, sep as sep18 } from "path";
19380
+ import { basename as basename5, join as join24, relative as relative18, resolve as resolve24, sep as sep18 } from "path";
19165
19381
  var FlavorIdeClient = class {
19166
19382
  #workspace;
19167
19383
  #home;
@@ -19257,7 +19473,7 @@ var FlavorIdeClient = class {
19257
19473
  };
19258
19474
  if (await this.#healthy(configured)) return configured;
19259
19475
  }
19260
- const directory = join23(this.#home, ".flavor-code", "ide");
19476
+ const directory = join24(this.#home, ".flavor-code", "ide");
19261
19477
  let entries;
19262
19478
  try {
19263
19479
  entries = (await readdir9(directory)).filter((name) => name.endsWith(".lock"));
@@ -19265,7 +19481,7 @@ var FlavorIdeClient = class {
19265
19481
  return void 0;
19266
19482
  }
19267
19483
  const candidates = (await Promise.all(entries.map(async (name) => {
19268
- const path = join23(directory, name);
19484
+ const path = join24(directory, name);
19269
19485
  try {
19270
19486
  const [contents, info] = await Promise.all([readFile21(path, "utf8"), stat8(path)]);
19271
19487
  const lock = parseLockfile(contents);
@@ -19385,7 +19601,7 @@ function relativeDisplay(workspace, filePath) {
19385
19601
  }
19386
19602
 
19387
19603
  // src/jobs/registry.ts
19388
- import { randomUUID as randomUUID15 } from "crypto";
19604
+ import { randomUUID as randomUUID17 } from "crypto";
19389
19605
  var JobRegistry = class {
19390
19606
  #jobs = /* @__PURE__ */ new Map();
19391
19607
  #maxOutputChars;
@@ -19399,7 +19615,7 @@ var JobRegistry = class {
19399
19615
  }
19400
19616
  create(input) {
19401
19617
  this.#pruneCompleted();
19402
- const id = `job-${randomUUID15().slice(0, 12)}`;
19618
+ const id = `job-${randomUUID17().slice(0, 12)}`;
19403
19619
  const now = (/* @__PURE__ */ new Date()).toISOString();
19404
19620
  let settle;
19405
19621
  const done = new Promise((resolve28) => {
@@ -19525,7 +19741,7 @@ function snapshot2(job) {
19525
19741
  }
19526
19742
 
19527
19743
  // src/terminal/service.ts
19528
- import { randomUUID as randomUUID16 } from "crypto";
19744
+ import { randomUUID as randomUUID18 } from "crypto";
19529
19745
  import { createRequire as nodeCreateRequire } from "module";
19530
19746
  import { isAbsolute as isAbsolute11, relative as relative19, resolve as resolve25, sep as sep19 } from "path";
19531
19747
  var TerminalService = class {
@@ -19543,7 +19759,7 @@ var TerminalService = class {
19543
19759
  open(input) {
19544
19760
  const cwd = workspacePath(this.#root, input.cwd ?? ".");
19545
19761
  const shell = input.shell ?? defaultShell();
19546
- const id = `term-${randomUUID16().slice(0, 12)}`;
19762
+ const id = `term-${randomUUID18().slice(0, 12)}`;
19547
19763
  const pty = this.#factory(shell, input.args ?? [], {
19548
19764
  cwd,
19549
19765
  cols: input.columns ?? 100,
@@ -19753,10 +19969,10 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
19753
19969
  }
19754
19970
 
19755
19971
  // src/pals/address.ts
19756
- import { createHash as createHash16 } from "crypto";
19972
+ import { createHash as createHash17 } from "crypto";
19757
19973
  function opaqueUserScope(userScope) {
19758
19974
  if (userScope.length === 0) throw new Error("User scope must not be empty");
19759
- const digest = createHash16("sha256").update(userScope, "utf8").digest("hex").slice(0, 16);
19975
+ const digest = createHash17("sha256").update(userScope, "utf8").digest("hex").slice(0, 16);
19760
19976
  return `u-${digest}`;
19761
19977
  }
19762
19978
  function palSocketAddress(options) {
@@ -19769,7 +19985,7 @@ function palSocketAddress(options) {
19769
19985
  }
19770
19986
 
19771
19987
  // src/pals/client.ts
19772
- import { randomUUID as randomUUID17 } from "crypto";
19988
+ import { randomUUID as randomUUID19 } from "crypto";
19773
19989
  import { Socket as Socket3 } from "net";
19774
19990
  var DEFAULT_REQUEST_TIMEOUT_MS = 5e3;
19775
19991
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -19845,7 +20061,7 @@ var PalClient = class {
19845
20061
  return this.starting;
19846
20062
  }
19847
20063
  async list() {
19848
- const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID17() });
20064
+ const data2 = await this.request({ version: 1, type: "list", requestId: randomUUID19() });
19849
20065
  return PalPresenceListSchema.parse(data2);
19850
20066
  }
19851
20067
  async rename(alias) {
@@ -19858,38 +20074,38 @@ var PalClient = class {
19858
20074
  throw error;
19859
20075
  }
19860
20076
  }
19861
- async sendTask(target, message2, messageId = randomUUID17()) {
19862
- const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID17(), messageId, target, goal: message2 });
20077
+ async sendTask(target, message2, messageId = randomUUID19()) {
20078
+ const data2 = await this.request({ version: 1, type: "task", requestId: randomUUID19(), messageId, target, goal: message2 });
19863
20079
  return DeliveryReceiptSchema.parse(data2);
19864
20080
  }
19865
- async sendChat(target, message2, messageId = randomUUID17()) {
19866
- const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID17(), messageId, target, message: message2 });
20081
+ async sendChat(target, message2, messageId = randomUUID19()) {
20082
+ const data2 = await this.request({ version: 1, type: "chat", requestId: randomUUID19(), messageId, target, message: message2 });
19867
20083
  return DeliveryReceiptSchema.parse(data2);
19868
20084
  }
19869
20085
  async startCoWork(input) {
19870
20086
  const data2 = await this.request({
19871
20087
  version: 1,
19872
20088
  type: "cowork-propose",
19873
- requestId: randomUUID17(),
19874
- coWorkId: input.coWorkId ?? randomUUID17(),
20089
+ requestId: randomUUID19(),
20090
+ coWorkId: input.coWorkId ?? randomUUID19(),
19875
20091
  goal: input.goal,
19876
20092
  participants: input.participants
19877
20093
  });
19878
20094
  return CoWorkSnapshotSchema.parse(data2);
19879
20095
  }
19880
20096
  async coWorkAction(action) {
19881
- const request = { version: 1, requestId: randomUUID17(), ...action };
20097
+ const request = { version: 1, requestId: randomUUID19(), ...action };
19882
20098
  return CoWorkSnapshotSchema.parse(await this.request(request));
19883
20099
  }
19884
20100
  async coWorkStatus(coWorkId) {
19885
- const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID17(), coWorkId });
20101
+ const data2 = await this.request({ version: 1, type: "cowork-get", requestId: randomUUID19(), coWorkId });
19886
20102
  return CoWorkSnapshotSchema.parse(data2);
19887
20103
  }
19888
20104
  async integrateCoWork(input) {
19889
20105
  return this.coWorkAction({ type: "cowork-integration", ...input });
19890
20106
  }
19891
20107
  async cancelCoWork(coWorkId, reason) {
19892
- const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID17(), coWorkId, reason });
20108
+ const data2 = await this.request({ version: 1, type: "cowork-cancel", requestId: randomUUID19(), coWorkId, reason });
19893
20109
  return CoWorkSnapshotSchema.parse(data2);
19894
20110
  }
19895
20111
  subscribe(listener) {
@@ -19901,7 +20117,7 @@ var PalClient = class {
19901
20117
  if (this.registered && this.socket !== void 0) {
19902
20118
  let disconnectTimer;
19903
20119
  await Promise.race([
19904
- this.request({ version: 1, type: "disconnect", requestId: randomUUID17() }).catch(() => void 0),
20120
+ this.request({ version: 1, type: "disconnect", requestId: randomUUID19() }).catch(() => void 0),
19905
20121
  new Promise((resolve28) => {
19906
20122
  disconnectTimer = setTimeout(resolve28, Math.min(this.requestTimeoutMs, 250));
19907
20123
  })
@@ -19992,7 +20208,7 @@ var PalClient = class {
19992
20208
  const data2 = await this.request({
19993
20209
  version: 1,
19994
20210
  type: "register",
19995
- requestId: randomUUID17(),
20211
+ requestId: randomUUID19(),
19996
20212
  authToken: this.authToken,
19997
20213
  presence: { version: 1, ...this.registration, connectedAt: now, lastSeenAt: now }
19998
20214
  });
@@ -20104,7 +20320,7 @@ var PalClient = class {
20104
20320
  if (!this.registered) return;
20105
20321
  if (this.heartbeatPending) return;
20106
20322
  this.heartbeatPending = true;
20107
- void this.request({ version: 1, type: "heartbeat", requestId: randomUUID17() }).catch(() => void 0).finally(() => {
20323
+ void this.request({ version: 1, type: "heartbeat", requestId: randomUUID19() }).catch(() => void 0).finally(() => {
20108
20324
  this.heartbeatPending = false;
20109
20325
  });
20110
20326
  }, this.heartbeatIntervalMs);
@@ -20391,7 +20607,7 @@ var ApprovalBridge = class {
20391
20607
  request(request, signal = new AbortController().signal) {
20392
20608
  if (this.#settle !== void 0) return Promise.resolve("deny");
20393
20609
  if (signal.aborted) return Promise.resolve("deny");
20394
- this.#pending = { id: randomUUID18(), ...request };
20610
+ this.#pending = { id: randomUUID20(), ...request };
20395
20611
  this.#onChange?.();
20396
20612
  return new Promise((resolvePromise) => {
20397
20613
  this.#settle = resolvePromise;
@@ -20510,7 +20726,20 @@ async function createProductionRuntime(options) {
20510
20726
  environment.OPENAI_API_KEY,
20511
20727
  environment.ANTHROPIC_API_KEY
20512
20728
  ].filter((value) => typeof value === "string" && value.length > 0);
20513
- const hooks = new HookBus();
20729
+ let hookSessionId;
20730
+ let islandControlMetadata;
20731
+ const hooks = new HookBus({
20732
+ eventContext: () => ({
20733
+ protocolVersion: 2,
20734
+ workspace,
20735
+ ...hookSessionId === void 0 ? {} : { sessionId: hookSessionId },
20736
+ ...islandControlMetadata === void 0 ? {} : {
20737
+ islandControlEndpoint: islandControlMetadata.endpoint,
20738
+ islandControlToken: islandControlMetadata.token,
20739
+ islandControlCapabilities: islandControlMetadata.capabilities
20740
+ }
20741
+ })
20742
+ });
20514
20743
  hooks.on(
20515
20744
  "PostToolUseFailure",
20516
20745
  createIncidentReporter({
@@ -20530,8 +20759,7 @@ async function createProductionRuntime(options) {
20530
20759
  diagnostics.push(...permissionPolicy.diagnostics);
20531
20760
  const approvals = new ApprovalBridge(options.onApprovalChange);
20532
20761
  const resolveToolApproval = options.approvalPolicy === "deny" && options.rpcToolApprovals !== true ? () => "deny" : (request, signal) => approvals.request(request, signal);
20533
- const questions = new QuestionBridge(options.onApprovalChange);
20534
- const askUserQuestionHandler = async (qs, signal) => {
20762
+ const relayQuestions = async (qs, signal) => {
20535
20763
  if (options.approvalPolicy === "deny") throw new Error("AskUserQuestion is not available in non-interactive mode");
20536
20764
  if (hooks.hasListeners("PermissionRequest")) {
20537
20765
  let decision;
@@ -20549,8 +20777,10 @@ async function createProductionRuntime(options) {
20549
20777
  const hookAnswers = hookAnswersFromUpdatedInput(decision.updatedInput, qs);
20550
20778
  if (hookAnswers !== void 0) return hookAnswers;
20551
20779
  }
20552
- return questions.ask(qs, signal);
20780
+ return void 0;
20553
20781
  };
20782
+ const questions = new QuestionBridge(options.onApprovalChange, relayQuestions);
20783
+ const askUserQuestionHandler = (qs, signal) => questions.ask(qs, signal);
20554
20784
  const executionEnvironment = createExecutionEnvironment(workspace, config.execution);
20555
20785
  const jobs = new JobRegistry();
20556
20786
  const terminals = new TerminalService(workspace, { jobs });
@@ -20622,8 +20852,8 @@ async function createProductionRuntime(options) {
20622
20852
  const registeredProviders = registration.registered;
20623
20853
  let effectiveLlm = registration.effectiveLlm;
20624
20854
  const pluginHost = new PluginHost({
20625
- globalPluginDirs: [join24(home, ".flavor-code", "plugins")],
20626
- projectPluginDirs: [join24(workspace, ".flavor", "plugins")],
20855
+ globalPluginDirs: [join25(home, ".flavor-code", "plugins")],
20856
+ projectPluginDirs: [join25(workspace, ".flavor", "plugins")],
20627
20857
  config,
20628
20858
  // Existing project plugins (including astgraph and superharness) use Node.js
20629
20859
  // built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
@@ -20697,6 +20927,7 @@ async function createProductionRuntime(options) {
20697
20927
  const pendingCollaborationEvents = [];
20698
20928
  let collaborationEventPump;
20699
20929
  let collaborationEventsOpen = true;
20930
+ let islandControlServer;
20700
20931
  const pumpCollaborationEvents = () => {
20701
20932
  if (collaborationEventPump !== void 0 || collaborationSession === void 0 || deliverCollaborationEvent === void 0) return;
20702
20933
  collaborationEventPump = (async () => {
@@ -20730,7 +20961,7 @@ async function createProductionRuntime(options) {
20730
20961
  const address = palSocketAddress({
20731
20962
  platform: process.platform,
20732
20963
  userScope: home,
20733
- runtimeDir: join24(tmpdir3(), `flavor-code-pals-${createHash17("sha256").update(home).digest("hex").slice(0, 16)}`)
20964
+ runtimeDir: join25(tmpdir4(), `flavor-code-pals-${createHash18("sha256").update(home).digest("hex").slice(0, 16)}`)
20734
20965
  });
20735
20966
  const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
20736
20967
  instanceId: collaboration.instanceId,
@@ -20841,8 +21072,8 @@ async function createProductionRuntime(options) {
20841
21072
  diagnostics.push(`MCP servers could not start: ${message(error)}`);
20842
21073
  });
20843
21074
  const skills = new SkillRegistry({
20844
- globalRoots: [join24(home, ".flavor-code", "skills")],
20845
- projectRoots: [join24(workspace, ".flavor", "skills"), ...pluginSkillRoots],
21075
+ globalRoots: [join25(home, ".flavor-code", "skills")],
21076
+ projectRoots: [join25(workspace, ".flavor", "skills"), ...pluginSkillRoots],
20846
21077
  authorizeResource: async () => true,
20847
21078
  disabledNames: config.skills.disabled
20848
21079
  });
@@ -20850,7 +21081,7 @@ async function createProductionRuntime(options) {
20850
21081
  skillsReady.catch(() => void 0);
20851
21082
  tools.push(createSkillTool(skills), createSkillResourceTool(skills));
20852
21083
  if (options.extraTools !== void 0) tools.push(...options.extraTools);
20853
- const flavor = await optionalText2(join24(workspace, "FLAVOR.md"));
21084
+ const flavor = await optionalText2(join25(workspace, "FLAVOR.md"));
20854
21085
  const instructionBaseline = await workspaceInstructions.baseline();
20855
21086
  let memoryContext;
20856
21087
  if (memoryStore !== void 0) {
@@ -20874,7 +21105,8 @@ async function createProductionRuntime(options) {
20874
21105
  let taskResults = { ...recovered?.tasks.results ?? {} };
20875
21106
  const subagentStartedAt = {};
20876
21107
  const subagentElapsedMs = {};
20877
- let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`;
21108
+ let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`;
21109
+ hookSessionId = sessionId;
20878
21110
  let harnessJournal = new HarnessJournal({ workspace, sessionId });
20879
21111
  const harnessRecovery = harnessJournal.recover();
20880
21112
  if (harnessRecovery.queue.length > 0 || harnessRecovery.incompleteTools.length > 0 || harnessRecovery.incompleteModelIds.length > 0 || harnessRecovery.interruptedTurnIds.length > 0) {
@@ -20958,7 +21190,13 @@ async function createProductionRuntime(options) {
20958
21190
  const publishTaskState = async () => {
20959
21191
  harness.main.context.updateTaskState(serializedTaskState());
20960
21192
  await persist();
20961
- emitOutput({ type: "tasks", snapshot: taskSnapshot() });
21193
+ const snapshot4 = taskSnapshot();
21194
+ emitOutput({ type: "tasks", snapshot: snapshot4 });
21195
+ await hooks.emit({
21196
+ version: 1,
21197
+ type: "Notification",
21198
+ payload: { kind: "task_snapshot", taskSnapshot: snapshot4 }
21199
+ }).catch(() => void 0);
20962
21200
  };
20963
21201
  for (const tool of createTaskPlanTools({
20964
21202
  getPlan: () => taskPlan,
@@ -21377,7 +21615,7 @@ async function createProductionRuntime(options) {
21377
21615
  evolveService.toolDefinition(),
21378
21616
  ...mcpTools
21379
21617
  ];
21380
- const loopFlavor = await optionalText2(join24(input.workspace, "FLAVOR.md"));
21618
+ const loopFlavor = await optionalText2(join25(input.workspace, "FLAVOR.md"));
21381
21619
  const loopInstructionBaseline = await loopInstructions.baseline();
21382
21620
  const loopEnvironment = createPromptEnvironment({
21383
21621
  now: /* @__PURE__ */ new Date(),
@@ -21516,7 +21754,7 @@ async function createProductionRuntime(options) {
21516
21754
  return answers[0] === "Continue" ? "approved" : "rejected";
21517
21755
  },
21518
21756
  fingerprint: workspaceFingerprint,
21519
- idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`
21757
+ idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`
21520
21758
  });
21521
21759
  const goalStore = new GoalStore({ workspace });
21522
21760
  const goalOrchestrator = new GoalOrchestrator({
@@ -21535,7 +21773,7 @@ async function createProductionRuntime(options) {
21535
21773
  executionEnvironment
21536
21774
  ),
21537
21775
  now: () => (/* @__PURE__ */ new Date()).toISOString(),
21538
- idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`,
21776
+ idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`,
21539
21777
  runWorker: ({ workspace: goalWorkspace, prompt, signal }) => runLoopWorker({ workspace: goalWorkspace, prompt, signal })
21540
21778
  });
21541
21779
  let sessionHistory = await SessionHistory.open({
@@ -21648,7 +21886,7 @@ async function createProductionRuntime(options) {
21648
21886
  runExplicitSkill(harness, skills, skill, prompt, signal, selectedModels.mainError),
21649
21887
  persist
21650
21888
  ),
21651
- runLoop: (goal, signal) => runLoopSession(loopOrchestrator, goal, signal),
21889
+ runLoop: (goal, signal) => runLoopSession(loopOrchestrator, hooks, goal, signal),
21652
21890
  runGoal: (goal, signal) => persistEach(runGoalSession(goalOrchestrator, goal, signal), persist),
21653
21891
  mcp: async (command, signal) => {
21654
21892
  signal.throwIfAborted();
@@ -21806,7 +22044,8 @@ ${summary2}`;
21806
22044
  for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
21807
22045
  for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
21808
22046
  const previousIdeSessionId = ideSessionId;
21809
- sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID18().slice(0, 8)}`;
22047
+ sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID20().slice(0, 8)}`;
22048
+ hookSessionId = sessionId;
21810
22049
  harnessJournal = new HarnessJournal({ workspace, sessionId });
21811
22050
  setUsageSession(sessionId);
21812
22051
  ideSessionId = sessionId;
@@ -21906,7 +22145,7 @@ ${renderMemoryDocument(entries)}`;
21906
22145
  return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
21907
22146
  }
21908
22147
  try {
21909
- const tokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
22148
+ const tokenStore = createFileTokenStore(join25(home, ".flavor-code", "auth.json"));
21910
22149
  const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
21911
22150
  await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
21912
22151
  const oauth = new OAuthCallbackAuthProvider({
@@ -21949,7 +22188,7 @@ ${renderMemoryDocument(entries)}`;
21949
22188
  }
21950
22189
  },
21951
22190
  async logout() {
21952
- const tokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
22191
+ const tokenStore = createFileTokenStore(join25(home, ".flavor-code", "auth.json"));
21953
22192
  const tokens = await tokenStore.load();
21954
22193
  const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
21955
22194
  if (!hadCredentials) {
@@ -22006,6 +22245,22 @@ ${renderMemoryDocument(entries)}`;
22006
22245
  sleepScheduler.start();
22007
22246
  }
22008
22247
  const session = new FlavorSession(services);
22248
+ if (pluginHost.loadedPlugins.some((plugin) => plugin.name === "flavor-island")) {
22249
+ try {
22250
+ islandControlServer = await createIslandControlServer({
22251
+ sessionId,
22252
+ session,
22253
+ ...options.islandControl?.focus === void 0 ? {} : { focus: options.islandControl.focus }
22254
+ });
22255
+ islandControlMetadata = {
22256
+ endpoint: islandControlServer.endpoint,
22257
+ token: islandControlServer.token,
22258
+ capabilities: islandControlServer.capabilities
22259
+ };
22260
+ } catch (error) {
22261
+ diagnostics.push(`Flavor Island control channel unavailable: ${message(error)}`);
22262
+ }
22263
+ }
22009
22264
  collaborationSession = session;
22010
22265
  pumpCollaborationEvents();
22011
22266
  const authorization = {
@@ -22041,6 +22296,7 @@ ${renderMemoryDocument(entries)}`;
22041
22296
  if (persistTail !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "persist-tail", persistTail);
22042
22297
  if (ideSessionId !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "ide-end-session", ide.endSession(ideSessionId));
22043
22298
  if (executionEnvironment !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "execution-environment", executionEnvironment.dispose());
22299
+ if (islandControlServer !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "island-control", islandControlServer.close());
22044
22300
  terminals.dispose();
22045
22301
  await boundedStep(stepTimeoutMs, diagnostics, "jobs", jobs.dispose());
22046
22302
  auditLogger.close();
@@ -22054,6 +22310,7 @@ ${renderMemoryDocument(entries)}`;
22054
22310
  collaborationEventsOpen = false;
22055
22311
  mcpDiscarded = true;
22056
22312
  memoryReviews.dispose();
22313
+ await islandControlServer?.close().catch(() => void 0);
22057
22314
  try {
22058
22315
  const stepTimeoutMs = options.shutdownStepTimeoutMs ?? DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS;
22059
22316
  if (ideSessionId !== void 0) await boundedStep(stepTimeoutMs, diagnostics, "ide-end-session", ide.endSession(ideSessionId));
@@ -22105,9 +22362,10 @@ function formatMcpReconnect(server) {
22105
22362
  if (server.status === "disabled") return `MCP server "${server.name}" is disabled. Enable it before reconnecting.`;
22106
22363
  return `Failed to reconnect MCP server "${server.name}": ${server.error ?? "unknown error"}`;
22107
22364
  }
22108
- async function* runLoopSession(orchestrator, goal, signal) {
22365
+ async function* runLoopSession(orchestrator, hooks, goal, signal) {
22109
22366
  let totalInputTokens = 0;
22110
22367
  let totalOutputTokens = 0;
22368
+ let latestVerification;
22111
22369
  for await (const event of orchestrator.run({ goal, signal })) {
22112
22370
  if (event.type === "worker-event") {
22113
22371
  if (event.event.type === "usage") {
@@ -22121,8 +22379,19 @@ async function* runLoopSession(orchestrator, goal, signal) {
22121
22379
  } else if (event.event.type !== "done") yield event.event;
22122
22380
  continue;
22123
22381
  }
22382
+ if (event.type === "loop-verification") latestVerification = event.evidence;
22124
22383
  yield loopProgressEvent(event);
22125
22384
  if (event.type === "loop-terminal") {
22385
+ await hooks.emit({
22386
+ version: 1,
22387
+ type: "LoopEnd",
22388
+ payload: {
22389
+ loopId: event.loopId,
22390
+ outcome: event.status,
22391
+ reason: event.reason,
22392
+ verification: latestVerification ?? null
22393
+ }
22394
+ }).catch(() => void 0);
22126
22395
  yield { type: "done", usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens } };
22127
22396
  }
22128
22397
  }
@@ -22179,7 +22448,7 @@ function terminalProgressState(status) {
22179
22448
  return "failed";
22180
22449
  }
22181
22450
  async function workspaceFingerprint(workspace) {
22182
- const hash = createHash17("sha256");
22451
+ const hash = createHash18("sha256");
22183
22452
  const diff = await execFileNoThrow(
22184
22453
  "git",
22185
22454
  ["-C", workspace, "diff", "--no-ext-diff", "--binary", "HEAD"],
@@ -22414,7 +22683,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
22414
22683
  const configured = { ...providers };
22415
22684
  if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
22416
22685
  if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
22417
- const oauthTokenStore = createFileTokenStore(join24(home, ".flavor-code", "auth.json"));
22686
+ const oauthTokenStore = createFileTokenStore(join25(home, ".flavor-code", "auth.json"));
22418
22687
  const registered = [];
22419
22688
  let effectiveLlm;
22420
22689
  for (const [name, provider] of Object.entries(configured)) {
@@ -22833,10 +23102,10 @@ async function* runGoalSession(orchestrator, goal, signal) {
22833
23102
  }
22834
23103
  function memoryTranscriptHash(messages) {
22835
23104
  const visible = messages.filter((item) => item.role === "user" || item.role === "assistant").map((item) => `${item.role}\0${modelContentText(item.content).trim()}`).join("\n");
22836
- return createHash17("sha256").update(visible, "utf8").digest("hex");
23105
+ return createHash18("sha256").update(visible, "utf8").digest("hex");
22837
23106
  }
22838
23107
  function createMemoryTaskId() {
22839
- return `memory-${randomUUID18()}`;
23108
+ return `memory-${randomUUID20()}`;
22840
23109
  }
22841
23110
 
22842
23111
  export {