flavor-code 1.2.15 → 1.2.17

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.
@@ -15,7 +15,7 @@ export interface AgentRunRequest {
15
15
  /** Messages supplied while a tool batch is running, injected before the next model call. */
16
16
  getSteeringMessages?(): readonly string[];
17
17
  }
18
- export type AgentErrorCode = ProviderErrorCode | "iteration_limit" | "incomplete_stream";
18
+ export type AgentErrorCode = ProviderErrorCode | "iteration_limit" | "incomplete_stream" | "empty_response";
19
19
  export interface AgentError {
20
20
  code: AgentErrorCode;
21
21
  message: string;
@@ -15,7 +15,7 @@ import {
15
15
  packageVersion,
16
16
  redactErrorText,
17
17
  transcriptReducer
18
- } from "./chunk-5TG3E2EJ.js";
18
+ } from "./chunk-QQCXJC4U.js";
19
19
  import "./chunk-IG4Z23CL.js";
20
20
  import {
21
21
  Box_default,
@@ -12,4 +12,11 @@ export interface OAuthTokenStore {
12
12
  load(): Promise<Record<string, StoredToken>>;
13
13
  save(tokens: Record<string, StoredToken>): Promise<void>;
14
14
  }
15
+ /**
16
+ * Reduce the token store to a single credential. `/login` is a switch-to
17
+ * operation: keeping tokens from earlier services made the runtime pick the
18
+ * active provider by registration order, so the welcome card could show a
19
+ * stale service (e.g. a previous "Token Plan") instead of the one just logged in.
20
+ */
21
+ export declare function retainOnlyCredentials(store: OAuthTokenStore, keepCredentialId: string): Promise<void>;
15
22
  export declare function createFileTokenStore(filePath: string): OAuthTokenStore;
@@ -2900,6 +2900,7 @@ var MVP_COMMANDS = [
2900
2900
  "init",
2901
2901
  "config",
2902
2902
  "login",
2903
+ "logout",
2903
2904
  "permissions",
2904
2905
  "skills",
2905
2906
  "plugins",
@@ -2937,6 +2938,7 @@ var COMMAND_DESCRIPTIONS = {
2937
2938
  init: "Initialize Flavor project files",
2938
2939
  config: "Show the resolved configuration",
2939
2940
  login: "Authenticate via OAuth PKCE",
2941
+ logout: "Clear stored OAuth credentials and restore configured models",
2940
2942
  permissions: "Change the tool permission mode",
2941
2943
  skills: "List discovered skills",
2942
2944
  plugins: "List loaded plugins",
@@ -3214,6 +3216,7 @@ var HELP = [
3214
3216
  "/model <main|subagent> <provider:model> switch any configured model",
3215
3217
  "/permissions <default|acceptEdits|plan|bypassPermissions|auto|bubble>",
3216
3218
  "/login authenticate via OAuth PKCE",
3219
+ "/logout clear stored OAuth credentials",
3217
3220
  "/init /config /skills /plugins /hooks /tasks",
3218
3221
  "/memory /remember [type] <text> /forget <text-or-id> /forget-cold /finish",
3219
3222
  "/checkpoint [label] /tree /rewind <node> /unrevert /fork <node>",
@@ -3628,6 +3631,8 @@ var FlavorSession = class {
3628
3631
  } else if (command.name === "login") {
3629
3632
  this.#notice("Opening browser for authentication...");
3630
3633
  this.#notice(await this.#services.login());
3634
+ } else if (command.name === "logout") {
3635
+ this.#notice(await this.#services.logout());
3631
3636
  } else if (command.name === "help") this.#notice(HELP);
3632
3637
  else if (command.name === "exit") this.#services.output({ type: "exit" });
3633
3638
  }
@@ -6534,7 +6539,8 @@ var RECOVERABLE_MODEL_ERRORS = /* @__PURE__ */ new Set([
6534
6539
  "network",
6535
6540
  "rate_limit",
6536
6541
  "unknown",
6537
- "incomplete_stream"
6542
+ "incomplete_stream",
6543
+ "empty_response"
6538
6544
  ]);
6539
6545
  function envMaxIterations() {
6540
6546
  const raw = process.env["FLAVOR_MAX_ITERATIONS"];
@@ -6728,6 +6734,12 @@ var AgentLoop = class {
6728
6734
  } finally {
6729
6735
  if (!completed && terminalError === void 0) {
6730
6736
  terminalError = { code: "incomplete_stream", message: "Provider stream ended without a done or error event" };
6737
+ } else if (completed && assistantText.trim().length === 0 && collectedToolCalls.length === 0) {
6738
+ completed = false;
6739
+ terminalError = {
6740
+ code: "empty_response",
6741
+ message: "Provider completed without visible text or tool calls"
6742
+ };
6731
6743
  }
6732
6744
  providerError = terminalError !== void 0;
6733
6745
  try {
@@ -6767,7 +6779,7 @@ var AgentLoop = class {
6767
6779
  totalCacheCreationTokens += usage.cacheCreationTokens ?? 0;
6768
6780
  yield { type: "usage", ...usage, totalInputTokens, totalOutputTokens };
6769
6781
  }
6770
- const providerProducedOutput = assistantText.length > 0 || collectedToolCalls.length > 0;
6782
+ const providerProducedOutput = assistantText.trim().length > 0 || collectedToolCalls.length > 0;
6771
6783
  if (terminalError?.code === "context_overflow" && !reactiveRetried && !providerProducedOutput && attempt < maxAttempts) {
6772
6784
  let compacted = false;
6773
6785
  try {
@@ -10646,7 +10658,7 @@ import { readFile as readFile11 } from "fs/promises";
10646
10658
  import { join as join16 } from "path";
10647
10659
 
10648
10660
  // src/tools/shell.ts
10649
- import { spawn as spawn6 } from "child_process";
10661
+ import { spawn as spawn6, spawnSync } from "child_process";
10650
10662
  import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve13, sep as sep10 } from "path";
10651
10663
  import { z as z16 } from "zod";
10652
10664
 
@@ -10776,6 +10788,29 @@ function tokenizeCommandLine(command) {
10776
10788
  function unquoteToken(token) {
10777
10789
  return token.length >= 2 && token.startsWith('"') && token.endsWith('"') ? token.slice(1, -1) : token;
10778
10790
  }
10791
+ var cachedWindowsShell;
10792
+ function hasExecutable(name) {
10793
+ try {
10794
+ return spawnSync("where", [name], { stdio: "ignore", windowsHide: true }).status === 0;
10795
+ } catch {
10796
+ return false;
10797
+ }
10798
+ }
10799
+ function detectWindowsShell() {
10800
+ if (cachedWindowsShell !== void 0) return cachedWindowsShell;
10801
+ cachedWindowsShell = hasExecutable("pwsh") ? "pwsh" : hasExecutable("powershell") ? "powershell" : "cmd";
10802
+ return cachedWindowsShell;
10803
+ }
10804
+ function psQuote(token) {
10805
+ return `'${token.replace(/'/g, "''")}'`;
10806
+ }
10807
+ function buildWindowsCommandLine(kind, commandLine, command, args) {
10808
+ if (kind === "cmd") return `chcp 65001>nul & ${commandLine}`;
10809
+ const executable = kind === "pwsh" ? "pwsh" : "powershell";
10810
+ const script = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map(psQuote).join(" ")}` : ""}; exit $LASTEXITCODE`;
10811
+ const encoded = Buffer.from(script, "utf16le").toString("base64");
10812
+ return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
10813
+ }
10779
10814
  function createShellTool(workspace, options = {}) {
10780
10815
  const root = resolve13(workspace);
10781
10816
  const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
@@ -10896,7 +10931,7 @@ async function executeShell(root, input, cancellation, maxBytes, onOutput) {
10896
10931
  const quoteArg = (s) => s.includes(" ") ? `"${s.replace(/"/g, '""')}"` : s;
10897
10932
  const quoted = input.args.map(quoteArg).join(" ");
10898
10933
  const commandLine = quoted.length > 0 ? `${quoteArg(input.command)} ${quoted}` : quoteArg(input.command);
10899
- const shellCommandLine = process.platform === "win32" ? `chcp 65001>nul & ${commandLine}` : commandLine;
10934
+ const shellCommandLine = process.platform === "win32" ? buildWindowsCommandLine(detectWindowsShell(), commandLine, input.command, input.args) : commandLine;
10900
10935
  const child = spawn6(shellCommandLine, {
10901
10936
  cwd,
10902
10937
  shell: true,
@@ -12435,6 +12470,11 @@ function escapeHtml(text) {
12435
12470
 
12436
12471
  // src/auth/store.ts
12437
12472
  import { dirname as dirname13 } from "path";
12473
+ async function retainOnlyCredentials(store, keepCredentialId) {
12474
+ const tokens = await store.load();
12475
+ const retained = Object.fromEntries(Object.entries(tokens).filter(([id]) => id === keepCredentialId));
12476
+ await store.save(retained);
12477
+ }
12438
12478
  function createFileTokenStore(filePath) {
12439
12479
  let baseline = {};
12440
12480
  const store = {
@@ -19464,7 +19504,10 @@ async function createProductionRuntime(options) {
19464
19504
  states: { ...taskStates },
19465
19505
  results: { ...taskResults }
19466
19506
  },
19467
- models: { main: harness.mainModelId, subagent: harness.subagentModelId },
19507
+ // Persist the resolved decision, not the harness snapshot: after logout the
19508
+ // harness may still hold the old login model, and resuming from that stale
19509
+ // value is what made the welcome card show a previous service.
19510
+ models: { main: mainModel, subagent: childModel },
19468
19511
  permissionMode: harness.permissionMode,
19469
19512
  memory: memoryLifecycle,
19470
19513
  timeline: { version: 1, state: timelineState }
@@ -19719,7 +19762,7 @@ async function createProductionRuntime(options) {
19719
19762
  const messages = allMessages.slice(memoryLifecycle.messageStart ?? 0);
19720
19763
  const transcriptHash = memoryTranscriptHash(messages);
19721
19764
  if (memoryLifecycle.status === "completed" && memoryLifecycle.transcriptHash === transcriptHash) {
19722
- return "Task was already completed and evaluated for long-term memory.";
19765
+ return "This conversation segment was already evaluated for long-term memory.";
19723
19766
  }
19724
19767
  if (!manual && memoryBehavior.autoExtractPaused) {
19725
19768
  return "Automatic long-term-memory extraction is paused after repeated dismissals; use /finish, /remember, or an explicit \u201Cremember\u201D request to store memory manually.";
@@ -19727,7 +19770,7 @@ async function createProductionRuntime(options) {
19727
19770
  autoStoredContents.length = 0;
19728
19771
  const finalization = memoryCoordinator === void 0 || !config.memory.autoExtract || options.approvalPolicy === "deny" ? { evaluated: true, candidates: false, stored: 0 } : await memoryCoordinator.finalize(memoryLifecycle.taskId ?? sessionId, messages);
19729
19772
  if (!finalization.evaluated) {
19730
- return "Task memory evaluation failed and was not marked complete; retry /finish after checking diagnostics.";
19773
+ return "Long-term-memory evaluation failed; retry /finish after checking diagnostics.";
19731
19774
  }
19732
19775
  memoryLifecycle = {
19733
19776
  status: "completed",
@@ -19739,9 +19782,9 @@ async function createProductionRuntime(options) {
19739
19782
  await persist();
19740
19783
  if (finalization.candidates && finalization.stored > 0) {
19741
19784
  const storedText = autoStoredContents[0] ?? "a high-confidence memory";
19742
- return `Task completed. Stored high-confidence long-term memory directly: "${storedText}". Run /forget to remove it if undesired.`;
19785
+ return `Long-term memory updated. Stored high-confidence entry: "${storedText}". Run /forget to remove it if undesired.`;
19743
19786
  }
19744
- return finalization.candidates ? "Task completed. Review the generated long-term-memory candidates before anything is stored." : "Task completed; no durable long-term-memory candidates passed the threshold.";
19787
+ return finalization.candidates ? "Long-term-memory evaluation completed. Review the generated candidates before anything is stored." : "Long-term-memory evaluation completed; no durable candidates passed the threshold.";
19745
19788
  };
19746
19789
  let explicitMemoryRequest;
19747
19790
  let automaticMemoryTask = false;
@@ -19752,10 +19795,10 @@ async function createProductionRuntime(options) {
19752
19795
  timelineState = transcriptReducer(timelineState, { type: "submit", prompt });
19753
19796
  automaticMemoryTask = !prompt.startsWith("/") && harness.permissionProfile !== "d2c";
19754
19797
  if (automaticMemoryTask) memoryReviews.dismissAll();
19755
- if (!prompt.startsWith("/") && memoryLifecycle.status === "completed") {
19798
+ if (!prompt.startsWith("/")) {
19756
19799
  memoryLifecycle = {
19757
19800
  status: "active",
19758
- taskId: createMemoryTaskId(),
19801
+ taskId: memoryLifecycle.status === "completed" ? createMemoryTaskId() : memoryLifecycle.taskId ?? createMemoryTaskId(),
19759
19802
  messageStart: harness.main.context.snapshot().messages.length
19760
19803
  };
19761
19804
  }
@@ -19797,9 +19840,9 @@ async function createProductionRuntime(options) {
19797
19840
  }
19798
19841
  if (explicit === void 0 && automatic && event.payload.outcome === "completed" && memoryCoordinator !== void 0 && config.memory.autoExtract && options.approvalPolicy !== "deny" && !memoryBehavior.autoExtractPaused) {
19799
19842
  const result = await finalizeMemoryTask();
19800
- if (result.startsWith("Task memory evaluation failed")) {
19843
+ if (result.startsWith("Long-term-memory evaluation failed")) {
19801
19844
  emitOutput({ type: "notice", message: "Automatic long-term-memory evaluation failed; use /finish to retry after checking diagnostics." });
19802
- } else if (result.startsWith("Task completed. Review") || result.startsWith("Task completed. Stored")) {
19845
+ } else if (result.startsWith("Long-term-memory evaluation completed. Review") || result.startsWith("Long-term memory updated.")) {
19803
19846
  emitOutput({ type: "notice", message: result });
19804
19847
  }
19805
19848
  }
@@ -20105,8 +20148,8 @@ async function createProductionRuntime(options) {
20105
20148
  const services = {
20106
20149
  hooks,
20107
20150
  workspace,
20108
- mainModel: () => harness.mainModelId,
20109
- subagentModel: () => harness.subagentModelId,
20151
+ mainModel: () => mainModel,
20152
+ subagentModel: () => childModel,
20110
20153
  llmServiceName: () => effectiveLlm?.serviceName,
20111
20154
  permissionMode: () => harness.permissionMode,
20112
20155
  ...palServices === void 0 ? {} : { pals: palServices },
@@ -20180,6 +20223,8 @@ async function createProductionRuntime(options) {
20180
20223
  }
20181
20224
  }
20182
20225
  harness.setModel(role, id);
20226
+ if (role === "main") mainModel = id;
20227
+ else childModel = id;
20183
20228
  await persist();
20184
20229
  },
20185
20230
  setPermissionMode: async (mode) => {
@@ -20426,12 +20471,48 @@ ${renderMemoryDocument(entries)}`;
20426
20471
  goalOrchestrator.setModels(mainModel, mainModel);
20427
20472
  delete selectedModels.mainError;
20428
20473
  delete selectedModels.childError;
20474
+ await retainOnlyCredentials(tokenStore, credentialId);
20429
20475
  if (!secrets.includes(next.accessToken)) secrets.push(next.accessToken);
20430
20476
  await persist();
20431
20477
  return `Authenticated to "${next.serviceName}". Main model ${mainModel}; subagent ${childModel}; gateway ${next.baseURL}. Configuration is active now.`;
20432
20478
  } catch (error) {
20433
20479
  return `Login failed: ${message(error)}`;
20434
20480
  }
20481
+ },
20482
+ async logout() {
20483
+ const tokenStore = createFileTokenStore(join22(home, ".flavor-code", "auth.json"));
20484
+ const tokens = await tokenStore.load();
20485
+ const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
20486
+ if (!hadCredentials) {
20487
+ return "Not authenticated \u2014 no stored OAuth credentials to clear.";
20488
+ }
20489
+ for (let i = registeredProviders.length - 1; i >= 0; i -= 1) {
20490
+ const provider = registeredProviders[i];
20491
+ if (provider !== void 0 && provider.pkceManaged === true) {
20492
+ registry.unregister(provider.name);
20493
+ registeredProviders.splice(i, 1);
20494
+ }
20495
+ }
20496
+ if (effectiveLlm !== void 0) {
20497
+ registry.unregister(effectiveLlm.providerId);
20498
+ effectiveLlm = void 0;
20499
+ }
20500
+ await tokenStore.save({});
20501
+ const fallback = selectModels(config, registeredProviders, diagnostics);
20502
+ mainModel = fallback.main;
20503
+ childModel = fallback.child;
20504
+ if (fallback.mainError === void 0) delete selectedModels.mainError;
20505
+ else selectedModels.mainError = fallback.mainError;
20506
+ if (fallback.childError === void 0) delete selectedModels.childError;
20507
+ else selectedModels.childError = fallback.childError;
20508
+ if (fallback.mainError === void 0 && registry.has(safeProvider(mainModel)) && registry.has(safeProvider(childModel))) {
20509
+ harness.setModel("main", mainModel);
20510
+ harness.setModel("subagent", childModel);
20511
+ hallucinationGuard.setModel(childModel);
20512
+ goalOrchestrator.setModels(mainModel, mainModel);
20513
+ }
20514
+ await persist();
20515
+ return fallback.mainError === void 0 ? `Logged out. Cleared OAuth credentials. Main model ${mainModel}; subagent ${childModel}.` : `Logged out. Cleared OAuth credentials. ${fallback.mainError}`;
20435
20516
  }
20436
20517
  };
20437
20518
  if (config.sleep) {
@@ -4,7 +4,7 @@ import {
4
4
  message,
5
5
  redactErrorText,
6
6
  redactSecrets
7
- } from "./chunk-5TG3E2EJ.js";
7
+ } from "./chunk-QQCXJC4U.js";
8
8
 
9
9
  // src/rpc/server.ts
10
10
  import { createInterface } from "readline";
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire } from "node:module"; const require = createRequire(impor
3
3
  import {
4
4
  FlavorRpcServer,
5
5
  TraceRecorder
6
- } from "./chunk-BKARQJQZ.js";
6
+ } from "./chunk-YRIPE5K5.js";
7
7
  import {
8
8
  staticTaskLines
9
9
  } from "./chunk-KKIBYDJI.js";
@@ -22,7 +22,7 @@ import {
22
22
  packageVersion,
23
23
  redactErrorText,
24
24
  runPalBroker
25
- } from "./chunk-5TG3E2EJ.js";
25
+ } from "./chunk-QQCXJC4U.js";
26
26
  import {
27
27
  McpServerConfigSchema,
28
28
  McpServerNameSchema,
@@ -861,7 +861,7 @@ async function runInteractiveCli(props) {
861
861
  const [{ render, AlternateScreen }, { createElement }, { App }] = await Promise.all([
862
862
  import("./claude-ink-ORUA7GWG.js"),
863
863
  import("react"),
864
- import("./app-3KW2FTVJ.js")
864
+ import("./app-F4P7RV2X.js")
865
865
  ]);
866
866
  const instance = await render(createElement(
867
867
  AlternateScreen,
@@ -505,6 +505,7 @@ var DESKTOP_CHANNELS = {
505
505
  d2cRunQualityJudge: "desktop:d2c-run-quality-judge",
506
506
  d2cResolveQualityIssue: "desktop:d2c-resolve-quality-issue",
507
507
  d2cSetManualAcceptance: "desktop:d2c-set-manual-acceptance",
508
+ e2eGetDeliveryRun: "desktop:e2e-get-delivery-run",
508
509
  event: "desktop:event"
509
510
  };
510
511
 
@@ -6127,7 +6128,8 @@ var RECOVERABLE_MODEL_ERRORS = /* @__PURE__ */ new Set([
6127
6128
  "network",
6128
6129
  "rate_limit",
6129
6130
  "unknown",
6130
- "incomplete_stream"
6131
+ "incomplete_stream",
6132
+ "empty_response"
6131
6133
  ]);
6132
6134
  function envMaxIterations() {
6133
6135
  const raw = process.env["FLAVOR_MAX_ITERATIONS"];
@@ -6321,6 +6323,12 @@ var AgentLoop = class {
6321
6323
  } finally {
6322
6324
  if (!completed && terminalError === void 0) {
6323
6325
  terminalError = { code: "incomplete_stream", message: "Provider stream ended without a done or error event" };
6326
+ } else if (completed && assistantText.trim().length === 0 && collectedToolCalls.length === 0) {
6327
+ completed = false;
6328
+ terminalError = {
6329
+ code: "empty_response",
6330
+ message: "Provider completed without visible text or tool calls"
6331
+ };
6324
6332
  }
6325
6333
  providerError = terminalError !== void 0;
6326
6334
  try {
@@ -6360,7 +6368,7 @@ var AgentLoop = class {
6360
6368
  totalCacheCreationTokens += usage.cacheCreationTokens ?? 0;
6361
6369
  yield { type: "usage", ...usage, totalInputTokens, totalOutputTokens };
6362
6370
  }
6363
- const providerProducedOutput = assistantText.length > 0 || collectedToolCalls.length > 0;
6371
+ const providerProducedOutput = assistantText.trim().length > 0 || collectedToolCalls.length > 0;
6364
6372
  if (terminalError?.code === "context_overflow" && !reactiveRetried && !providerProducedOutput && attempt < maxAttempts) {
6365
6373
  let compacted = false;
6366
6374
  try {
@@ -11210,7 +11218,7 @@ import { readFile as readFile12 } from "fs/promises";
11210
11218
  import { join as join17 } from "path";
11211
11219
 
11212
11220
  // src/tools/shell.ts
11213
- import { spawn as spawn3 } from "child_process";
11221
+ import { spawn as spawn3, spawnSync } from "child_process";
11214
11222
  import { isAbsolute as isAbsolute8, relative as relative11, resolve as resolve13, sep as sep10 } from "path";
11215
11223
  import { z as z21 } from "zod";
11216
11224
 
@@ -11340,6 +11348,29 @@ function tokenizeCommandLine(command) {
11340
11348
  function unquoteToken(token) {
11341
11349
  return token.length >= 2 && token.startsWith('"') && token.endsWith('"') ? token.slice(1, -1) : token;
11342
11350
  }
11351
+ var cachedWindowsShell;
11352
+ function hasExecutable(name) {
11353
+ try {
11354
+ return spawnSync("where", [name], { stdio: "ignore", windowsHide: true }).status === 0;
11355
+ } catch {
11356
+ return false;
11357
+ }
11358
+ }
11359
+ function detectWindowsShell() {
11360
+ if (cachedWindowsShell !== void 0) return cachedWindowsShell;
11361
+ cachedWindowsShell = hasExecutable("pwsh") ? "pwsh" : hasExecutable("powershell") ? "powershell" : "cmd";
11362
+ return cachedWindowsShell;
11363
+ }
11364
+ function psQuote(token) {
11365
+ return `'${token.replace(/'/g, "''")}'`;
11366
+ }
11367
+ function buildWindowsCommandLine(kind, commandLine, command, args) {
11368
+ if (kind === "cmd") return `chcp 65001>nul & ${commandLine}`;
11369
+ const executable = kind === "pwsh" ? "pwsh" : "powershell";
11370
+ const script = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map(psQuote).join(" ")}` : ""}; exit $LASTEXITCODE`;
11371
+ const encoded = Buffer.from(script, "utf16le").toString("base64");
11372
+ return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
11373
+ }
11343
11374
  function createShellTool(workspace, options = {}) {
11344
11375
  const root = resolve13(workspace);
11345
11376
  const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
@@ -11460,7 +11491,7 @@ async function executeShell(root, input, cancellation, maxBytes, onOutput) {
11460
11491
  const quoteArg = (s) => s.includes(" ") ? `"${s.replace(/"/g, '""')}"` : s;
11461
11492
  const quoted = input.args.map(quoteArg).join(" ");
11462
11493
  const commandLine = quoted.length > 0 ? `${quoteArg(input.command)} ${quoted}` : quoteArg(input.command);
11463
- const shellCommandLine = process.platform === "win32" ? `chcp 65001>nul & ${commandLine}` : commandLine;
11494
+ const shellCommandLine = process.platform === "win32" ? buildWindowsCommandLine(detectWindowsShell(), commandLine, input.command, input.args) : commandLine;
11464
11495
  const child = spawn3(shellCommandLine, {
11465
11496
  cwd,
11466
11497
  shell: true,
@@ -13090,6 +13121,11 @@ function escapeHtml(text) {
13090
13121
 
13091
13122
  // src/auth/store.ts
13092
13123
  import { dirname as dirname13 } from "path";
13124
+ async function retainOnlyCredentials(store, keepCredentialId) {
13125
+ const tokens2 = await store.load();
13126
+ const retained = Object.fromEntries(Object.entries(tokens2).filter(([id]) => id === keepCredentialId));
13127
+ await store.save(retained);
13128
+ }
13093
13129
  function createFileTokenStore(filePath) {
13094
13130
  let baseline = {};
13095
13131
  const store = {
@@ -18789,6 +18825,7 @@ var MVP_COMMANDS = [
18789
18825
  "init",
18790
18826
  "config",
18791
18827
  "login",
18828
+ "logout",
18792
18829
  "permissions",
18793
18830
  "skills",
18794
18831
  "plugins",
@@ -19066,6 +19103,7 @@ var HELP = [
19066
19103
  "/model <main|subagent> <provider:model> switch any configured model",
19067
19104
  "/permissions <default|acceptEdits|plan|bypassPermissions|auto|bubble>",
19068
19105
  "/login authenticate via OAuth PKCE",
19106
+ "/logout clear stored OAuth credentials",
19069
19107
  "/init /config /skills /plugins /hooks /tasks",
19070
19108
  "/memory /remember [type] <text> /forget <text-or-id> /forget-cold /finish",
19071
19109
  "/checkpoint [label] /tree /rewind <node> /unrevert /fork <node>",
@@ -19480,6 +19518,8 @@ var FlavorSession = class {
19480
19518
  } else if (command.name === "login") {
19481
19519
  this.#notice("Opening browser for authentication...");
19482
19520
  this.#notice(await this.#services.login());
19521
+ } else if (command.name === "logout") {
19522
+ this.#notice(await this.#services.logout());
19483
19523
  } else if (command.name === "help") this.#notice(HELP);
19484
19524
  else if (command.name === "exit") this.#services.output({ type: "exit" });
19485
19525
  }
@@ -23167,7 +23207,10 @@ async function createProductionRuntime(options) {
23167
23207
  states: { ...taskStates },
23168
23208
  results: { ...taskResults }
23169
23209
  },
23170
- models: { main: harness.mainModelId, subagent: harness.subagentModelId },
23210
+ // Persist the resolved decision, not the harness snapshot: after logout the
23211
+ // harness may still hold the old login model, and resuming from that stale
23212
+ // value is what made the welcome card show a previous service.
23213
+ models: { main: mainModel, subagent: childModel },
23171
23214
  permissionMode: harness.permissionMode,
23172
23215
  memory: memoryLifecycle,
23173
23216
  timeline: { version: 1, state: timelineState }
@@ -23422,7 +23465,7 @@ async function createProductionRuntime(options) {
23422
23465
  const messages = allMessages.slice(memoryLifecycle.messageStart ?? 0);
23423
23466
  const transcriptHash = memoryTranscriptHash(messages);
23424
23467
  if (memoryLifecycle.status === "completed" && memoryLifecycle.transcriptHash === transcriptHash) {
23425
- return "Task was already completed and evaluated for long-term memory.";
23468
+ return "This conversation segment was already evaluated for long-term memory.";
23426
23469
  }
23427
23470
  if (!manual && memoryBehavior.autoExtractPaused) {
23428
23471
  return "Automatic long-term-memory extraction is paused after repeated dismissals; use /finish, /remember, or an explicit \u201Cremember\u201D request to store memory manually.";
@@ -23430,7 +23473,7 @@ async function createProductionRuntime(options) {
23430
23473
  autoStoredContents.length = 0;
23431
23474
  const finalization = memoryCoordinator === void 0 || !config.memory.autoExtract || options.approvalPolicy === "deny" ? { evaluated: true, candidates: false, stored: 0 } : await memoryCoordinator.finalize(memoryLifecycle.taskId ?? sessionId, messages);
23432
23475
  if (!finalization.evaluated) {
23433
- return "Task memory evaluation failed and was not marked complete; retry /finish after checking diagnostics.";
23476
+ return "Long-term-memory evaluation failed; retry /finish after checking diagnostics.";
23434
23477
  }
23435
23478
  memoryLifecycle = {
23436
23479
  status: "completed",
@@ -23442,9 +23485,9 @@ async function createProductionRuntime(options) {
23442
23485
  await persist();
23443
23486
  if (finalization.candidates && finalization.stored > 0) {
23444
23487
  const storedText = autoStoredContents[0] ?? "a high-confidence memory";
23445
- return `Task completed. Stored high-confidence long-term memory directly: "${storedText}". Run /forget to remove it if undesired.`;
23488
+ return `Long-term memory updated. Stored high-confidence entry: "${storedText}". Run /forget to remove it if undesired.`;
23446
23489
  }
23447
- return finalization.candidates ? "Task completed. Review the generated long-term-memory candidates before anything is stored." : "Task completed; no durable long-term-memory candidates passed the threshold.";
23490
+ return finalization.candidates ? "Long-term-memory evaluation completed. Review the generated candidates before anything is stored." : "Long-term-memory evaluation completed; no durable candidates passed the threshold.";
23448
23491
  };
23449
23492
  let explicitMemoryRequest;
23450
23493
  let automaticMemoryTask = false;
@@ -23455,10 +23498,10 @@ async function createProductionRuntime(options) {
23455
23498
  timelineState = transcriptReducer(timelineState, { type: "submit", prompt });
23456
23499
  automaticMemoryTask = !prompt.startsWith("/") && harness.permissionProfile !== "d2c";
23457
23500
  if (automaticMemoryTask) memoryReviews.dismissAll();
23458
- if (!prompt.startsWith("/") && memoryLifecycle.status === "completed") {
23501
+ if (!prompt.startsWith("/")) {
23459
23502
  memoryLifecycle = {
23460
23503
  status: "active",
23461
- taskId: createMemoryTaskId(),
23504
+ taskId: memoryLifecycle.status === "completed" ? createMemoryTaskId() : memoryLifecycle.taskId ?? createMemoryTaskId(),
23462
23505
  messageStart: harness.main.context.snapshot().messages.length
23463
23506
  };
23464
23507
  }
@@ -23500,9 +23543,9 @@ async function createProductionRuntime(options) {
23500
23543
  }
23501
23544
  if (explicit === void 0 && automatic && event.payload.outcome === "completed" && memoryCoordinator !== void 0 && config.memory.autoExtract && options.approvalPolicy !== "deny" && !memoryBehavior.autoExtractPaused) {
23502
23545
  const result = await finalizeMemoryTask();
23503
- if (result.startsWith("Task memory evaluation failed")) {
23546
+ if (result.startsWith("Long-term-memory evaluation failed")) {
23504
23547
  emitOutput({ type: "notice", message: "Automatic long-term-memory evaluation failed; use /finish to retry after checking diagnostics." });
23505
- } else if (result.startsWith("Task completed. Review") || result.startsWith("Task completed. Stored")) {
23548
+ } else if (result.startsWith("Long-term-memory evaluation completed. Review") || result.startsWith("Long-term memory updated.")) {
23506
23549
  emitOutput({ type: "notice", message: result });
23507
23550
  }
23508
23551
  }
@@ -23808,8 +23851,8 @@ async function createProductionRuntime(options) {
23808
23851
  const services = {
23809
23852
  hooks,
23810
23853
  workspace,
23811
- mainModel: () => harness.mainModelId,
23812
- subagentModel: () => harness.subagentModelId,
23854
+ mainModel: () => mainModel,
23855
+ subagentModel: () => childModel,
23813
23856
  llmServiceName: () => effectiveLlm?.serviceName,
23814
23857
  permissionMode: () => harness.permissionMode,
23815
23858
  ...palServices === void 0 ? {} : { pals: palServices },
@@ -23883,6 +23926,8 @@ async function createProductionRuntime(options) {
23883
23926
  }
23884
23927
  }
23885
23928
  harness.setModel(role, id);
23929
+ if (role === "main") mainModel = id;
23930
+ else childModel = id;
23886
23931
  await persist();
23887
23932
  },
23888
23933
  setPermissionMode: async (mode) => {
@@ -24129,12 +24174,48 @@ ${renderMemoryDocument(entries)}`;
24129
24174
  goalOrchestrator.setModels(mainModel, mainModel);
24130
24175
  delete selectedModels.mainError;
24131
24176
  delete selectedModels.childError;
24177
+ await retainOnlyCredentials(tokenStore, credentialId);
24132
24178
  if (!secrets.includes(next.accessToken)) secrets.push(next.accessToken);
24133
24179
  await persist();
24134
24180
  return `Authenticated to "${next.serviceName}". Main model ${mainModel}; subagent ${childModel}; gateway ${next.baseURL}. Configuration is active now.`;
24135
24181
  } catch (error) {
24136
24182
  return `Login failed: ${message(error)}`;
24137
24183
  }
24184
+ },
24185
+ async logout() {
24186
+ const tokenStore = createFileTokenStore(join29(home, ".flavor-code", "auth.json"));
24187
+ const tokens2 = await tokenStore.load();
24188
+ const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
24189
+ if (!hadCredentials) {
24190
+ return "Not authenticated \u2014 no stored OAuth credentials to clear.";
24191
+ }
24192
+ for (let i = registeredProviders.length - 1; i >= 0; i -= 1) {
24193
+ const provider = registeredProviders[i];
24194
+ if (provider !== void 0 && provider.pkceManaged === true) {
24195
+ registry.unregister(provider.name);
24196
+ registeredProviders.splice(i, 1);
24197
+ }
24198
+ }
24199
+ if (effectiveLlm !== void 0) {
24200
+ registry.unregister(effectiveLlm.providerId);
24201
+ effectiveLlm = void 0;
24202
+ }
24203
+ await tokenStore.save({});
24204
+ const fallback = selectModels(config, registeredProviders, diagnostics);
24205
+ mainModel = fallback.main;
24206
+ childModel = fallback.child;
24207
+ if (fallback.mainError === void 0) delete selectedModels.mainError;
24208
+ else selectedModels.mainError = fallback.mainError;
24209
+ if (fallback.childError === void 0) delete selectedModels.childError;
24210
+ else selectedModels.childError = fallback.childError;
24211
+ if (fallback.mainError === void 0 && registry.has(safeProvider(mainModel)) && registry.has(safeProvider(childModel))) {
24212
+ harness.setModel("main", mainModel);
24213
+ harness.setModel("subagent", childModel);
24214
+ hallucinationGuard.setModel(childModel);
24215
+ goalOrchestrator.setModels(mainModel, mainModel);
24216
+ }
24217
+ await persist();
24218
+ return fallback.mainError === void 0 ? `Logged out. Cleared OAuth credentials. Main model ${mainModel}; subagent ${childModel}.` : `Logged out. Cleared OAuth credentials. ${fallback.mainError}`;
24138
24219
  }
24139
24220
  };
24140
24221
  if (config.sleep) {
@@ -27738,11 +27819,12 @@ var DesktopRuntimeController = class {
27738
27819
  ].join("\n");
27739
27820
  const delivery = await readDeliveryRun(workspace, task);
27740
27821
  if (delivery?.nodes.api.status === "running") {
27822
+ const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile33(join39(project, file)))));
27741
27823
  const completed = await updateDeliveryRun(
27742
27824
  workspace,
27743
27825
  task,
27744
27826
  delivery.revision,
27745
- (run) => completeDeliveryNode(run, "api", generated.files.map((file) => artifactRef(file, file)))
27827
+ (run) => completeDeliveryNode(run, "api", apiArtifacts)
27746
27828
  );
27747
27829
  await updateDeliveryRun(
27748
27830
  workspace,
@@ -28018,6 +28100,12 @@ VITE_API_BASE_URL=${running.url}
28018
28100
  });
28019
28101
  return readD2cProductPlanView(workspace, task).then((view) => view);
28020
28102
  }
28103
+ /** Read-only snapshot of the seven-node E2E delivery run; undefined when the task never started a delivery. */
28104
+ async getE2eDeliveryRun(task) {
28105
+ const workspace = this.#workspace;
28106
+ if (workspace === void 0) return void 0;
28107
+ return readDeliveryRun(workspace, task);
28108
+ }
28021
28109
  async #assertProductPrdLocked(workspace, task) {
28022
28110
  const current = await readD2cProductPlanView(workspace, task);
28023
28111
  if (current?.plan.approvedPrd === void 0) return;
@@ -28742,6 +28830,9 @@ function installIpcHandlers() {
28742
28830
  const input = D2cQualityIssueDecisionInputSchema.parse(value);
28743
28831
  return controller.resolveD2cQualityIssue(input.task, input.issueId, input.decision);
28744
28832
  });
28833
+ ipcMain.handle(DESKTOP_CHANNELS.e2eGetDeliveryRun, async (_event, value) => {
28834
+ return controller.getE2eDeliveryRun(D2cTaskActionInputSchema.parse(value).task);
28835
+ });
28745
28836
  }
28746
28837
  function applicationMenu() {
28747
28838
  return Menu.buildFromTemplate([
@@ -65,6 +65,7 @@ var DESKTOP_CHANNELS = {
65
65
  d2cRunQualityJudge: "desktop:d2c-run-quality-judge",
66
66
  d2cResolveQualityIssue: "desktop:d2c-resolve-quality-issue",
67
67
  d2cSetManualAcceptance: "desktop:d2c-set-manual-acceptance",
68
+ e2eGetDeliveryRun: "desktop:e2e-get-delivery-run",
68
69
  event: "desktop:event"
69
70
  };
70
71
 
@@ -158,6 +159,7 @@ var api = {
158
159
  saveD2cJudgeConfig: (input) => import_electron.ipcRenderer.invoke(DESKTOP_CHANNELS.d2cSaveJudgeConfig, input),
159
160
  runD2cQualityJudge: (task) => import_electron.ipcRenderer.invoke(DESKTOP_CHANNELS.d2cRunQualityJudge, { task }),
160
161
  resolveD2cQualityIssue: (task, issueId, decision) => import_electron.ipcRenderer.invoke(DESKTOP_CHANNELS.d2cResolveQualityIssue, { task, issueId, decision }),
162
+ getE2eDeliveryRun: (task) => import_electron.ipcRenderer.invoke(DESKTOP_CHANNELS.e2eGetDeliveryRun, { task }),
161
163
  onEvent(listener) {
162
164
  const handler = (_event, value) => listener(value);
163
165
  import_electron.ipcRenderer.on(DESKTOP_CHANNELS.event, handler);