zelari-code 2.35.0 → 2.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cli/anthropicOAuth.js +1 -1
  2. package/dist/cli/anthropicOAuth.js.map +1 -1
  3. package/dist/cli/desktopConfig.js +18 -3
  4. package/dist/cli/desktopConfig.js.map +1 -1
  5. package/dist/cli/evolution/ledger.js.map +1 -1
  6. package/dist/cli/evolution/runTelemetry.js +68 -0
  7. package/dist/cli/evolution/runTelemetry.js.map +1 -0
  8. package/dist/cli/headless.js +10 -0
  9. package/dist/cli/headless.js.map +1 -1
  10. package/dist/cli/hooks/useChatTurn.js +6 -0
  11. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  12. package/dist/cli/hooks/useSlashDispatch.js +1 -0
  13. package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
  14. package/dist/cli/keyStore.js +3 -7
  15. package/dist/cli/keyStore.js.map +1 -1
  16. package/dist/cli/main.bundled.js +682 -80
  17. package/dist/cli/main.bundled.js.map +4 -4
  18. package/dist/cli/main.js +2 -0
  19. package/dist/cli/main.js.map +1 -1
  20. package/dist/cli/provider/chatgpt.js +1 -1
  21. package/dist/cli/provider/chatgpt.js.map +1 -1
  22. package/dist/cli/provider/resolveStream.js +8 -0
  23. package/dist/cli/provider/resolveStream.js.map +1 -1
  24. package/dist/cli/provider/responsesApi.js +281 -0
  25. package/dist/cli/provider/responsesApi.js.map +1 -0
  26. package/dist/cli/providerConfig.js +49 -0
  27. package/dist/cli/providerConfig.js.map +1 -1
  28. package/dist/cli/refreshRegistry.js +78 -0
  29. package/dist/cli/refreshRegistry.js.map +1 -1
  30. package/dist/cli/runHeadless.js +32 -1
  31. package/dist/cli/runHeadless.js.map +1 -1
  32. package/dist/cli/slashCommands.js +18 -0
  33. package/dist/cli/slashCommands.js.map +1 -1
  34. package/dist/cli/slashHandlers/provider.js +11 -1
  35. package/dist/cli/slashHandlers/provider.js.map +1 -1
  36. package/dist/cli/thinking.js +5 -4
  37. package/dist/cli/thinking.js.map +1 -1
  38. package/dist/cli/thinkingCapability.js +7 -2
  39. package/dist/cli/thinkingCapability.js.map +1 -1
  40. package/dist/cli/workspace/planStore.js +29 -0
  41. package/dist/cli/workspace/planStore.js.map +1 -1
  42. package/dist/cli/zelariMission.js +130 -30
  43. package/dist/cli/zelariMission.js.map +1 -1
  44. package/package.json +2 -2
@@ -1473,7 +1473,7 @@ var init_anthropicOAuth = __esm({
1473
1473
  init_grokOAuth();
1474
1474
  DEFAULT_ANTHROPIC_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
1475
1475
  ANTHROPIC_AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
1476
- ANTHROPIC_TOKEN_URL = "https://console.anthropic.com/v1/oauth/token";
1476
+ ANTHROPIC_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
1477
1477
  ANTHROPIC_REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
1478
1478
  ANTHROPIC_SCOPE = "org:create_api_key user:profile user:inference";
1479
1479
  AnthropicOAuthError = class extends Error {
@@ -1495,7 +1495,35 @@ function registerDefaultRefreshImpls() {
1495
1495
  if (!registry.has("chatgpt")) registry.set("chatgpt", chatgptRefreshAdapter);
1496
1496
  if (!registry.has("anthropic")) registry.set("anthropic", anthropicRefreshAdapter);
1497
1497
  }
1498
- var registry, grokRefreshAdapter, chatgptRefreshAdapter, anthropicRefreshAdapter;
1498
+ function normalizeRefreshError(providerId, err) {
1499
+ const code = err?.code;
1500
+ const message = err instanceof Error ? err.message : String(err);
1501
+ if (code === "invalid_grant" || message.includes("invalid_grant")) {
1502
+ return new RefreshRejectedError(
1503
+ `${providerId}: refresh token rejected (invalid_grant) \u2014 run /login ${providerId} to re-authenticate`,
1504
+ providerId,
1505
+ err
1506
+ );
1507
+ }
1508
+ return err;
1509
+ }
1510
+ async function runRefreshImpl(id3, refreshToken) {
1511
+ const existing = inflightRefresh.get(id3);
1512
+ if (existing) return existing;
1513
+ const impl = getRefreshImpl(id3);
1514
+ if (!impl) {
1515
+ throw new Error(`No refresh impl registered for provider "${id3}"`);
1516
+ }
1517
+ const run = Promise.resolve().then(() => impl(id3, refreshToken)).catch((err) => {
1518
+ throw normalizeRefreshError(id3, err);
1519
+ });
1520
+ inflightRefresh.set(id3, run);
1521
+ void run.finally(() => {
1522
+ if (inflightRefresh.get(id3) === run) inflightRefresh.delete(id3);
1523
+ }).catch(() => void 0);
1524
+ return run;
1525
+ }
1526
+ var registry, grokRefreshAdapter, chatgptRefreshAdapter, anthropicRefreshAdapter, RefreshRejectedError, inflightRefresh;
1499
1527
  var init_refreshRegistry = __esm({
1500
1528
  "src/cli/refreshRegistry.ts"() {
1501
1529
  "use strict";
@@ -1514,6 +1542,17 @@ var init_refreshRegistry = __esm({
1514
1542
  anthropicRefreshAdapter = async (_providerId, refreshToken) => {
1515
1543
  return refreshAnthropicToken({ refreshToken });
1516
1544
  };
1545
+ RefreshRejectedError = class extends Error {
1546
+ constructor(message, providerId, cause) {
1547
+ super(message);
1548
+ this.providerId = providerId;
1549
+ this.cause = cause;
1550
+ this.name = "RefreshRejectedError";
1551
+ }
1552
+ /** Marker callers can check without importing the class. */
1553
+ reloginRequired = true;
1554
+ };
1555
+ inflightRefresh = /* @__PURE__ */ new Map();
1517
1556
  }
1518
1557
  });
1519
1558
 
@@ -1742,11 +1781,7 @@ var init_keyStore = __esm({
1742
1781
  "anthropic"
1743
1782
  ];
1744
1783
  defaultRefreshImpl = async (providerId, refreshToken) => {
1745
- const impl = getRefreshImpl(providerId);
1746
- if (!impl) {
1747
- throw new Error(`No refresh impl registered for provider "${providerId}"`);
1748
- }
1749
- return impl(providerId, refreshToken);
1784
+ return runRefreshImpl(providerId, refreshToken);
1750
1785
  };
1751
1786
  }
1752
1787
  });
@@ -1767,10 +1802,11 @@ function effortLevelsFor(id3, model) {
1767
1802
  const m = (model ?? "").trim();
1768
1803
  switch (id3) {
1769
1804
  case "grok":
1770
- case "openai-compatible":
1771
- case "custom":
1772
1805
  if (grokHasXhigh(m)) return [...BASE_EFFORTS, "xhigh"];
1773
1806
  return [...BASE_EFFORTS];
1807
+ case "openai-compatible":
1808
+ case "custom":
1809
+ return [...BASE_EFFORTS, "xhigh", "max"];
1774
1810
  case "chatgpt":
1775
1811
  if (gptHasMax(m)) return [...BASE_EFFORTS, "xhigh", "max"];
1776
1812
  if (gptHasXhigh(m)) return [...BASE_EFFORTS, "xhigh"];
@@ -1983,13 +2019,14 @@ function translateOpenAiCompatibleThinking(providerId, spec, model) {
1983
2019
  };
1984
2020
  }
1985
2021
  }
1986
- function translateResponsesThinking(spec, model) {
2022
+ function translateResponsesThinking(spec, model, providerId) {
1987
2023
  if (spec === "auto") return { patch: {}, degraded: false };
2024
+ const id3 = providerId ?? "chatgpt";
1988
2025
  switch (spec.kind) {
1989
2026
  case "off":
1990
2027
  return { patch: { reasoning: { effort: "minimal" } }, degraded: false };
1991
2028
  case "effort": {
1992
- const resolved = clampEffort("chatgpt", model, spec.effort);
2029
+ const resolved = clampEffort(id3, model, spec.effort);
1993
2030
  return withClampNote(
1994
2031
  { reasoning: { effort: resolved.effort } },
1995
2032
  resolved.clamped,
@@ -1997,7 +2034,7 @@ function translateResponsesThinking(spec, model) {
1997
2034
  );
1998
2035
  }
1999
2036
  case "budget":
2000
- return degrade('thinking "budget" is not supported for chatgpt \u2014 use low/medium/high/xhigh/max');
2037
+ return degrade(`thinking "budget" is not supported on the Responses API for "${id3}" \u2014 use low/medium/high/xhigh/max`);
2001
2038
  }
2002
2039
  }
2003
2040
  function translateAnthropicThinking(spec, model) {
@@ -2046,6 +2083,7 @@ __export(providerConfig_exports, {
2046
2083
  clearKrakenVerifier: () => clearKrakenVerifier,
2047
2084
  getActiveModel: () => getActiveModel,
2048
2085
  getActiveProvider: () => getActiveProvider,
2086
+ getApiStyleFor: () => getApiStyleFor,
2049
2087
  getCustomEndpoint: () => getCustomEndpoint,
2050
2088
  getKrakenVerifierOverride: () => getKrakenVerifierOverride,
2051
2089
  getModelForProvider: () => getModelForProvider,
@@ -2054,6 +2092,7 @@ __export(providerConfig_exports, {
2054
2092
  getThinkingForProvider: () => getThinkingForProvider,
2055
2093
  loadProviderConfig: () => loadProviderConfig,
2056
2094
  setActiveProviderId: () => setActiveProviderId,
2095
+ setApiStyleFor: () => setApiStyleFor,
2057
2096
  setCustomEndpoint: () => setCustomEndpoint,
2058
2097
  setKrakenVerifier: () => setKrakenVerifier,
2059
2098
  setModelForProvider: () => setModelForProvider,
@@ -2071,6 +2110,7 @@ function mergeStoredProviderConfig(parsed) {
2071
2110
  modelByProvider: { ...DEFAULTS.modelByProvider, ...parsed.modelByProvider },
2072
2111
  thinkingByProvider: { ...DEFAULTS.thinkingByProvider, ...parsed.thinkingByProvider },
2073
2112
  customEndpoints: mergeCustomEndpoints(parsed.customEndpoints),
2113
+ apiStyleByProvider: mergeApiStyles(parsed.apiStyleByProvider),
2074
2114
  krakenVerifier: mergeKrakenVerifier(parsed.krakenVerifier)
2075
2115
  };
2076
2116
  }
@@ -2084,6 +2124,17 @@ function cloneDefaults() {
2084
2124
  customEndpoints: { ...DEFAULTS.customEndpoints }
2085
2125
  };
2086
2126
  }
2127
+ function mergeApiStyles(raw) {
2128
+ if (!raw || typeof raw !== "object") return {};
2129
+ const result = {};
2130
+ const validIds = new Set(PROVIDERS.map((p3) => p3.id));
2131
+ for (const [key, value] of Object.entries(raw)) {
2132
+ if (!validIds.has(key)) continue;
2133
+ if (value !== "responses") continue;
2134
+ result[key] = "responses";
2135
+ }
2136
+ return result;
2137
+ }
2087
2138
  function applyEnvOverrides(config2) {
2088
2139
  const envActive = process.env.ANATHEMA_ACTIVE_PROVIDER;
2089
2140
  const envModel = process.env.OPENAI_MODEL;
@@ -2155,6 +2206,23 @@ function clearCustomEndpoint(id3) {
2155
2206
  delete config2.customEndpoints[id3];
2156
2207
  writeProviderConfig(config2);
2157
2208
  }
2209
+ function getApiStyleFor(id3) {
2210
+ return getProviderConfig().apiStyleByProvider?.[id3] === "responses" ? "responses" : "chat";
2211
+ }
2212
+ function setApiStyleFor(id3, style) {
2213
+ const spec = PROVIDERS.find((p3) => p3.id === id3);
2214
+ if (!spec) {
2215
+ throw new Error(`Unknown provider id: "${id3}". Available: ${PROVIDERS.map((p3) => p3.id).join(", ")}`);
2216
+ }
2217
+ if (style !== "chat" && style !== "responses") {
2218
+ throw new Error(`Invalid api style: "${style}". Use 'chat' or 'responses'.`);
2219
+ }
2220
+ const config2 = getProviderConfig();
2221
+ if (!config2.apiStyleByProvider) config2.apiStyleByProvider = {};
2222
+ if (style === "chat") delete config2.apiStyleByProvider[id3];
2223
+ else config2.apiStyleByProvider[id3] = "responses";
2224
+ writeProviderConfig(config2);
2225
+ }
2158
2226
  function mergeKrakenVerifier(raw) {
2159
2227
  if (!raw || typeof raw !== "object") return void 0;
2160
2228
  const provider = typeof raw.provider === "string" ? raw.provider.trim() : "";
@@ -2362,23 +2430,23 @@ async function resolveAuthToken(provider, options) {
2362
2430
  return resolved?.apiKey;
2363
2431
  }
2364
2432
  async function resolveDiscoveryHeaders(provider, authToken) {
2365
- const headers2 = { Accept: "application/json" };
2366
- if (!authToken) return headers2;
2433
+ const headers3 = { Accept: "application/json" };
2434
+ if (!authToken) return headers3;
2367
2435
  if (provider === "anthropic") {
2368
- headers2.Authorization = `Bearer ${authToken}`;
2369
- headers2["x-api-key"] = authToken;
2370
- headers2["anthropic-version"] = "2023-06-01";
2371
- headers2["anthropic-beta"] = "oauth-2025-04-20";
2372
- return headers2;
2436
+ headers3.Authorization = `Bearer ${authToken}`;
2437
+ headers3["x-api-key"] = authToken;
2438
+ headers3["anthropic-version"] = "2023-06-01";
2439
+ headers3["anthropic-beta"] = "oauth-2025-04-20";
2440
+ return headers3;
2373
2441
  }
2374
- headers2.Authorization = `Bearer ${authToken}`;
2442
+ headers3.Authorization = `Bearer ${authToken}`;
2375
2443
  if (provider === "chatgpt") {
2376
2444
  const { getOAuthToken: getOAuthToken2 } = await Promise.resolve().then(() => (init_keyStore(), keyStore_exports));
2377
2445
  const accountId = getOAuthToken2("chatgpt")?.accountId;
2378
- if (accountId) headers2["ChatGPT-Account-Id"] = accountId;
2379
- headers2["OpenAI-Beta"] = "responses=experimental";
2446
+ if (accountId) headers3["ChatGPT-Account-Id"] = accountId;
2447
+ headers3["OpenAI-Beta"] = "responses=experimental";
2380
2448
  }
2381
- return headers2;
2449
+ return headers3;
2382
2450
  }
2383
2451
  function parseAnthropicModelsResponse(json3) {
2384
2452
  if (!json3 || typeof json3 !== "object") return [];
@@ -2430,8 +2498,8 @@ async function discoverModelsForProvider(provider, options = {}) {
2430
2498
  const fetchImpl = options.fetchImpl ?? fetch;
2431
2499
  let response;
2432
2500
  try {
2433
- const headers2 = await resolveDiscoveryHeaders(provider, authToken);
2434
- response = await fetchImpl(url2, { method: "GET", headers: headers2 });
2501
+ const headers3 = await resolveDiscoveryHeaders(provider, authToken);
2502
+ response = await fetchImpl(url2, { method: "GET", headers: headers3 });
2435
2503
  } catch (err) {
2436
2504
  throw new ModelDiscoveryError(
2437
2505
  `Network error contacting ${url2}: ${err instanceof Error ? err.message : String(err)}`,
@@ -33786,10 +33854,28 @@ function buildMissionBrief(input) {
33786
33854
  ],
33787
33855
  phases,
33788
33856
  sliceMvp,
33789
- slices: [sliceMvp],
33857
+ slices: buildSlicePlan(sliceMvp, input.planTaskIds, maxTasks),
33790
33858
  userPromptOriginal: userMessage
33791
33859
  };
33792
33860
  }
33861
+ function buildSlicePlan(sliceMvp, planTaskIds, maxTasks) {
33862
+ const ids = Array.isArray(planTaskIds) ? planTaskIds.filter((t) => typeof t === "string" && t) : [];
33863
+ if (ids.length === 0)
33864
+ return [{ ...sliceMvp }];
33865
+ const size = Number.isFinite(maxTasks) && maxTasks > 0 ? Math.floor(maxTasks) : 8;
33866
+ const slices = [];
33867
+ for (let i = 0; i < ids.length; i += size) {
33868
+ const chunk = ids.slice(i, i + size);
33869
+ const index = i / size;
33870
+ slices.push(index === 0 ? { ...sliceMvp, taskIds: chunk, maxTasks: size } : {
33871
+ id: `slice-${index + 1}`,
33872
+ title: `Increment ${index + 1} \u2014 ${chunk.length} plan task(s)`,
33873
+ taskIds: chunk,
33874
+ maxTasks: size
33875
+ });
33876
+ }
33877
+ return slices;
33878
+ }
33793
33879
  var STACK_SIGNALS;
33794
33880
  var init_missionBrief = __esm({
33795
33881
  "packages/core/dist/council/missionBrief.js"() {
@@ -34614,6 +34700,7 @@ __export(council_exports, {
34614
34700
  buildMotionFixPrompt: () => buildMotionFixPrompt,
34615
34701
  buildRetryPrompt: () => buildRetryPrompt,
34616
34702
  buildSkillDefinition: () => buildSkillDefinition,
34703
+ buildSlicePlan: () => buildSlicePlan,
34617
34704
  buildSystemPrompt: () => buildSystemPrompt,
34618
34705
  buildSystemPromptSplit: () => buildSystemPromptSplit,
34619
34706
  captureFailure: () => captureFailure,
@@ -38221,7 +38308,7 @@ var CORE_VERSION;
38221
38308
  var init_version = __esm({
38222
38309
  "packages/core/dist/version.js"() {
38223
38310
  "use strict";
38224
- CORE_VERSION = "2.35.0";
38311
+ CORE_VERSION = "2.37.0";
38225
38312
  }
38226
38313
  });
38227
38314
 
@@ -38471,6 +38558,7 @@ __export(dist_exports, {
38471
38558
  buildRetryPrompt: () => buildRetryPrompt,
38472
38559
  buildRuntimeObserverBus: () => buildRuntimeObserverBus,
38473
38560
  buildSkillDefinition: () => buildSkillDefinition,
38561
+ buildSlicePlan: () => buildSlicePlan,
38474
38562
  buildSystemPrompt: () => buildSystemPrompt,
38475
38563
  buildSystemPromptSplit: () => buildSystemPromptSplit,
38476
38564
  canExternalClientMutate: () => canExternalClientMutate,
@@ -41557,6 +41645,24 @@ var init_storage = __esm({
41557
41645
  });
41558
41646
 
41559
41647
  // src/cli/workspace/planStore.ts
41648
+ var planStore_exports = {};
41649
+ __export(planStore_exports, {
41650
+ PLAN_FILES_MAX: () => PLAN_FILES_MAX,
41651
+ PLAN_FILE_GLOB_MAX: () => PLAN_FILE_GLOB_MAX,
41652
+ PLAN_MAX_TASKS: () => PLAN_MAX_TASKS,
41653
+ PLAN_NOTES_MAX: () => PLAN_NOTES_MAX,
41654
+ PLAN_SCHEMA_VERSION: () => PLAN_SCHEMA_VERSION,
41655
+ PLAN_TAG_MAX: () => PLAN_TAG_MAX,
41656
+ PLAN_TASK_STATUSES: () => PLAN_TASK_STATUSES,
41657
+ PLAN_TITLE_MAX: () => PLAN_TITLE_MAX,
41658
+ PlanStoreError: () => PlanStoreError,
41659
+ listOpenPlanTaskIds: () => listOpenPlanTaskIds,
41660
+ nextPlanTaskId: () => nextPlanTaskId,
41661
+ normalizePlanTaskFiles: () => normalizePlanTaskFiles,
41662
+ planJsonPathFor: () => planJsonPathFor,
41663
+ withPlanStore: () => withPlanStore,
41664
+ writePlanTaskArtifact: () => writePlanTaskArtifact
41665
+ });
41560
41666
  import {
41561
41667
  copyFileSync,
41562
41668
  existsSync as existsSync20,
@@ -41579,6 +41685,24 @@ function normalizePlanTaskFiles(values) {
41579
41685
  function planJsonPathFor(projectRoot = process.cwd()) {
41580
41686
  return join17(resolveWorkspaceRoot(projectRoot), "plan.json");
41581
41687
  }
41688
+ async function listOpenPlanTaskIds(projectRoot) {
41689
+ try {
41690
+ const jsonPath = planJsonPathFor(projectRoot);
41691
+ if (!existsSync20(jsonPath)) return [];
41692
+ const parsed = JSON.parse(readFileSync18(jsonPath, "utf8"));
41693
+ if (!Array.isArray(parsed.tasks)) return [];
41694
+ const ids = [];
41695
+ for (const raw of parsed.tasks) {
41696
+ if (raw === null || typeof raw !== "object") continue;
41697
+ const t = raw;
41698
+ if (typeof t.id !== "string" || t.id.length === 0) continue;
41699
+ if (t.status === "pending" || t.status === "in_progress") ids.push(t.id);
41700
+ }
41701
+ return ids;
41702
+ } catch {
41703
+ return [];
41704
+ }
41705
+ }
41582
41706
  async function withPlanStore(projectRoot, fn) {
41583
41707
  const rootDir = resolveWorkspaceRoot(projectRoot);
41584
41708
  return workspaceMutex.run(`${rootDir}:plan`, () => {
@@ -41722,7 +41846,7 @@ function normalizeStatus(raw) {
41722
41846
  function firstString(v) {
41723
41847
  return typeof v === "string" && v.trim().length > 0 ? v : null;
41724
41848
  }
41725
- var PLAN_SCHEMA_VERSION, PLAN_MAX_TASKS, PLAN_TITLE_MAX, PLAN_NOTES_MAX, PLAN_TAG_MAX, PLAN_FILES_MAX, PLAN_FILE_GLOB_MAX, PlanStoreError;
41849
+ var PLAN_SCHEMA_VERSION, PLAN_MAX_TASKS, PLAN_TITLE_MAX, PLAN_NOTES_MAX, PLAN_TAG_MAX, PLAN_TASK_STATUSES, PLAN_FILES_MAX, PLAN_FILE_GLOB_MAX, PlanStoreError;
41726
41850
  var init_planStore = __esm({
41727
41851
  "src/cli/workspace/planStore.ts"() {
41728
41852
  "use strict";
@@ -41733,6 +41857,13 @@ var init_planStore = __esm({
41733
41857
  PLAN_TITLE_MAX = 200;
41734
41858
  PLAN_NOTES_MAX = 2e3;
41735
41859
  PLAN_TAG_MAX = 64;
41860
+ PLAN_TASK_STATUSES = [
41861
+ "pending",
41862
+ "in_progress",
41863
+ "completed",
41864
+ "cancelled",
41865
+ "blocked"
41866
+ ];
41736
41867
  PLAN_FILES_MAX = 32;
41737
41868
  PLAN_FILE_GLOB_MAX = 260;
41738
41869
  PlanStoreError = class extends Error {
@@ -51016,7 +51147,7 @@ function openaiCompatibleProvider(config2) {
51016
51147
  const forceRecoveryTool = generation?.toolChoice === "required" && capabilities.buildRecovery.forceToolChoice && recoveryAttempt <= capabilities.buildRecovery.maxForcedTurns;
51017
51148
  body.tool_choice = forceRecoveryTool ? "required" : "auto";
51018
51149
  }
51019
- const headers2 = {
51150
+ const headers3 = {
51020
51151
  "Content-Type": "application/json",
51021
51152
  Authorization: `Bearer ${config2.apiKey}`,
51022
51153
  ...config2.extraHeaders ?? {}
@@ -51024,7 +51155,7 @@ function openaiCompatibleProvider(config2) {
51024
51155
  const affinityHeader = capabilities.promptCache.conversationAffinityHeader;
51025
51156
  const conversationId = params.conversationId?.trim();
51026
51157
  if (affinityHeader && conversationId && conversationId.length <= 256 && !/[\u0000-\u001f\u007f]/.test(conversationId)) {
51027
- headers2[affinityHeader] = conversationId;
51158
+ headers3[affinityHeader] = conversationId;
51028
51159
  }
51029
51160
  let response;
51030
51161
  let lastErrText = "";
@@ -51049,7 +51180,7 @@ function openaiCompatibleProvider(config2) {
51049
51180
  try {
51050
51181
  response = await fetch(`${config2.baseUrl}/chat/completions`, {
51051
51182
  method: "POST",
51052
- headers: headers2,
51183
+ headers: headers3,
51053
51184
  body: JSON.stringify(body),
51054
51185
  // Cancel aborts the HTTP request; stream idle is enforced below
51055
51186
  // per-chunk so active multi-minute streams are not killed.
@@ -52958,6 +53089,244 @@ var init_chatgpt = __esm({
52958
53089
  }
52959
53090
  });
52960
53091
 
53092
+ // src/cli/provider/responsesApi.ts
53093
+ function backoffDelay2(attempt, retryAfterHeader) {
53094
+ if (retryAfterHeader) {
53095
+ const seconds = Number.parseFloat(retryAfterHeader);
53096
+ if (Number.isFinite(seconds) && seconds >= 0) {
53097
+ return Math.min(seconds * 1e3, BACKOFF_CAP_MS2);
53098
+ }
53099
+ }
53100
+ return Math.min(BACKOFF_BASE_MS2 * 2 ** attempt, BACKOFF_CAP_MS2);
53101
+ }
53102
+ function abortableSleep2(ms, signal) {
53103
+ return new Promise((resolve9) => {
53104
+ if (signal?.aborted) return resolve9();
53105
+ const t = setTimeout(resolve9, ms);
53106
+ signal?.addEventListener(
53107
+ "abort",
53108
+ () => {
53109
+ clearTimeout(t);
53110
+ resolve9();
53111
+ },
53112
+ { once: true }
53113
+ );
53114
+ });
53115
+ }
53116
+ function headers2(config2) {
53117
+ const h = {
53118
+ "Content-Type": "application/json",
53119
+ Accept: "text/event-stream",
53120
+ Authorization: `Bearer ${config2.apiKey}`
53121
+ };
53122
+ if (config2.extraHeaders) Object.assign(h, config2.extraHeaders);
53123
+ return h;
53124
+ }
53125
+ function responsesApiProvider(config2) {
53126
+ return async function* (params) {
53127
+ const capabilities = capabilitiesFor(params.model, config2.providerId);
53128
+ const { instructions, input } = toInput(params.messages);
53129
+ const body = {
53130
+ model: params.model,
53131
+ stream: true,
53132
+ input
53133
+ };
53134
+ if (instructions) body.instructions = instructions;
53135
+ if (params.tools && params.tools.length > 0) {
53136
+ body.tools = params.tools.map((t) => ({
53137
+ type: "function",
53138
+ name: t.name,
53139
+ description: t.description,
53140
+ parameters: t.parameters
53141
+ }));
53142
+ }
53143
+ const generation = params.generation;
53144
+ body.temperature = generation?.temperature ?? capabilities.sampling.temperature;
53145
+ const maxTokens = generation?.maxTokens ?? capabilities.maxOutputTokens;
53146
+ if (typeof maxTokens === "number" && maxTokens > 0) {
53147
+ body.max_output_tokens = maxTokens;
53148
+ }
53149
+ const thinkingSpec = config2.thinking ?? "auto";
53150
+ if (thinkingSpec !== "auto") {
53151
+ const t = translateResponsesThinking(thinkingSpec, config2.model, config2.providerId);
53152
+ if (t.degraded) {
53153
+ console.warn(`[thinking] ${t.note ?? "unsupported"} \u2014 falling back to provider default.`);
53154
+ } else {
53155
+ Object.assign(body, t.patch);
53156
+ }
53157
+ }
53158
+ const base2 = config2.baseUrl.replace(/\/$/, "");
53159
+ const url2 = `${base2}/responses`;
53160
+ let response;
53161
+ let lastStatus = 0;
53162
+ let lastErrText = "";
53163
+ for (let attempt = 0; ; attempt++) {
53164
+ const connectController = new AbortController();
53165
+ const connectTimer = setTimeout(
53166
+ () => connectController.abort(
53167
+ new Error(
53168
+ `Provider connect timeout after ${Math.round(PROVIDER_CONNECT_TIMEOUT_MS / 1e3)}s (no response headers). Override ZELARI_PROVIDER_CONNECT_TIMEOUT_MS.`
53169
+ )
53170
+ ),
53171
+ PROVIDER_CONNECT_TIMEOUT_MS
53172
+ );
53173
+ const signals = [connectController.signal];
53174
+ if (params.signal) signals.push(params.signal);
53175
+ try {
53176
+ response = await fetch(url2, {
53177
+ method: "POST",
53178
+ headers: headers2(config2),
53179
+ body: JSON.stringify(body),
53180
+ signal: signals.length === 1 ? signals[0] : AbortSignal.any(signals)
53181
+ });
53182
+ } catch (err) {
53183
+ lastStatus = 0;
53184
+ lastErrText = err instanceof Error ? err.message : String(err);
53185
+ if (params.signal?.aborted) {
53186
+ yield { kind: "error", message: "aborted" };
53187
+ return;
53188
+ }
53189
+ if (attempt < MAX_RETRIES2) {
53190
+ await abortableSleep2(backoffDelay2(attempt, null), params.signal);
53191
+ continue;
53192
+ }
53193
+ yield { kind: "error", message: `Network error: ${lastErrText}` };
53194
+ return;
53195
+ } finally {
53196
+ clearTimeout(connectTimer);
53197
+ }
53198
+ if (response.ok && response.body) break;
53199
+ lastStatus = response.status;
53200
+ lastErrText = await response.text().catch(() => "");
53201
+ if (!RETRYABLE_STATUSES2.has(response.status) || attempt >= MAX_RETRIES2) break;
53202
+ await abortableSleep2(backoffDelay2(attempt, response.headers.get("retry-after")), params.signal);
53203
+ if (params.signal?.aborted) {
53204
+ yield { kind: "error", message: "aborted" };
53205
+ return;
53206
+ }
53207
+ }
53208
+ if (!response || !response.ok || !response.body) {
53209
+ const msg = lastStatus === 0 ? `Network error: ${lastErrText}` : `HTTP ${lastStatus}: ${lastErrText.slice(0, 240)}`;
53210
+ yield { kind: "error", message: msg };
53211
+ return;
53212
+ }
53213
+ const reader = response.body.getReader();
53214
+ const decoder = new TextDecoder();
53215
+ let buffer = "";
53216
+ const tools = /* @__PURE__ */ new Map();
53217
+ let emittedTool = false;
53218
+ const flush = function* (id3) {
53219
+ const t = tools.get(id3);
53220
+ if (!t?.name) return;
53221
+ let args = {};
53222
+ try {
53223
+ args = JSON.parse(t.argsJson || "{}");
53224
+ } catch {
53225
+ args = {};
53226
+ }
53227
+ tools.delete(id3);
53228
+ emittedTool = true;
53229
+ yield { kind: "tool_call", toolCallId: t.id, toolName: t.name, args };
53230
+ };
53231
+ const streamStartedAt = Date.now();
53232
+ let lastUsefulAt = streamStartedAt;
53233
+ const streamDeadline = streamStartedAt + PROVIDER_STREAM_MAX_MS;
53234
+ try {
53235
+ while (true) {
53236
+ const { value, done } = await readChunkWithTimeout(reader, {
53237
+ idleMs: PROVIDER_STREAM_IDLE_MS,
53238
+ deadlineMs: streamDeadline,
53239
+ signal: params.signal,
53240
+ lastUsefulAt: () => lastUsefulAt
53241
+ });
53242
+ if (done) break;
53243
+ buffer += decoder.decode(value, { stream: true });
53244
+ const lines = buffer.split("\n");
53245
+ buffer = lines.pop() ?? "";
53246
+ for (const line of lines) {
53247
+ const trimmed = line.trim();
53248
+ if (!trimmed.startsWith("data:")) continue;
53249
+ const data = trimmed.slice(5).trim();
53250
+ if (!data || data === "[DONE]") continue;
53251
+ let ev;
53252
+ try {
53253
+ ev = JSON.parse(data);
53254
+ } catch {
53255
+ continue;
53256
+ }
53257
+ const type = typeof ev.type === "string" ? ev.type : "";
53258
+ if (type) lastUsefulAt = Date.now();
53259
+ if (type === "response.output_text.delta" && typeof ev.delta === "string") {
53260
+ yield { kind: "text", delta: ev.delta };
53261
+ } else if (type === "response.reasoning_text.delta" && typeof ev.delta === "string") {
53262
+ yield { kind: "thinking", delta: ev.delta };
53263
+ } else if (type === "response.output_item.added") {
53264
+ const item = ev.item;
53265
+ if (item?.type === "function_call") {
53266
+ const id3 = String(item.call_id ?? item.id ?? `fc-${tools.size}`);
53267
+ tools.set(id3, {
53268
+ id: id3,
53269
+ name: typeof item.name === "string" ? item.name : "",
53270
+ argsJson: typeof item.arguments === "string" ? item.arguments : ""
53271
+ });
53272
+ }
53273
+ } else if (type === "response.function_call_arguments.delta") {
53274
+ const itemId = String(ev.item_id ?? ev.call_id ?? "");
53275
+ const existing = itemId ? tools.get(itemId) : [...tools.values()].at(-1);
53276
+ if (existing && typeof ev.delta === "string") existing.argsJson += ev.delta;
53277
+ } else if (type === "response.output_item.done") {
53278
+ const item = ev.item;
53279
+ if (item?.type === "function_call") {
53280
+ const id3 = String(item.call_id ?? item.id ?? "");
53281
+ if (id3) yield* flush(id3);
53282
+ }
53283
+ } else if (type === "response.completed") {
53284
+ const usage = ev.response?.usage;
53285
+ if (usage) {
53286
+ yield {
53287
+ kind: "usage",
53288
+ usage: {
53289
+ promptTokens: usage.input_tokens ?? usage.prompt_tokens ?? 0,
53290
+ completionTokens: usage.output_tokens ?? usage.completion_tokens ?? 0,
53291
+ totalTokens: usage.total_tokens ?? (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0)
53292
+ }
53293
+ };
53294
+ }
53295
+ yield { kind: "finish", reason: emittedTool ? "tool_calls" : "stop" };
53296
+ return;
53297
+ } else if (type === "response.failed" || type === "error") {
53298
+ const msg = typeof ev.message === "string" ? ev.message : JSON.stringify(ev.error ?? ev).slice(0, 200);
53299
+ yield { kind: "error", message: msg };
53300
+ return;
53301
+ }
53302
+ }
53303
+ }
53304
+ for (const id3 of [...tools.keys()]) yield* flush(id3);
53305
+ yield { kind: "finish", reason: emittedTool ? "tool_calls" : "stop" };
53306
+ } finally {
53307
+ reader.releaseLock();
53308
+ }
53309
+ };
53310
+ }
53311
+ var RETRYABLE_STATUSES2, MAX_RETRIES2, BACKOFF_BASE_MS2, BACKOFF_CAP_MS2;
53312
+ var init_responsesApi = __esm({
53313
+ "src/cli/provider/responsesApi.ts"() {
53314
+ "use strict";
53315
+ init_openai_compatible();
53316
+ init_chatgpt();
53317
+ init_thinking();
53318
+ init_capabilities();
53319
+ RETRYABLE_STATUSES2 = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
53320
+ MAX_RETRIES2 = (() => {
53321
+ const raw = process.env.ZELARI_PROVIDER_MAX_RETRIES;
53322
+ const n = raw ? Number.parseInt(raw, 10) : 3;
53323
+ return Number.isFinite(n) && n >= 0 ? n : 3;
53324
+ })();
53325
+ BACKOFF_BASE_MS2 = 500;
53326
+ BACKOFF_CAP_MS2 = 8e3;
53327
+ }
53328
+ });
53329
+
52961
53330
  // src/cli/provider/resolveStream.ts
52962
53331
  var resolveStream_exports = {};
52963
53332
  __export(resolveStream_exports, {
@@ -52966,6 +53335,9 @@ __export(resolveStream_exports, {
52966
53335
  function buildProviderStream(config2) {
52967
53336
  if (config2.providerId === "anthropic") return anthropicMessagesProvider(config2);
52968
53337
  if (config2.providerId === "chatgpt") return chatgptResponsesProvider(config2);
53338
+ if (getApiStyleFor(config2.providerId) === "responses") {
53339
+ return responsesApiProvider(config2);
53340
+ }
52969
53341
  return openaiCompatibleProvider(config2);
52970
53342
  }
52971
53343
  var init_resolveStream = __esm({
@@ -52974,6 +53346,8 @@ var init_resolveStream = __esm({
52974
53346
  init_openai_compatible();
52975
53347
  init_anthropic();
52976
53348
  init_chatgpt();
53349
+ init_responsesApi();
53350
+ init_providerConfig();
52977
53351
  }
52978
53352
  });
52979
53353
 
@@ -56744,6 +57118,7 @@ function parseHeadlessFlags(argv) {
56744
57118
  let once = false;
56745
57119
  let profile;
56746
57120
  let resumeSessionId;
57121
+ let resumeMission = false;
56747
57122
  let exportSessionPath;
56748
57123
  let strictDone;
56749
57124
  let missionStrict;
@@ -56890,6 +57265,8 @@ function parseHeadlessFlags(argv) {
56890
57265
  }
56891
57266
  profile = next;
56892
57267
  i++;
57268
+ } else if (arg === "--resume-mission") {
57269
+ resumeMission = true;
56893
57270
  } else if (arg === "--resume") {
56894
57271
  const next = argv[i + 1];
56895
57272
  if (!next || next.startsWith("--")) {
@@ -56963,6 +57340,7 @@ function parseHeadlessFlags(argv) {
56963
57340
  ...history2 && history2.length > 0 ? { history: history2 } : {},
56964
57341
  ...todos2 && todos2.length > 0 ? { todos: todos2 } : {},
56965
57342
  ...once ? { once: true } : {},
57343
+ ...resumeMission ? { resumeMission: true } : {},
56966
57344
  ...profile ? { profile } : {},
56967
57345
  ...resumeSessionId ? { resumeSessionId } : {},
56968
57346
  ...exportSessionPath ? { exportSessionPath } : {},
@@ -60440,11 +60818,11 @@ var init_httpTransport = __esm({
60440
60818
  const sid = this.sessionId;
60441
60819
  this.sessionId = null;
60442
60820
  if (!sid) return;
60443
- const headers2 = {
60821
+ const headers3 = {
60444
60822
  ...this.opts.headers ?? {},
60445
60823
  "mcp-session-id": sid
60446
60824
  };
60447
- void fetch(this.opts.url, { method: "DELETE", headers: headers2 }).catch(() => {
60825
+ void fetch(this.opts.url, { method: "DELETE", headers: headers3 }).catch(() => {
60448
60826
  });
60449
60827
  }
60450
60828
  // ── internals ────────────────────────────────────────────────────────
@@ -60454,15 +60832,15 @@ var init_httpTransport = __esm({
60454
60832
  const timer = setTimeout(() => ac.abort(), timeoutMs2 + ABORT_GRACE_MS);
60455
60833
  const hadSession = this.sessionId !== null;
60456
60834
  try {
60457
- const headers2 = {
60835
+ const headers3 = {
60458
60836
  "content-type": "application/json",
60459
60837
  accept: "application/json, text/event-stream",
60460
60838
  ...this.opts.headers ?? {}
60461
60839
  };
60462
- if (this.sessionId) headers2["mcp-session-id"] = this.sessionId;
60840
+ if (this.sessionId) headers3["mcp-session-id"] = this.sessionId;
60463
60841
  const res = await fetch(this.opts.url, {
60464
60842
  method: "POST",
60465
- headers: headers2,
60843
+ headers: headers3,
60466
60844
  signal: ac.signal,
60467
60845
  body: JSON.stringify({ jsonrpc: "2.0", ...msg })
60468
60846
  });
@@ -62937,14 +63315,19 @@ var init_traceStore = __esm({
62937
63315
  // src/cli/zelariMission.ts
62938
63316
  var zelariMission_exports = {};
62939
63317
  __export(zelariMission_exports, {
63318
+ MissionResumeError: () => MissionResumeError,
62940
63319
  formatBriefForChat: () => formatBriefForChat,
62941
63320
  isMissionAutoStart: () => isMissionAutoStart,
63321
+ isResumableMission: () => isResumableMission,
63322
+ loadMissionState: () => loadMissionState,
62942
63323
  missionGapKey: () => missionGapKey,
62943
63324
  missionPressure: () => missionPressure,
62944
63325
  resolveMaxCost: () => resolveMaxCost,
62945
63326
  resolveMaxIterations: () => resolveMaxIterations,
62946
63327
  resolveMaxStall: () => resolveMaxStall,
62947
63328
  resolveMaxTokens: () => resolveMaxTokens,
63329
+ resolveMissionSlices: () => resolveMissionSlices,
63330
+ resumeZelariMission: () => resumeZelariMission,
62948
63331
  runZelariMission: () => runZelariMission
62949
63332
  });
62950
63333
  import { randomUUID as randomUUID8 } from "node:crypto";
@@ -63006,16 +63389,42 @@ async function writeMissionState(projectRoot, state3) {
63006
63389
  }
63007
63390
  }
63008
63391
  }
63009
- function buildSlicePrompt(brief, userMessage, runMode, iteration) {
63392
+ async function loadMissionState(projectRoot) {
63393
+ try {
63394
+ const raw = await fs33.readFile(
63395
+ path76.join(projectRoot, ".zelari", "mission-state.json"),
63396
+ "utf8"
63397
+ );
63398
+ const parsed = JSON.parse(raw);
63399
+ if (!parsed || typeof parsed.missionId !== "string" || !parsed.brief) return void 0;
63400
+ return parsed;
63401
+ } catch {
63402
+ return void 0;
63403
+ }
63404
+ }
63405
+ function isResumableMission(state3) {
63406
+ return !!state3 && state3.status !== "success";
63407
+ }
63408
+ function resolveMissionSlices(brief) {
63409
+ const slices = Array.isArray(brief.slices) ? brief.slices.filter((s) => s && !!s.id) : [];
63410
+ return slices.length ? slices : [brief.sliceMvp];
63411
+ }
63412
+ function countImplementationSlices(state3) {
63413
+ return Array.isArray(state3.trace) ? state3.trace.filter((t) => t?.runMode === "implementation").length : 0;
63414
+ }
63415
+ function buildSlicePrompt(brief, userMessage, runMode, iteration, slice) {
63010
63416
  if (runMode === "design-phase") {
63011
63417
  return `${userMessage}
63012
63418
 
63013
- [Zelari mission] Produce the design-phase plan for the MVP: ${brief.deliverableThisMission}. Keep the first slice to at most ${brief.sliceMvp.maxTasks ?? 8} tasks.`;
63419
+ [Zelari mission] Produce the design-phase plan for the MVP: ${brief.deliverableThisMission}. Keep the first slice to at most ${slice.maxTasks ?? 8} tasks.`;
63014
63420
  }
63015
63421
  const fix = iteration > 1 ? " Address any remaining verification failures recorded in .zelari/completion.json." : "";
63422
+ const isMvpSlice = slice.id === resolveMissionSlices(brief)[0].id;
63423
+ const target = isMvpSlice ? "the MVP slice" : `increment "${slice.title}"`;
63424
+ const scope = slice.taskIds?.length ? ` Scope: plan tasks ${slice.taskIds.join(", ")}.` : "";
63016
63425
  return `${userMessage}
63017
63426
 
63018
- [Zelari mission] Implement the MVP slice: ${brief.deliverableThisMission}.${fix} You MUST create or modify the real project files with write_file / edit \u2014 not just describe them in prose. A run that claims completion without writing any file is a failed run and will not be accepted.`;
63427
+ [Zelari mission] Implement ${target}: ${brief.deliverableThisMission}.${scope}${fix} You MUST create or modify the real project files with write_file / edit \u2014 not just describe them in prose. A run that claims completion without writing any file is a failed run and will not be accepted.`;
63019
63428
  }
63020
63429
  function formatBriefForChat(brief) {
63021
63430
  const lines = [
@@ -63035,34 +63444,74 @@ function formatBriefForChat(brief) {
63035
63444
  lines.push(" out of scope:");
63036
63445
  for (const o of brief.outOfScope) lines.push(` - ${o}`);
63037
63446
  }
63447
+ const slices = resolveMissionSlices(brief);
63038
63448
  lines.push(` MVP slice: ${brief.sliceMvp.title} (\u2264 ${brief.sliceMvp.maxTasks} tasks)`);
63449
+ if (slices.length > 1) {
63450
+ lines.push(` increments: ${slices.length} (gated: slice N+1 starts only when N is green)`);
63451
+ for (const s of slices.slice(1)) lines.push(` - ${s.id}: ${s.title}`);
63452
+ }
63039
63453
  return lines.join("\n");
63040
63454
  }
63041
63455
  async function runZelariMission(userMessage, brief, deps) {
63042
63456
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
63043
- const maxIter = deps.maxIterations ?? resolveMaxIterations(deps.env);
63044
- const maxStall = resolveMaxStall(deps.env);
63045
- const maxCost = resolveMaxCost(deps.env);
63046
- const maxTokens = resolveMaxTokens(deps.env);
63047
- const missionId = deps.missionId ?? `m_${randomUUID8().slice(0, 8)}`;
63048
63457
  const startedAt = now().toISOString();
63049
63458
  const state3 = {
63050
- missionId,
63459
+ missionId: deps.missionId ?? `m_${randomUUID8().slice(0, 8)}`,
63051
63460
  userPrompt: userMessage,
63052
63461
  brief,
63053
63462
  iteration: 0,
63054
- currentSliceId: brief.sliceMvp.id,
63463
+ currentSliceId: resolveMissionSlices(brief)[0].id,
63055
63464
  status: "running",
63056
63465
  lastCompletionOk: false,
63057
63466
  startedAt,
63058
63467
  updatedAt: startedAt
63059
63468
  };
63469
+ return driveMission(userMessage, brief, deps, state3, false);
63470
+ }
63471
+ async function resumeZelariMission(deps) {
63472
+ const loaded = await loadMissionState(deps.projectRoot);
63473
+ if (!loaded) {
63474
+ throw new MissionResumeError(
63475
+ "nessuna missione da riprendere: .zelari/mission-state.json assente o illeggibile."
63476
+ );
63477
+ }
63478
+ if (!isResumableMission(loaded)) {
63479
+ throw new MissionResumeError(
63480
+ `la missione ${loaded.missionId} \xE8 gi\xE0 completata (status=success).`
63481
+ );
63482
+ }
63483
+ const brief = loaded.brief;
63484
+ const userMessage = loaded.userPrompt ?? brief.userPromptOriginal;
63485
+ deps.emit(
63486
+ `[zelari] resume missione ${loaded.missionId} \u2014 step ${loaded.iteration}, slice ${loaded.currentSliceId}, status precedente ${loaded.status}`
63487
+ );
63488
+ return driveMission(userMessage, brief, deps, loaded, true);
63489
+ }
63490
+ async function driveMission(userMessage, brief, deps, state3, resumed) {
63491
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
63492
+ const maxIter = deps.maxIterations ?? resolveMaxIterations(deps.env);
63493
+ const maxStall = resolveMaxStall(deps.env);
63494
+ const maxCost = resolveMaxCost(deps.env);
63495
+ const maxTokens = resolveMaxTokens(deps.env);
63496
+ const missionId = state3.missionId;
63497
+ const slices = resolveMissionSlices(brief);
63498
+ let sliceIndex = resumed ? Math.max(
63499
+ 0,
63500
+ slices.findIndex((s) => s.id === state3.currentSliceId)
63501
+ ) : 0;
63502
+ const currentSlice = () => slices[sliceIndex] ?? slices[0];
63503
+ state3.currentSliceId = currentSlice().id;
63504
+ state3.status = "running";
63505
+ state3.updatedAt = now().toISOString();
63060
63506
  await deps.memory.init(deps.projectRoot);
63061
63507
  const persist = async () => {
63062
63508
  await writeMissionState(deps.projectRoot, state3);
63063
63509
  await deps.onStatePersisted?.(state3);
63064
63510
  };
63065
- deps.onMissionPhase?.("design", "mission-start");
63511
+ deps.onMissionPhase?.(
63512
+ resumed ? "build" : "design",
63513
+ resumed ? "mission-resume" : "mission-start"
63514
+ );
63066
63515
  await persist();
63067
63516
  const stateStore = deps.stateStore ?? await getStateStore(deps.projectRoot, deps.env ?? process.env);
63068
63517
  try {
@@ -63070,7 +63519,7 @@ async function runZelariMission(userMessage, brief, deps) {
63070
63519
  } catch {
63071
63520
  }
63072
63521
  let missionCheckpointId;
63073
- if ((deps.env ?? process.env).ZELARI_CHECKPOINT !== "0") {
63522
+ if (!resumed && (deps.env ?? process.env).ZELARI_CHECKPOINT !== "0") {
63074
63523
  const cp = await createCheckpoint(deps.projectRoot, `zelari mission ${missionId}`);
63075
63524
  if (cp.ok) {
63076
63525
  missionCheckpointId = cp.value.id;
@@ -63081,11 +63530,11 @@ async function runZelariMission(userMessage, brief, deps) {
63081
63530
  }
63082
63531
  const designFirst = brief.phases[0]?.mode === "design-phase";
63083
63532
  let noWriteStreak = 0;
63084
- let step = 0;
63085
- let implStep = 0;
63086
- let pendingDesign = designFirst;
63087
- let cumulativeCostUsd = 0;
63088
- let cumulativeTokens = 0;
63533
+ let step = resumed ? state3.iteration : 0;
63534
+ let implStep = resumed ? countImplementationSlices(state3) : 0;
63535
+ let pendingDesign = designFirst && !resumed;
63536
+ let cumulativeCostUsd = resumed ? state3.cumulativeCostUsd ?? 0 : 0;
63537
+ let cumulativeTokens = resumed ? state3.cumulativeTokens ?? 0 : 0;
63089
63538
  const repairHistory = Array.isArray(state3.repairHistory) ? [...state3.repairHistory] : [];
63090
63539
  let forcePivot = false;
63091
63540
  const missionStartMs = now().getTime();
@@ -63113,25 +63562,25 @@ async function runZelariMission(userMessage, brief, deps) {
63113
63562
  });
63114
63563
  const ragContext = formatMemoryHits(hits);
63115
63564
  const promptIter = runMode === "implementation" ? implStep : 1;
63116
- const slicePrompt = buildSlicePrompt(brief, userMessage, runMode, promptIter);
63565
+ const slicePrompt = buildSlicePrompt(brief, userMessage, runMode, promptIter, currentSlice());
63117
63566
  const implementerRetry = runMode === "implementation" && (implStep > 1 || forcePivot);
63118
63567
  const sliceStartedAt = now().toISOString();
63119
63568
  const sliceStartMs = now().getTime();
63120
63569
  if (runMode === "design-phase") {
63121
63570
  deps.emit(
63122
- `[zelari] design-phase (fuori budget) \xB7 step ${step} \xB7 slice ${brief.sliceMvp.id}`
63571
+ `[zelari] design-phase (fuori budget) \xB7 step ${step} \xB7 slice ${currentSlice().id}`
63123
63572
  );
63124
63573
  } else if (deps.buildViaAgent) {
63125
63574
  deps.emit(
63126
- `[zelari] implementazione ${implStep}/${maxIter} \xB7 step ${step} \xB7 build@agent \xB7 slice ${brief.sliceMvp.id}`
63575
+ `[zelari] implementazione ${implStep}/${maxIter} \xB7 step ${step} \xB7 build@agent \xB7 slice ${currentSlice().id}`
63127
63576
  );
63128
63577
  } else if (implementerRetry) {
63129
63578
  deps.emit(
63130
- `[zelari] implementazione ${implStep}/${maxIter} \xB7 step ${step} \xB7 roster ridotto (Minosse+Lucifero) \xB7 slice ${brief.sliceMvp.id}`
63579
+ `[zelari] implementazione ${implStep}/${maxIter} \xB7 step ${step} \xB7 roster ridotto (Minosse+Lucifero) \xB7 slice ${currentSlice().id}`
63131
63580
  );
63132
63581
  } else {
63133
63582
  deps.emit(
63134
- `[zelari] implementazione ${implStep}/${maxIter} \xB7 step ${step} \xB7 council completo \xB7 slice ${brief.sliceMvp.id}`
63583
+ `[zelari] implementazione ${implStep}/${maxIter} \xB7 step ${step} \xB7 council completo \xB7 slice ${currentSlice().id}`
63135
63584
  );
63136
63585
  }
63137
63586
  let result;
@@ -63173,7 +63622,7 @@ async function runZelariMission(userMessage, brief, deps) {
63173
63622
  {
63174
63623
  projectRoot: deps.projectRoot,
63175
63624
  missionId,
63176
- sliceId: brief.sliceMvp.id,
63625
+ sliceId: currentSlice().id,
63177
63626
  source: "council",
63178
63627
  iteration: step,
63179
63628
  memoryKind: result.completionOk ? "outcome" : runMode === "design-phase" ? "decision" : "episode",
@@ -63197,7 +63646,7 @@ async function runZelariMission(userMessage, brief, deps) {
63197
63646
  state3.cumulativeTokens = cumulativeTokens;
63198
63647
  if (!state3.trace) state3.trace = [];
63199
63648
  state3.trace.push({
63200
- sliceId: brief.sliceMvp.id,
63649
+ sliceId: currentSlice().id,
63201
63650
  iteration: step,
63202
63651
  runMode,
63203
63652
  completionOk,
@@ -63222,7 +63671,7 @@ async function runZelariMission(userMessage, brief, deps) {
63222
63671
  env: deps.env,
63223
63672
  mode: "zelari",
63224
63673
  layer: hard ? `mission:impl-${implStep}` : `mission:progress-${implStep}`,
63225
- label: hard ? `zelari ${brief.sliceMvp.id} impl ${implStep} verified` : `zelari ${brief.sliceMvp.id} progress impl ${implStep}`,
63674
+ label: hard ? `zelari ${currentSlice().id} impl ${implStep} verified` : `zelari ${currentSlice().id} progress impl ${implStep}`,
63226
63675
  sessionId: missionId,
63227
63676
  verification: { ok: hard, ran: result.ran },
63228
63677
  force: !hard,
@@ -63261,12 +63710,24 @@ async function runZelariMission(userMessage, brief, deps) {
63261
63710
  budget: { iterationsUsed: implStep, iterationsMax: maxIter }
63262
63711
  });
63263
63712
  deps.onMissionProgress?.(advice, step);
63713
+ if (completionOk && sliceIndex < slices.length - 1) {
63714
+ const doneSlice = currentSlice();
63715
+ sliceIndex += 1;
63716
+ state3.currentSliceId = currentSlice().id;
63717
+ state3.updatedAt = now().toISOString();
63718
+ await persist();
63719
+ deps.emit(
63720
+ `[zelari] \u2713 incremento ${sliceIndex}/${slices.length} verde (${doneSlice.id}) \u2014 avvio ${currentSlice().id}`
63721
+ );
63722
+ continue;
63723
+ }
63264
63724
  if (completionOk) {
63265
63725
  state3.status = "success";
63266
63726
  deps.onMissionPhase?.("done", "mvp-green");
63267
63727
  await persist();
63728
+ const sliceLabel = slices.length === 1 ? "slice MVP" : `ultimo incremento (${currentSlice().id})`;
63268
63729
  deps.emit(
63269
- `[zelari] \u2713 missione completata \u2014 slice MVP verde all'implementazione ${implStep}/${maxIter} (step ${step}).`
63730
+ `[zelari] \u2713 missione completata \u2014 ${sliceLabel} verde all'implementazione ${implStep}/${maxIter} (step ${step}).`
63270
63731
  );
63271
63732
  return state3;
63272
63733
  }
@@ -63344,7 +63805,7 @@ async function runZelariMission(userMessage, brief, deps) {
63344
63805
  );
63345
63806
  return state3;
63346
63807
  }
63347
- var DEFAULT_MAX_ITER, DEFAULT_MAX_STALL;
63808
+ var DEFAULT_MAX_ITER, DEFAULT_MAX_STALL, MissionResumeError;
63348
63809
  var init_zelariMission = __esm({
63349
63810
  "src/cli/zelariMission.ts"() {
63350
63811
  "use strict";
@@ -63357,6 +63818,12 @@ var init_zelariMission = __esm({
63357
63818
  init_traceStore();
63358
63819
  DEFAULT_MAX_ITER = 6;
63359
63820
  DEFAULT_MAX_STALL = 2;
63821
+ MissionResumeError = class extends Error {
63822
+ constructor(message) {
63823
+ super(message);
63824
+ this.name = "MissionResumeError";
63825
+ }
63826
+ };
63360
63827
  }
63361
63828
  });
63362
63829
 
@@ -65494,6 +65961,16 @@ function handleProviderPicker(ctx, openPicker) {
65494
65961
  function handleProviderCustom(ctx, opts) {
65495
65962
  const id3 = ctx.activeProviderSpec.id;
65496
65963
  try {
65964
+ if (opts.apiStyle) {
65965
+ if (id3 === "chatgpt" || id3 === "anthropic") {
65966
+ appendSystem(ctx.setMessages, `[provider] ${id3} has a fixed transport \u2014 nothing to select.`);
65967
+ return;
65968
+ }
65969
+ setApiStyleFor(id3, opts.apiStyle);
65970
+ const target = opts.apiStyle === "responses" ? "POST /responses" : "POST /chat/completions";
65971
+ appendSystem(ctx.setMessages, `[provider] ${id3} endpoint style set to ${opts.apiStyle} (${target})`);
65972
+ return;
65973
+ }
65497
65974
  if (opts.clear) {
65498
65975
  clearCustomEndpoint(id3);
65499
65976
  appendSystem(ctx.setMessages, `[provider] cleared custom endpoint for ${id3} \u2014 falling back to default`);
@@ -68804,6 +69281,76 @@ var init_run = __esm({
68804
69281
  }
68805
69282
  });
68806
69283
 
69284
+ // src/cli/evolution/runTelemetry.ts
69285
+ var runTelemetry_exports = {};
69286
+ __export(runTelemetry_exports, {
69287
+ RunTelemetryAccumulator: () => RunTelemetryAccumulator
69288
+ });
69289
+ var RunTelemetryAccumulator;
69290
+ var init_runTelemetry = __esm({
69291
+ "src/cli/evolution/runTelemetry.ts"() {
69292
+ "use strict";
69293
+ RunTelemetryAccumulator = class {
69294
+ constructor(meta3 = {}) {
69295
+ this.meta = meta3;
69296
+ }
69297
+ totals = {
69298
+ inputTokens: 0,
69299
+ outputTokens: 0,
69300
+ cacheHitTokens: 0,
69301
+ toolCalls: 0,
69302
+ usageReports: 0
69303
+ };
69304
+ /** Mirror one dispatch/spine event. Never throws on unknown shapes. */
69305
+ observe(ev) {
69306
+ if (!ev || typeof ev !== "object" || !("type" in ev)) return;
69307
+ const e = ev;
69308
+ if (e["type"] === "tool_execution_end") {
69309
+ this.totals.toolCalls += 1;
69310
+ return;
69311
+ }
69312
+ if (e["type"] !== "message_end") return;
69313
+ const usage = e["usage"];
69314
+ if (!usage || typeof usage !== "object") return;
69315
+ const u = usage;
69316
+ if (typeof u["promptTokens"] === "number") this.totals.inputTokens += u["promptTokens"];
69317
+ if (typeof u["completionTokens"] === "number") this.totals.outputTokens += u["completionTokens"];
69318
+ if (typeof u["cachedPromptTokens"] === "number") this.totals.cacheHitTokens += u["cachedPromptTokens"];
69319
+ this.totals.usageReports += 1;
69320
+ }
69321
+ /** Cumulative totals (defensive copy). */
69322
+ usage() {
69323
+ return { ...this.totals };
69324
+ }
69325
+ /**
69326
+ * Final NDJSON `usage` event for JSON hosts (Desktop, competitive bench,
69327
+ * anchor runner). Emitted once per run, after the dispatch stream ends —
69328
+ * `tools/eval/competitive/adapters.ts#parseZelariUsage` reads exactly this
69329
+ * flat `{ inputTokens, outputTokens, cacheHitTokens, model?, provider? }`
69330
+ * shape, so the bench stops recording `tokens: null` with no bench change.
69331
+ */
69332
+ usageEvent() {
69333
+ return {
69334
+ type: "usage",
69335
+ ...this.usage(),
69336
+ ...this.meta.model ? { model: this.meta.model } : {},
69337
+ ...this.meta.provider ? { provider: this.meta.provider } : {}
69338
+ };
69339
+ }
69340
+ /**
69341
+ * Ledger projection: toolCalls always (event-countable), token fields ONLY
69342
+ * when backed by ≥1 provider usage report (unknown ≠ estimated ≠ zero).
69343
+ */
69344
+ ledgerFields() {
69345
+ return {
69346
+ toolCalls: this.totals.toolCalls,
69347
+ ...this.totals.usageReports > 0 ? { inputTokens: this.totals.inputTokens, outputTokens: this.totals.outputTokens } : {}
69348
+ };
69349
+ }
69350
+ };
69351
+ }
69352
+ });
69353
+
68807
69354
  // src/cli/triggerLock.ts
68808
69355
  var triggerLock_exports = {};
68809
69356
  __export(triggerLock_exports, {
@@ -69408,6 +69955,9 @@ async function runHeadlessCouncilBody(opts, provider, model, providerStream, ext
69408
69955
  const scrub = createStreamScrubber2({ stripQuestion: opts.output !== "json" });
69409
69956
  let lastAssistantText = "";
69410
69957
  let currentAssistantText = "";
69958
+ const { RunTelemetryAccumulator: RunTelemetryAccumulator2 } = await Promise.resolve().then(() => (init_runTelemetry(), runTelemetry_exports));
69959
+ const telemetry = new RunTelemetryAccumulator2({ model, provider });
69960
+ const councilStartedAt = Date.now();
69411
69961
  try {
69412
69962
  const { composeProjectContext: composeProjectContext2 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
69413
69963
  const { loadDurableContext: loadDurableContext2 } = await Promise.resolve().then(() => (init_loadDurableContext(), loadDurableContext_exports));
@@ -69463,6 +70013,7 @@ async function runHeadlessCouncilBody(opts, provider, model, providerStream, ext
69463
70013
  currentAssistantText = "";
69464
70014
  }
69465
70015
  spine.observe(event);
70016
+ telemetry.observe(event);
69466
70017
  if (event.type === "message_delta" && typeof event.delta === "string") {
69467
70018
  const cleanDelta = scrub.push(event.delta);
69468
70019
  if (cleanDelta.length > 0) currentAssistantText += cleanDelta;
@@ -69490,6 +70041,9 @@ async function runHeadlessCouncilBody(opts, provider, model, providerStream, ext
69490
70041
  }
69491
70042
  }
69492
70043
  }
70044
+ if (opts.output === "json") {
70045
+ emitEvent(telemetry.usageEvent());
70046
+ }
69493
70047
  } catch (err) {
69494
70048
  process.stderr.write(
69495
70049
  `[zelari-code --headless] council error: ${err instanceof Error ? err.message : String(err)}
@@ -69513,6 +70067,12 @@ async function runHeadlessCouncilBody(opts, provider, model, providerStream, ext
69513
70067
  at: (/* @__PURE__ */ new Date()).toISOString(),
69514
70068
  mode: "shadow",
69515
70069
  taskClass: classifyTask2({ prompt: effectiveTask }).taskClass,
70070
+ // Steal #1/#2 wiring: real efficiency + attribution fields — the
70071
+ // ledger schema carried them since ADR-0036, this site never wrote them.
70072
+ latencyMs: Date.now() - councilStartedAt,
70073
+ model,
70074
+ provider,
70075
+ ...telemetry.ledgerFields(),
69516
70076
  verdict: signal.aborted ? "UNKNOWN" : exitCode === 0 ? "PASS" : exitCode === 3 ? "FAIL" : "UNKNOWN"
69517
70077
  });
69518
70078
  }
@@ -69580,14 +70140,17 @@ async function runHeadlessZelariBody(opts, provider, model, providerStream, extr
69580
70140
  spine.missionPhase("design", "mission-start");
69581
70141
  const { buildMissionBrief: buildMissionBrief2 } = await Promise.resolve().then(() => (init_council(), council_exports));
69582
70142
  const { hasWorkspacePlan: hasWorkspacePlan2 } = await Promise.resolve().then(() => (init_planDetect(), planDetect_exports));
70143
+ const { listOpenPlanTaskIds: listOpenPlanTaskIds2 } = await Promise.resolve().then(() => (init_planStore(), planStore_exports));
69583
70144
  const { getMemoryBackend: getMemoryBackend2 } = await Promise.resolve().then(() => (init_fileBackend(), fileBackend_exports));
69584
70145
  const { runZelariMission: runZelariMission2 } = await Promise.resolve().then(() => (init_zelariMission(), zelariMission_exports));
69585
70146
  const { dispatchCouncil: dispatchCouncil2 } = await Promise.resolve().then(() => (init_councilDispatcher(), councilDispatcher_exports));
69586
70147
  const { FeedbackStore: FeedbackStore2 } = await Promise.resolve().then(() => (init_councilFeedback(), councilFeedback_exports));
69587
70148
  const { runPostCouncilHook: runPostCouncilHook2 } = await Promise.resolve().then(() => (init_postCouncilHook(), postCouncilHook_exports));
70149
+ const planTaskIds = await listOpenPlanTaskIds2(projectRoot);
69588
70150
  const brief = buildMissionBrief2({
69589
70151
  userMessage: opts.task,
69590
- hasPlan: hasWorkspacePlan2(projectRoot)
70152
+ hasPlan: hasWorkspacePlan2(projectRoot),
70153
+ planTaskIds
69591
70154
  });
69592
70155
  const memory = await getMemoryBackend2(
69593
70156
  projectRoot,
@@ -69672,7 +70235,9 @@ ${JSON.stringify({ deliverable: brief.deliverableThisMission, mvp: brief.sliceMv
69672
70235
  }
69673
70236
  }
69674
70237
  try {
69675
- const state3 = await runZelariMission2(missionTask, brief, {
70238
+ const { resumeZelariMission: resumeZelariMission2 } = await Promise.resolve().then(() => (init_zelariMission(), zelariMission_exports));
70239
+ const runMission = (missionDeps) => opts.resumeMission ? resumeZelariMission2(missionDeps) : runZelariMission2(missionTask, brief, missionDeps);
70240
+ const state3 = await runMission({
69676
70241
  projectRoot,
69677
70242
  memory,
69678
70243
  emit,
@@ -70095,6 +70660,7 @@ function parseSetConfigFlags(argv) {
70095
70660
  let model;
70096
70661
  let endpoint;
70097
70662
  let thinking;
70663
+ let apiStyle;
70098
70664
  let endpointClear = false;
70099
70665
  let verifierProvider;
70100
70666
  let verifierModel;
@@ -70113,6 +70679,9 @@ function parseSetConfigFlags(argv) {
70113
70679
  } else if (arg === "--thinking") {
70114
70680
  thinking = argv[i + 1];
70115
70681
  i++;
70682
+ } else if (arg === "--api-style") {
70683
+ apiStyle = argv[i + 1];
70684
+ i++;
70116
70685
  } else if (arg === "--endpoint-clear") {
70117
70686
  endpointClear = true;
70118
70687
  } else if (arg === "--verifier-provider") {
@@ -70125,7 +70694,7 @@ function parseSetConfigFlags(argv) {
70125
70694
  verifierClear = true;
70126
70695
  }
70127
70696
  }
70128
- if (!provider && !model && !endpoint && !endpointClear && !thinking && !verifierProvider && !verifierModel && !verifierClear) {
70697
+ if (!provider && !model && !endpoint && !endpointClear && !thinking && !apiStyle && !verifierProvider && !verifierModel && !verifierClear) {
70129
70698
  return {
70130
70699
  request: null,
70131
70700
  error: "--set-config: nothing to update \u2014 provide at least one of --provider, --model, --endpoint, --thinking, --verifier-provider + --verifier-model, --verifier-clear, or --endpoint-clear"
@@ -70140,6 +70709,9 @@ function parseSetConfigFlags(argv) {
70140
70709
  if (endpoint !== void 0 && endpoint.trim().length === 0) {
70141
70710
  return { request: null, error: "--endpoint cannot be empty" };
70142
70711
  }
70712
+ if (apiStyle !== void 0 && apiStyle !== "chat" && apiStyle !== "responses") {
70713
+ return { request: null, error: "invalid --api-style " + apiStyle + " (use chat or responses)" };
70714
+ }
70143
70715
  if (verifierClear && (verifierProvider || verifierModel)) {
70144
70716
  return { request: null, error: "--verifier-clear conflicts with --verifier-provider/--verifier-model" };
70145
70717
  }
@@ -70164,6 +70736,7 @@ function parseSetConfigFlags(argv) {
70164
70736
  model: model?.trim(),
70165
70737
  endpoint: endpoint?.trim(),
70166
70738
  endpointClear: endpointClear || void 0,
70739
+ apiStyle,
70167
70740
  thinking: thinking?.trim().toLowerCase(),
70168
70741
  verifierProvider: verifierProvider?.trim(),
70169
70742
  verifierModel: verifierModel?.trim(),
@@ -70243,6 +70816,7 @@ function buildDesktopConfigSnapshot() {
70243
70816
  models,
70244
70817
  defaultModel,
70245
70818
  endpoint: custom2 ?? null,
70819
+ apiStyle: p3.id === "anthropic" || p3.id === "chatgpt" ? void 0 : getApiStyleFor(p3.id),
70246
70820
  baseUrl: custom2 ?? builtin,
70247
70821
  authKind: !hasKey ? "none" : oauth ? "oauth" : "api_key",
70248
70822
  expiresAt: stored?.expiresAt ?? null,
@@ -70289,6 +70863,9 @@ function applySetConfig(req) {
70289
70863
  if (req.endpoint) {
70290
70864
  setCustomEndpoint(targetProvider, req.endpoint);
70291
70865
  }
70866
+ if (req.apiStyle) {
70867
+ setApiStyleFor(targetProvider, req.apiStyle);
70868
+ }
70292
70869
  if (req.model) {
70293
70870
  setModelForProvider(targetProvider, req.model);
70294
70871
  }
@@ -79074,11 +79651,14 @@ async function dispatchZelariPromptImpl(text, deps, pendingRef) {
79074
79651
  }
79075
79652
  const { buildMissionBrief: buildMissionBrief2 } = await Promise.resolve().then(() => (init_council(), council_exports));
79076
79653
  const { hasWorkspacePlan: hasWorkspacePlan2 } = await Promise.resolve().then(() => (init_planDetect(), planDetect_exports));
79654
+ const { listOpenPlanTaskIds: listOpenPlanTaskIds2 } = await Promise.resolve().then(() => (init_planStore(), planStore_exports));
79077
79655
  const { formatBriefForChat: formatBriefForChat2, isMissionAutoStart: isMissionAutoStart2 } = await Promise.resolve().then(() => (init_zelariMission(), zelariMission_exports));
79078
79656
  const projectRoot = process.cwd();
79657
+ const planTaskIds = await listOpenPlanTaskIds2(projectRoot);
79079
79658
  const brief = buildMissionBrief2({
79080
79659
  userMessage: text,
79081
- hasPlan: hasWorkspacePlan2(projectRoot)
79660
+ hasPlan: hasWorkspacePlan2(projectRoot),
79661
+ planTaskIds
79082
79662
  });
79083
79663
  emit(formatBriefForChat2(brief));
79084
79664
  if (isMissionAutoStart2()) {
@@ -79104,11 +79684,14 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
79104
79684
  const projectRoot = process.cwd();
79105
79685
  const { buildMissionBrief: buildMissionBrief2 } = await Promise.resolve().then(() => (init_council(), council_exports));
79106
79686
  const { hasWorkspacePlan: hasWorkspacePlan2 } = await Promise.resolve().then(() => (init_planDetect(), planDetect_exports));
79687
+ const { listOpenPlanTaskIds: listOpenPlanTaskIds2 } = await Promise.resolve().then(() => (init_planStore(), planStore_exports));
79107
79688
  const { getMemoryBackend: getMemoryBackend2 } = await Promise.resolve().then(() => (init_fileBackend(), fileBackend_exports));
79108
79689
  const { runZelariMission: runZelariMission2 } = await Promise.resolve().then(() => (init_zelariMission(), zelariMission_exports));
79690
+ const planTaskIds = await listOpenPlanTaskIds2(projectRoot);
79109
79691
  const brief = buildMissionBrief2({
79110
79692
  userMessage,
79111
- hasPlan: hasWorkspacePlan2(projectRoot)
79693
+ hasPlan: hasWorkspacePlan2(projectRoot),
79694
+ planTaskIds
79112
79695
  });
79113
79696
  const missionSpineHolder = {
79114
79697
  get current() {
@@ -79657,6 +80240,24 @@ ${formatSkillList(availableSkills)}`
79657
80240
  customEndpoint: url2
79658
80241
  };
79659
80242
  }
80243
+ if (subcommand === "api") {
80244
+ const target = args[1];
80245
+ if (!target || target === "show") {
80246
+ return {
80247
+ handled: true,
80248
+ kind: "provider_custom",
80249
+ message: "Usage: /provider api chat \u2014 POST /chat/completions (default)\n /provider api responses \u2014 POST /responses (OpenAI Responses API)\nApplies to the active provider; chatgpt/anthropic have a fixed transport."
80250
+ };
80251
+ }
80252
+ if (target !== "chat" && target !== "responses") {
80253
+ return {
80254
+ handled: true,
80255
+ kind: "provider_custom",
80256
+ message: `[provider] unknown api style: ${target}. Use: chat | responses`
80257
+ };
80258
+ }
80259
+ return { handled: true, kind: "provider_custom", apiStyle: target };
80260
+ }
79660
80261
  const providerId = subcommand;
79661
80262
  const sub = args[1];
79662
80263
  if (sub === "refresh") {
@@ -81013,27 +81614,27 @@ function parseCsv(text) {
81013
81614
  records.pop();
81014
81615
  }
81015
81616
  if (records.length === 0) return { headers: [], rows: [] };
81016
- const headers2 = records[0];
81617
+ const headers3 = records[0];
81017
81618
  const rows = records.slice(1).map((r) => {
81018
81619
  const obj = {};
81019
- for (let i = 0; i < headers2.length; i++) obj[headers2[i]] = r[i] ?? "";
81620
+ for (let i = 0; i < headers3.length; i++) obj[headers3[i]] = r[i] ?? "";
81020
81621
  return obj;
81021
81622
  });
81022
- return { headers: headers2, rows };
81623
+ return { headers: headers3, rows };
81023
81624
  }
81024
81625
  function applyTemplate(template, row) {
81025
81626
  return template.replace(/\{([a-zA-Z_][\w-]*)\}/g, (_, k) => row[k] ?? "");
81026
81627
  }
81027
- function serializeCsv(headers2, rows) {
81628
+ function serializeCsv(headers3, rows) {
81028
81629
  const escape = (v) => {
81029
81630
  if (v.includes(",") || v.includes("\n") || v.includes('"')) {
81030
81631
  return `"${v.replace(/"/g, '""')}"`;
81031
81632
  }
81032
81633
  return v;
81033
81634
  };
81034
- const out = [headers2.map(escape).join(",")];
81635
+ const out = [headers3.map(escape).join(",")];
81035
81636
  for (const row of rows) {
81036
- out.push(headers2.map((h) => escape(row[h] ?? "")).join(","));
81637
+ out.push(headers3.map((h) => escape(row[h] ?? "")).join(","));
81037
81638
  }
81038
81639
  return out.join("\n") + "\n";
81039
81640
  }
@@ -81047,21 +81648,21 @@ async function runCsvFanout(args, deps, opts) {
81047
81648
  const start = Date.now();
81048
81649
  const absCsv = path80.isAbsolute(args.csv_path) ? args.csv_path : path80.join(opts.parentCwd, args.csv_path);
81049
81650
  const absOut = path80.isAbsolute(args.output_csv_path) ? args.output_csv_path : path80.join(opts.parentCwd, args.output_csv_path);
81050
- const { headers: headers2, rows } = await readCsv(absCsv);
81051
- if (headers2.length === 0) {
81651
+ const { headers: headers3, rows } = await readCsv(absCsv);
81652
+ if (headers3.length === 0) {
81052
81653
  throw new Error(`kraken_csv_fanout: ${absCsv} is empty`);
81053
81654
  }
81054
81655
  if (!args.id_column) {
81055
81656
  throw new Error("kraken_csv_fanout: id_column is required");
81056
81657
  }
81057
- if (!headers2.includes(args.id_column)) {
81058
- throw new Error(`kraken_csv_fanout: id_column "${args.id_column}" not in CSV header [${headers2.join(", ")}]`);
81658
+ if (!headers3.includes(args.id_column)) {
81659
+ throw new Error(`kraken_csv_fanout: id_column "${args.id_column}" not in CSV header [${headers3.join(", ")}]`);
81059
81660
  }
81060
81661
  const concurrency = args.max_concurrency ?? resolveMaxConcurrency();
81061
81662
  opts.onLog?.(`csv fanout: ${rows.length} rows \xD7 ${args.agent_kind} @ concurrency=${concurrency}`);
81062
81663
  const perRowMs = args.max_runtime_seconds !== void 0 ? args.max_runtime_seconds * 1e3 : args.agent_kind === "general" ? TASK_TOOL_TIMEOUT_MS : 3e5;
81063
81664
  const outputRecords = rows.map((r) => ({ ...r, status: "pending", result: "", error: "" }));
81064
- const outHeaders = [...headers2, "status", "result", "error"];
81665
+ const outHeaders = [...headers3, "status", "result", "error"];
81065
81666
  let writeChain2 = Promise.resolve();
81066
81667
  function queueWrite(contents) {
81067
81668
  const next = writeChain2.then(() => atomicWrite(absOut, contents));
@@ -82422,6 +83023,7 @@ function useSlashDispatch(params) {
82422
83023
  handleProviderCustom(providerCtx, {
82423
83024
  endpoint: result.customEndpoint,
82424
83025
  clear: result.customClear,
83026
+ apiStyle: result.apiStyle,
82425
83027
  message: result.message
82426
83028
  });
82427
83029
  setInput("");
@@ -84381,7 +84983,7 @@ proposals: npm run evolve:propose \u2014 decisions in npm run evolve:decide (P1:
84381
84983
  }
84382
84984
  if (argv.includes("--help") || argv.includes("-h")) {
84383
84985
  console.log(
84384
- "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor [--json] Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --task-file <path> Read the task prompt from a file (Windows argv cap)\n --once Single-cycle run (cron/git-hook triggers, ADR-0014)\n --kraken-graph <goal> Plan + execute a Kraken task graph\n (variant: --kraken-graph-file <path>; kill-switch\n ZELARI_KRAKEN_GRAPH=0)\n --plan-only Serialize the graph plan to .zelari/radio/ and exit 0\n --run-plan <id> Execute a pre-built .zelari/radio/plan-<id>.json\n --gauntlet Host-driven gauntlet loop (builder/critic tentacles)\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --serve-harness Long-lived harness kernel for hosts (NDJSON JSON-RPC\n on stdin/stdout; Desktop/companion transport)\n --print-config Print provider/model config as JSON (no secrets)\n --print-settings Print zelari.config.json values + the origin of\n each (default < user < project < env)\n --permissions <p> Permission preset: strict | standard | yolo \u2014 changes\n category DEFAULTS only (env vars and policy files win)\n --evolve-status Evolution ledger stats (read-only; ADR-0036; the\n ledger is written only when ZELARI_EVOLUTION=shadow)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable for stdio servers\n --url <endpoint> Streamable HTTP endpoint (e.g. UE 5.8 editor)\n --args <json> JSON array of args (stdio, optional)\n --timeout <ms> Per-server request timeout (http, optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --preset unreal-mcp Unreal Engine 5.8+ editor (MCP over HTTP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ZELARI_MEMORY_MCP=1 Enable the optional external memory MCP server\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
84986
+ "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor [--json] Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n zelari/mission mode auto-scopes slices from open tasks in .zelari/plan.json\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --resume-mission Resume .zelari/mission-state.json (not the spine)\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --task-file <path> Read the task prompt from a file (Windows argv cap)\n --once Single-cycle run (cron/git-hook triggers, ADR-0014)\n --kraken-graph <goal> Plan + execute a Kraken task graph\n (variant: --kraken-graph-file <path>; kill-switch\n ZELARI_KRAKEN_GRAPH=0)\n --plan-only Serialize the graph plan to .zelari/radio/ and exit 0\n --run-plan <id> Execute a pre-built .zelari/radio/plan-<id>.json\n --gauntlet Host-driven gauntlet loop (builder/critic tentacles)\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --serve-harness Long-lived harness kernel for hosts (NDJSON JSON-RPC\n on stdin/stdout; Desktop/companion transport)\n --print-config Print provider/model config as JSON (no secrets)\n --print-settings Print zelari.config.json values + the origin of\n each (default < user < project < env)\n --permissions <p> Permission preset: strict | standard | yolo \u2014 changes\n category DEFAULTS only (env vars and policy files win)\n --evolve-status Evolution ledger stats (read-only; ADR-0036; the\n ledger is written only when ZELARI_EVOLUTION=shadow)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable for stdio servers\n --url <endpoint> Streamable HTTP endpoint (e.g. UE 5.8 editor)\n --args <json> JSON array of args (stdio, optional)\n --timeout <ms> Per-server request timeout (http, optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --preset unreal-mcp Unreal Engine 5.8+ editor (MCP over HTTP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ZELARI_MEMORY_MCP=1 Enable the optional external memory MCP server\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
84385
84987
  );
84386
84988
  process.exit(0);
84387
84989
  }