archal 0.9.16 → 0.9.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -33917,7 +33917,7 @@ var init_generated_catalog = __esm({
33917
33917
  icon: "AP",
33918
33918
  name: "Apify",
33919
33919
  description: "Actors, runs, datasets, key-value stores, and request queues.",
33920
- toolCount: 43,
33920
+ toolCount: 45,
33921
33921
  transport: "rest"
33922
33922
  },
33923
33923
  {
@@ -33925,7 +33925,7 @@ var init_generated_catalog = __esm({
33925
33925
  icon: "DC",
33926
33926
  name: "Discord",
33927
33927
  description: "Guilds, channels, messages, webhooks, threads, commands, and interaction responses.",
33928
- toolCount: 68,
33928
+ toolCount: 67,
33929
33929
  transport: "both"
33930
33930
  },
33931
33931
  {
@@ -33941,7 +33941,7 @@ var init_generated_catalog = __esm({
33941
33941
  icon: "GW",
33942
33942
  name: "Google Workspace",
33943
33943
  description: "Gmail, Calendar, Drive, Sheets, and Contacts.",
33944
- toolCount: 9,
33944
+ toolCount: 249,
33945
33945
  transport: "both"
33946
33946
  },
33947
33947
  {
@@ -34038,7 +34038,7 @@ var init_src2 = __esm({
34038
34038
  function formatMinutesLabel(minutes) {
34039
34039
  return minutes.toLocaleString("en-US");
34040
34040
  }
34041
- var PLAN_MONTHLY_TWIN_MINUTE_LIMITS, PLAN_CONCURRENT_SESSION_LIMITS, FREE_WORKSPACE_MEMBER_LIMIT, PRO_PLAN_PRICE_USD, FREE_MINUTES, PRO_MINUTES, PRO_EVALS_PER_SEAT, PRO_SESSION_MINUTES_PER_SEAT, PRO_MAX_SEATS, FREE_EVALS_PER_MONTH, PLAN_DISPLAY, PLAN_MAX_SESSION_TTL_SECONDS, MAX_ABSOLUTE_SESSION_LIFETIME_SECONDS, LIFETIME_PERIOD_RESETS_AT, SCENARIO_USAGE_WINDOW_DAYS, SCENARIO_USAGE_WINDOW_MS, SCENARIO_USAGE_WINDOW_SECONDS;
34041
+ var PLAN_MONTHLY_TWIN_MINUTE_LIMITS, PLAN_CONCURRENT_SESSION_LIMITS, FREE_WORKSPACE_MEMBER_LIMIT, PRO_PLAN_PRICE_USD, FREE_MINUTES, PRO_MINUTES, PRO_EVALS_PER_SEAT, PRO_SESSION_MINUTES_PER_SEAT, PRO_MAX_SEATS, FREE_EVALS_PER_MONTH, PLAN_DISPLAY, PLAN_MAX_SESSION_TTL_SECONDS, MAX_ABSOLUTE_SESSION_LIFETIME_SECONDS, LIFETIME_PERIOD_RESETS_AT, SCENARIO_USAGE_WINDOW_DAYS, SCENARIO_USAGE_WINDOW_MS, SCENARIO_USAGE_WINDOW_SECONDS, LLM_PRICING_USD_PER_M_TOKENS, LLM_PRICING_FAMILY_RATES;
34042
34042
  var init_src3 = __esm({
34043
34043
  "../packages/billing-constants/src/index.ts"() {
34044
34044
  "use strict";
@@ -34118,6 +34118,51 @@ var init_src3 = __esm({
34118
34118
  SCENARIO_USAGE_WINDOW_DAYS = 7;
34119
34119
  SCENARIO_USAGE_WINDOW_MS = SCENARIO_USAGE_WINDOW_DAYS * 24 * 60 * 60 * 1e3;
34120
34120
  SCENARIO_USAGE_WINDOW_SECONDS = SCENARIO_USAGE_WINDOW_DAYS * 24 * 60 * 60;
34121
+ LLM_PRICING_USD_PER_M_TOKENS = {
34122
+ // Google Gemini (verified 2026-05-05, standard tier <=200K input tokens)
34123
+ "gemini-2.5-pro": { input: 1.25, output: 10 },
34124
+ "gemini-2.5-flash": { input: 0.3, output: 2.5 },
34125
+ // OpenAI flagship text models (verified 2026-05-21, standard short-context tier)
34126
+ "gpt-5.5": { input: 5, output: 30 },
34127
+ "gpt-5.5-pro": { input: 30, output: 180 },
34128
+ "gpt-5.4": { input: 2.5, output: 15 },
34129
+ "gpt-5.4-mini": { input: 0.75, output: 4.5 },
34130
+ "gpt-5.4-nano": { input: 0.2, output: 1.25 },
34131
+ "gpt-5.4-pro": { input: 30, output: 180 },
34132
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
34133
+ "gpt-4o": { input: 2.5, output: 10 },
34134
+ "gpt-4.1-mini": { input: 0.4, output: 1.6 },
34135
+ "gpt-4.1-nano": { input: 0.1, output: 0.4 },
34136
+ "gpt-4.1": { input: 2, output: 8 },
34137
+ // DeepSeek (verified 2026-05-05; legacy names map to v4-flash per vendor)
34138
+ "deepseek-chat": { input: 0.14, output: 0.28 },
34139
+ "deepseek-reasoner": { input: 0.14, output: 0.28 },
34140
+ "deepseek-v4-flash": { input: 0.14, output: 0.28 }
34141
+ };
34142
+ LLM_PRICING_FAMILY_RATES = [
34143
+ { match: /^gemini-2\.5-pro/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gemini-2.5-pro"] },
34144
+ { match: /^gemini-2\.5-flash/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gemini-2.5-flash"] },
34145
+ { match: /^gpt-5\.5-pro/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-5.5-pro"] },
34146
+ { match: /^gpt-5\.5/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-5.5"] },
34147
+ { match: /^gpt-5\.4-pro/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-5.4-pro"] },
34148
+ { match: /^gpt-5\.4-mini/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-5.4-mini"] },
34149
+ { match: /^gpt-5\.4-nano/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-5.4-nano"] },
34150
+ { match: /^gpt-5\.4/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-5.4"] },
34151
+ { match: /^gpt-4o-mini/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-4o-mini"] },
34152
+ { match: /^gpt-4o(?!-mini)/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-4o"] },
34153
+ { match: /^gpt-4\.1-mini/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-4.1-mini"] },
34154
+ { match: /^gpt-4\.1-nano/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-4.1-nano"] },
34155
+ { match: /^gpt-4\.1(?!-mini|-nano)/i, rate: LLM_PRICING_USD_PER_M_TOKENS["gpt-4.1"] },
34156
+ { match: /^claude-opus-/i, rate: { input: 15, output: 75 } },
34157
+ { match: /^claude-haiku-/i, rate: { input: 0.8, output: 4 } },
34158
+ { match: /^claude-sonnet-/i, rate: { input: 3, output: 15 } },
34159
+ { match: /^opus-/i, rate: { input: 15, output: 75 } },
34160
+ { match: /^haiku-/i, rate: { input: 0.8, output: 4 } },
34161
+ { match: /^sonnet-/i, rate: { input: 3, output: 15 } },
34162
+ { match: /^deepseek-chat/i, rate: LLM_PRICING_USD_PER_M_TOKENS["deepseek-chat"] },
34163
+ { match: /^deepseek-reasoner/i, rate: LLM_PRICING_USD_PER_M_TOKENS["deepseek-reasoner"] },
34164
+ { match: /^deepseek-v4-flash/i, rate: LLM_PRICING_USD_PER_M_TOKENS["deepseek-v4-flash"] }
34165
+ ];
34121
34166
  }
34122
34167
  });
34123
34168
 
@@ -41904,7 +41949,7 @@ function buildCodegenUserPrompt(twinName, setupDescription) {
41904
41949
  const declarations = SDK_DECLARATIONS[twinName];
41905
41950
  const example = SDK_EXAMPLES[twinName];
41906
41951
  if (!declarations) {
41907
- return `Twin: ${twinName}
41952
+ return `Clone: ${twinName}
41908
41953
 
41909
41954
  Setup:
41910
41955
  ${setupDescription}
@@ -41925,7 +41970,7 @@ ${example.code}
41925
41970
  `;
41926
41971
  }
41927
41972
  prompt += `## Task
41928
- Twin: ${twinName}
41973
+ Clone: ${twinName}
41929
41974
 
41930
41975
  Setup:
41931
41976
  ${setupDescription}
@@ -41951,7 +41996,7 @@ ${originalCode}
41951
41996
  Fix the code and output ONLY the corrected JavaScript. No explanations.`;
41952
41997
  }
41953
41998
  function buildSimplifiedCodegenPrompt(twinName, setupDescription) {
41954
- return `Create seed data for the "${twinName}" twin.
41999
+ return `Create seed data for the "${twinName}" clone.
41955
42000
 
41956
42001
  Use ONLY these core functions (they are already defined globally):
41957
42002
  createUser(login), createRepo/createProject/createChannel/createCustomer(name),
@@ -44428,7 +44473,7 @@ var init_seed_llm = __esm({
44428
44473
 
44429
44474
  // ../packages/seedgen/src/codegen/seed-plan.ts
44430
44475
  function buildPlanPrompt(twinName, setupDescription) {
44431
- return `Twin: ${twinName}
44476
+ return `Clone: ${twinName}
44432
44477
 
44433
44478
  Setup:
44434
44479
  ${setupDescription}`;
@@ -50845,6 +50890,222 @@ var init_cache = __esm({
50845
50890
  });
50846
50891
 
50847
50892
  // ../packages/seedgen/src/runner/seed/supabase-sql-seed.ts
50893
+ function sqlLiteral2(value) {
50894
+ if (value === null || value === void 0) return "NULL";
50895
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "NULL";
50896
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
50897
+ return `'${String(value).replace(/'/g, "''")}'`;
50898
+ }
50899
+ function sqlIdent2(value) {
50900
+ if (!value) throw new Error("SQL identifier must not be empty");
50901
+ if (value.includes("\0")) throw new Error("SQL identifier must not contain null bytes");
50902
+ return `"${value.replace(/"/g, '""')}"`;
50903
+ }
50904
+ function sqlValueLiteral(value, columnType) {
50905
+ const type = columnType?.toLowerCase() ?? "";
50906
+ if (Array.isArray(value)) {
50907
+ if (type.includes("[]")) {
50908
+ const baseType = type.replace(/\[\].*$/, "") || "text";
50909
+ if (value.length === 0) return `ARRAY[]::${baseType}[]`;
50910
+ return `ARRAY[${value.map(sqlLiteral2).join(", ")}]`;
50911
+ }
50912
+ const json2 = sqlLiteral2(JSON.stringify(value));
50913
+ if (type.includes("jsonb")) return `${json2}::jsonb`;
50914
+ if (type.includes("json")) return `${json2}::json`;
50915
+ return json2;
50916
+ }
50917
+ if (value && typeof value === "object") {
50918
+ const json2 = sqlLiteral2(JSON.stringify(value));
50919
+ if (type.includes("jsonb")) return `${json2}::jsonb`;
50920
+ if (type.includes("json")) return `${json2}::json`;
50921
+ return json2;
50922
+ }
50923
+ return sqlLiteral2(value);
50924
+ }
50925
+ function splitSqlTopLevel(input, separator) {
50926
+ const parts = [];
50927
+ let depth = 0;
50928
+ let bracketDepth = 0;
50929
+ let inQuote = false;
50930
+ let start = 0;
50931
+ for (let i = 0; i < input.length; i++) {
50932
+ const ch = input[i];
50933
+ if (ch === void 0) continue;
50934
+ const next = i + 1 < input.length ? input[i + 1] : void 0;
50935
+ if (ch === "'") {
50936
+ if (inQuote && next === "'") {
50937
+ i += 1;
50938
+ continue;
50939
+ }
50940
+ inQuote = !inQuote;
50941
+ continue;
50942
+ }
50943
+ if (inQuote) continue;
50944
+ if (ch === "(") depth += 1;
50945
+ else if (ch === ")") depth = Math.max(0, depth - 1);
50946
+ else if (ch === "[") bracketDepth += 1;
50947
+ else if (ch === "]") bracketDepth = Math.max(0, bracketDepth - 1);
50948
+ if (depth === 0 && bracketDepth === 0 && ch === separator) {
50949
+ parts.push(input.slice(start, i).trim());
50950
+ start = i + 1;
50951
+ }
50952
+ }
50953
+ const tail = input.slice(start).trim();
50954
+ if (tail) parts.push(tail);
50955
+ return parts;
50956
+ }
50957
+ function splitSqlStatements(sql) {
50958
+ return splitSqlTopLevel(sql, ";").map((statement) => statement.trim()).filter(Boolean);
50959
+ }
50960
+ function normalizeSqlIdentifier(raw) {
50961
+ const parts = raw.split(".").map((part) => part.trim().replace(/^"|"$/g, "").replace(/""/g, '"')).filter(Boolean);
50962
+ return parts[parts.length - 1] ?? raw.trim();
50963
+ }
50964
+ function collectTableSchemas(sql) {
50965
+ const tableSchemas = /* @__PURE__ */ new Map();
50966
+ for (const statement of splitSqlStatements(sql)) {
50967
+ const match = statement.match(
50968
+ /^CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+([^\s(]+)\s*\(([\s\S]*)\)$/i
50969
+ );
50970
+ if (!match?.[1] || !match[2]) continue;
50971
+ const columnTypes = /* @__PURE__ */ new Map();
50972
+ const columnOrder = [];
50973
+ for (const columnDef of splitSqlTopLevel(match[2], ",")) {
50974
+ const columnMatch = columnDef.trim().match(/^("(?:""|[^"])+"|[a-zA-Z_][a-zA-Z0-9_]*)\s+([a-zA-Z0-9_."[\]]+)/);
50975
+ if (!columnMatch?.[1] || !columnMatch[2]) continue;
50976
+ const columnName = normalizeSqlIdentifier(columnMatch[1]);
50977
+ columnTypes.set(columnName, columnMatch[2].toLowerCase());
50978
+ columnOrder.push(columnName);
50979
+ }
50980
+ tableSchemas.set(normalizeSqlIdentifier(match[1]), { columnTypes, columnOrder });
50981
+ }
50982
+ return tableSchemas;
50983
+ }
50984
+ function normalizeJsonArrayLiteral(value, columnType) {
50985
+ const trimmed = value.trim();
50986
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return value;
50987
+ let parsed;
50988
+ try {
50989
+ parsed = JSON.parse(trimmed);
50990
+ } catch {
50991
+ return value;
50992
+ }
50993
+ if (!Array.isArray(parsed)) return value;
50994
+ const type = columnType?.toLowerCase() ?? "";
50995
+ if (type.includes("[]")) {
50996
+ const baseType = type.replace(/\[\].*$/, "") || "text";
50997
+ if (parsed.length === 0) return `ARRAY[]::${baseType}[]`;
50998
+ return `ARRAY[${parsed.map(sqlLiteral2).join(", ")}]`;
50999
+ }
51000
+ const json2 = sqlLiteral2(JSON.stringify(parsed));
51001
+ if (type.includes("jsonb")) return `${json2}::jsonb`;
51002
+ if (type.includes("json")) return `${json2}::json`;
51003
+ return json2;
51004
+ }
51005
+ function renderStructuredTableSql(value) {
51006
+ const tables = Array.isArray(value) ? value : value && typeof value === "object" && Array.isArray(value["tables"]) ? value["tables"] : null;
51007
+ if (!Array.isArray(tables)) return null;
51008
+ const statements = [];
51009
+ for (const table2 of tables) {
51010
+ if (!table2 || typeof table2 !== "object") return null;
51011
+ const record2 = table2;
51012
+ const tableName = typeof record2["name"] === "string" ? record2["name"] : void 0;
51013
+ const columns = Array.isArray(record2["columns"]) ? record2["columns"] : void 0;
51014
+ if (!tableName || !columns) return null;
51015
+ const columnTypes = /* @__PURE__ */ new Map();
51016
+ const columnDefinitions = columns.map((column) => {
51017
+ if (!column || typeof column !== "object") throw new Error("invalid column");
51018
+ const columnRecord = column;
51019
+ const name = typeof columnRecord["name"] === "string" ? columnRecord["name"] : void 0;
51020
+ const rawType = typeof columnRecord["type"] === "string" ? columnRecord["type"] : "text";
51021
+ if (!name) throw new Error("invalid column name");
51022
+ const isPrimary = columnRecord["is_primary"] === true || columnRecord["primary"] === true;
51023
+ const type = isPrimary && /^(bigint|int|integer|serial|bigserial)$/i.test(rawType) ? "bigserial" : rawType;
51024
+ columnTypes.set(name, type);
51025
+ return `${sqlIdent2(name)} ${type}${isPrimary ? " PRIMARY KEY" : ""}`;
51026
+ });
51027
+ statements.push(`CREATE TABLE IF NOT EXISTS public.${sqlIdent2(tableName)} (${columnDefinitions.join(", ")});`);
51028
+ const rows = Array.isArray(record2["rows"]) ? record2["rows"] : Array.isArray(record2["data"]) ? record2["data"] : [];
51029
+ for (const row of rows) {
51030
+ if (!row || typeof row !== "object") continue;
51031
+ const entries = Object.entries(row).filter(([column]) => columnTypes.has(column));
51032
+ if (entries.length === 0) continue;
51033
+ statements.push(
51034
+ `INSERT INTO public.${sqlIdent2(tableName)} (${entries.map(([column]) => sqlIdent2(column)).join(", ")}) VALUES (${entries.map(([column, rowValue]) => sqlValueLiteral(rowValue, columnTypes.get(column))).join(", ")});`
51035
+ );
51036
+ }
51037
+ }
51038
+ return statements.length > 0 ? statements.join("\n\n") : null;
51039
+ }
51040
+ function normalizeCreateTableStatement(statement) {
51041
+ const match = statement.match(
51042
+ /^(CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+[^\s(]+\s*)\(([\s\S]*)\)$/i
51043
+ );
51044
+ if (!match?.[1] || !match[2]) return statement;
51045
+ const columnDefs = splitSqlTopLevel(match[2], ",").map((columnDef) => {
51046
+ const columnMatch = columnDef.trim().match(
51047
+ /^("(?:""|[^"])+"|[a-zA-Z_][a-zA-Z0-9_]*)\s+([a-zA-Z0-9_."[\]]+)([\s\S]*)$/i
51048
+ );
51049
+ if (!columnMatch?.[2] || !columnMatch[3]) return columnDef;
51050
+ const columnType = columnMatch[2].toLowerCase();
51051
+ const rest = columnMatch[3].replace(
51052
+ /\bDEFAULT\s+(\[[\s\S]*?\])(?=\s|,|$)/i,
51053
+ (_full, value) => `DEFAULT ${normalizeJsonArrayLiteral(value, columnType)}`
51054
+ );
51055
+ return `${columnMatch[1]} ${columnMatch[2]}${rest}`;
51056
+ });
51057
+ return `${match[1]}(${columnDefs.join(", ")})`;
51058
+ }
51059
+ function normalizeSupabaseSql(sql) {
51060
+ const tableSchemas = collectTableSchemas(sql);
51061
+ return splitSqlStatements(sql).map((statement) => {
51062
+ if (/^CREATE\s+TABLE/i.test(statement)) return normalizeCreateTableStatement(statement);
51063
+ const match = statement.match(/^INSERT\s+INTO\s+([^\s(]+)\s*\(([^)]+)\)\s*VALUES\s*([\s\S]*)$/i) ?? statement.match(/^INSERT\s+INTO\s+([^\s(]+)\s+VALUES\s*([\s\S]*)$/i);
51064
+ if (!match?.[1]) return statement;
51065
+ const tableName = normalizeSqlIdentifier(match[1]);
51066
+ const hasColumnList = match.length === 4;
51067
+ const valuesSql = hasColumnList ? match[3] : match[2];
51068
+ if (!valuesSql) return statement;
51069
+ const schema = tableSchemas.get(tableName);
51070
+ const columns = hasColumnList ? splitSqlTopLevel(match[2] ?? "", ",").map(normalizeSqlIdentifier) : schema?.columnOrder ?? [];
51071
+ const tuples = [];
51072
+ let depth = 0;
51073
+ let bracketDepth = 0;
51074
+ let inQuote = false;
51075
+ let tupleStart = -1;
51076
+ for (let i = 0; i < valuesSql.length; i++) {
51077
+ const ch = valuesSql[i];
51078
+ if (ch === void 0) continue;
51079
+ const next = i + 1 < valuesSql.length ? valuesSql[i + 1] : void 0;
51080
+ if (ch === "'") {
51081
+ if (inQuote && next === "'") {
51082
+ i += 1;
51083
+ continue;
51084
+ }
51085
+ inQuote = !inQuote;
51086
+ }
51087
+ if (inQuote) continue;
51088
+ if (ch === "[") bracketDepth += 1;
51089
+ else if (ch === "]") bracketDepth = Math.max(0, bracketDepth - 1);
51090
+ else if (ch === "(" && bracketDepth === 0) {
51091
+ if (depth === 0) tupleStart = i + 1;
51092
+ depth += 1;
51093
+ } else if (ch === ")" && bracketDepth === 0) {
51094
+ depth -= 1;
51095
+ if (depth === 0 && tupleStart >= 0) {
51096
+ tuples.push(valuesSql.slice(tupleStart, i));
51097
+ tupleStart = -1;
51098
+ }
51099
+ }
51100
+ }
51101
+ if (tuples.length === 0) return statement;
51102
+ const normalizedTuples = tuples.map((tuple2) => {
51103
+ const values = splitSqlTopLevel(tuple2, ",").map((value, index) => normalizeJsonArrayLiteral(value, schema?.columnTypes.get(columns[index] ?? "")));
51104
+ return `(${values.join(", ")})`;
51105
+ });
51106
+ return hasColumnList ? `INSERT INTO ${match[1]} (${match[2]}) VALUES ${normalizedTuples.join(", ")}` : `INSERT INTO ${match[1]} VALUES ${normalizedTuples.join(", ")}`;
51107
+ }).join(";\n") + ";";
51108
+ }
50848
51109
  function extractSupabaseSql(response) {
50849
51110
  let sql = response.trim();
50850
51111
  if (sql.startsWith("```")) {
@@ -50853,24 +51114,35 @@ function extractSupabaseSql(response) {
50853
51114
  if (sql.startsWith("{")) {
50854
51115
  try {
50855
51116
  const parsed = JSON.parse(sql);
51117
+ const structuredSql = renderStructuredTableSql(parsed);
51118
+ if (structuredSql) return normalizeSupabaseSql(structuredSql);
50856
51119
  const extracted = parsed["sql"] ?? parsed["query"] ?? parsed["script"] ?? parsed["content"];
50857
- if (extracted && typeof extracted === "string") return extracted;
51120
+ if (extracted && typeof extracted === "string") return normalizeSupabaseSql(extracted);
51121
+ } catch {
51122
+ }
51123
+ }
51124
+ if (sql.startsWith('"')) {
51125
+ try {
51126
+ const parsed = JSON.parse(sql);
51127
+ if (typeof parsed === "string") return normalizeSupabaseSql(parsed);
50858
51128
  } catch {
50859
51129
  }
50860
51130
  }
50861
51131
  if (sql.startsWith("[")) {
50862
51132
  try {
50863
51133
  const parsed = JSON.parse(sql);
51134
+ const structuredSql = renderStructuredTableSql(parsed);
51135
+ if (structuredSql) return normalizeSupabaseSql(structuredSql);
50864
51136
  const parts = [];
50865
51137
  for (const item of parsed) {
50866
51138
  const s = item["sql"] ?? item["query"] ?? item["script"];
50867
51139
  if (s && typeof s === "string") parts.push(s);
50868
51140
  }
50869
- if (parts.length > 0) return parts.join("\n\n");
51141
+ if (parts.length > 0) return normalizeSupabaseSql(parts.join("\n\n"));
50870
51142
  } catch {
50871
51143
  }
50872
51144
  }
50873
- return sql;
51145
+ return normalizeSupabaseSql(sql);
50874
51146
  }
50875
51147
  var SUPABASE_SQL_SYSTEM_PROMPT;
50876
51148
  var init_supabase_sql_seed = __esm({
@@ -50891,6 +51163,8 @@ IMPORTANT CONSTRAINTS:
50891
51163
  - For RLS policies, use simple expressions like: USING (true) or USING (current_setting('app.user_id')::int = user_id)
50892
51164
  - Use serial or bigserial for primary keys, not uuid (unless explicitly asked)
50893
51165
  - Use simple types: text, int, boolean, timestamptz
51166
+ - Do NOT use JSON-style array literals like ["a", "b"] in INSERT values.
51167
+ Use ARRAY['a', 'b'] for SQL array columns, or quoted JSON with ::jsonb for jsonb columns.
50894
51168
  - Always include INSERT statements with realistic test data
50895
51169
 
50896
51170
 
@@ -51263,7 +51537,7 @@ async function enrichSeedWithLlm(seed, twinName, setupDescription, deadline, coo
51263
51537
  try {
51264
51538
  const response = await callCodegenLlm({
51265
51539
  systemPrompt: ENRICH_SYSTEM_PROMPT,
51266
- userPrompt: `Twin: ${twinName}
51540
+ userPrompt: `Clone: ${twinName}
51267
51541
 
51268
51542
  Setup description:
51269
51543
  ${setupDescription}
@@ -52473,6 +52747,9 @@ function ensureSlackScenarioChannelAccess(mergedSeed, intent) {
52473
52747
  }
52474
52748
  return mergedSeed;
52475
52749
  }
52750
+ function isSyntheticGoogleWorkspaceEmail(email3) {
52751
+ return email3 === GOOGLE_WORKSPACE_SYNTHETIC_EMAIL;
52752
+ }
52476
52753
  function googleWorkspaceEmailEntities(intent) {
52477
52754
  if (!intent || intent.twinName !== "google-workspace") return [];
52478
52755
  return intent.entities.filter((entity) => entity.kind === "email" && entity.key === "address" && typeof entity.value === "string" || entity.kind === "account" && entity.key === "email" && typeof entity.value === "string").map((entity) => normalizedEmail(entity.value)).filter((value) => Boolean(value));
@@ -52500,6 +52777,32 @@ function googleWorkspaceReferencedEmails(seed) {
52500
52777
  }
52501
52778
  return emails;
52502
52779
  }
52780
+ function googleWorkspaceBootstrapEmail(intentEmails, referencedEmails) {
52781
+ return intentEmails.find((email3) => !isSyntheticGoogleWorkspaceEmail(email3)) ?? referencedEmails.find((email3) => !isSyntheticGoogleWorkspaceEmail(email3)) ?? "user@example.com";
52782
+ }
52783
+ function rewriteSyntheticGoogleWorkspaceAccountRefs(seed, email3) {
52784
+ const collections = [
52785
+ "calendars",
52786
+ "calendarEvents",
52787
+ "gmailThreads",
52788
+ "gmailMessages",
52789
+ "gmailDrafts",
52790
+ "gmailAttachments",
52791
+ "gmailHistory",
52792
+ "driveFiles",
52793
+ "contacts",
52794
+ "googleAuthTokens"
52795
+ ];
52796
+ for (const collection of collections) {
52797
+ for (const item of seed[collection] ?? []) {
52798
+ const record2 = asRecord(item);
52799
+ if (!record2) continue;
52800
+ if (isSyntheticGoogleWorkspaceEmail(normalizedEmail(record2["accountEmail"]))) {
52801
+ record2["accountEmail"] = email3;
52802
+ }
52803
+ }
52804
+ }
52805
+ }
52503
52806
  function googleWorkspaceHasCalendarSurface(intent) {
52504
52807
  return intent.extractedSlots["workspace.surface.calendar"] === true || /\b(calendar|event|events|meeting|meetings|invite|invites)\b/i.test(intent.setupSummary);
52505
52808
  }
@@ -52534,6 +52837,15 @@ function ensureGoogleWorkspaceAccount(accounts, email3, primary) {
52534
52837
  primary
52535
52838
  });
52536
52839
  }
52840
+ function replaceGoogleWorkspaceAccount(account, email3) {
52841
+ account["accountId"] = "acct_primary";
52842
+ account["email"] = email3;
52843
+ account["displayName"] = "Primary Account";
52844
+ account["givenName"] = "Primary";
52845
+ account["familyName"] = "Account";
52846
+ account["timezone"] = "America/Los_Angeles";
52847
+ account["primary"] = true;
52848
+ }
52537
52849
  function ensureGoogleWorkspaceAuthToken(authTokens, email3) {
52538
52850
  if (authTokens.some((token) => normalizedEmail(token["accountEmail"]) === email3)) return;
52539
52851
  const localPart = email3.split("@")[0] ?? email3;
@@ -52553,7 +52865,8 @@ function ensureGoogleWorkspaceAuthToken(authTokens, email3) {
52553
52865
  function ensureGoogleWorkspacePrimaryCalendar(calendars, accountEmail) {
52554
52866
  const existingPrimary = calendars.find((calendar) => calendar["calendarId"] === "primary");
52555
52867
  if (existingPrimary) {
52556
- existingPrimary["accountEmail"] = normalizedEmail(existingPrimary["accountEmail"]) ?? accountEmail;
52868
+ const existingEmail = normalizedEmail(existingPrimary["accountEmail"]);
52869
+ existingPrimary["accountEmail"] = !existingEmail || isSyntheticGoogleWorkspaceEmail(existingEmail) ? accountEmail : existingEmail;
52557
52870
  existingPrimary["primary"] = true;
52558
52871
  return "primary";
52559
52872
  }
@@ -52578,6 +52891,15 @@ function ensureGoogleWorkspaceCalendarRows(mergedSeed) {
52578
52891
  for (const item of mergedSeed["calendarEvents"] ?? []) {
52579
52892
  const event = asRecord(item);
52580
52893
  if (!event) continue;
52894
+ if (typeof event["summary"] !== "string") {
52895
+ event["summary"] = "Calendar event";
52896
+ }
52897
+ if (typeof event["description"] !== "string") {
52898
+ event["description"] = "";
52899
+ }
52900
+ if (typeof event["location"] !== "string") {
52901
+ event["location"] = "";
52902
+ }
52581
52903
  const calendarId = typeof event["calendarId"] === "string" && event["calendarId"].trim() ? event["calendarId"].trim() : "primary";
52582
52904
  const accountEmail = normalizedEmail(event["accountEmail"]) ?? "self@local.invalid";
52583
52905
  if (existingCalendarIds.has(calendarId)) continue;
@@ -52617,21 +52939,28 @@ function ensureGoogleWorkspaceCalendarEvidence(mergedSeed, intent, accountEmail)
52617
52939
  organizerEmail: accountEmail,
52618
52940
  attendeeEmails: [],
52619
52941
  conferenceUrl: null,
52620
- extendedPropertiesShared: null
52942
+ extendedPropertiesShared: {}
52621
52943
  });
52622
52944
  }
52623
52945
  function ensureGoogleWorkspaceScenarioAccounts(mergedSeed, intent) {
52624
52946
  if (!intent || intent.twinName !== "google-workspace") return mergedSeed;
52625
52947
  const accounts = ensureArray(mergedSeed, "accounts");
52626
52948
  const authTokens = ensureArray(mergedSeed, "googleAuthTokens");
52949
+ const intentEmails = googleWorkspaceEmailEntities(intent);
52950
+ let referencedEmails = googleWorkspaceReferencedEmails(mergedSeed);
52951
+ const bootstrapEmail = googleWorkspaceBootstrapEmail(intentEmails, referencedEmails);
52627
52952
  if (accounts.length === 0) {
52628
- ensureGoogleWorkspaceAccount(accounts, "self@local.invalid", true);
52953
+ ensureGoogleWorkspaceAccount(accounts, bootstrapEmail, true);
52954
+ } else if (accounts.length === 1 && isSyntheticGoogleWorkspaceEmail(normalizedEmail(accounts[0]?.["email"]))) {
52955
+ replaceGoogleWorkspaceAccount(accounts[0], bootstrapEmail);
52629
52956
  }
52630
- const primaryEmail = normalizedEmail(accounts.find((account) => account["primary"] === true)?.["email"]) ?? normalizedEmail(accounts[0]?.["email"]) ?? "self@local.invalid";
52957
+ rewriteSyntheticGoogleWorkspaceAccountRefs(mergedSeed, bootstrapEmail);
52958
+ referencedEmails = googleWorkspaceReferencedEmails(mergedSeed);
52959
+ const primaryEmail = normalizedEmail(accounts.find((account) => account["primary"] === true)?.["email"]) ?? normalizedEmail(accounts[0]?.["email"]) ?? GOOGLE_WORKSPACE_SYNTHETIC_EMAIL;
52631
52960
  ensureGoogleWorkspaceAuthToken(authTokens, primaryEmail);
52632
52961
  const requiredEmails = Array.from(/* @__PURE__ */ new Set([
52633
- ...googleWorkspaceEmailEntities(intent),
52634
- ...googleWorkspaceReferencedEmails(mergedSeed),
52962
+ ...intentEmails,
52963
+ ...referencedEmails,
52635
52964
  ...accounts.map((account) => normalizedEmail(account["email"])).filter((value) => Boolean(value))
52636
52965
  ]));
52637
52966
  for (const email3 of requiredEmails) {
@@ -52649,7 +52978,7 @@ function applyScenarioCoverageFixups(mergedSeed, intent) {
52649
52978
  nextSeed = ensureGoogleWorkspaceScenarioAccounts(nextSeed, intent);
52650
52979
  return nextSeed;
52651
52980
  }
52652
- var GOOGLE_WORKSPACE_BOOTSTRAP_SCOPES;
52981
+ var GOOGLE_WORKSPACE_BOOTSTRAP_SCOPES, GOOGLE_WORKSPACE_SYNTHETIC_EMAIL;
52653
52982
  var init_scenario_coverage_fixups = __esm({
52654
52983
  "../packages/seedgen/src/runner/seed/scenario-coverage-fixups.ts"() {
52655
52984
  "use strict";
@@ -52662,6 +52991,7 @@ var init_scenario_coverage_fixups = __esm({
52662
52991
  "https://www.googleapis.com/auth/drive.readonly",
52663
52992
  "https://www.googleapis.com/auth/contacts.readonly"
52664
52993
  ];
52994
+ GOOGLE_WORKSPACE_SYNTHETIC_EMAIL = "self@local.invalid";
52665
52995
  }
52666
52996
  });
52667
52997
 
@@ -52739,7 +53069,7 @@ var init_seed_postprocessing = __esm({
52739
53069
  const details = validationErrors.length > 0 ? `:
52740
53070
  ${validationErrors.map((e) => ` - ${e}`).join("\n")}` : ".";
52741
53071
  const suffix = hint ?? "\n\nHint: Run `archal login` and retry. For deterministic reruns, use `--replay-seed <path>` with a previously saved managed seed snapshot.";
52742
- super(`Dynamic seed generation failed for twin "${twinName}"${details}${suffix}`);
53072
+ super(`Dynamic seed generation failed for clone "${twinName}"${details}${suffix}`);
52743
53073
  this.name = "DynamicSeedError";
52744
53074
  this.twinName = twinName;
52745
53075
  this.validationErrors = validationErrors;
@@ -53275,8 +53605,8 @@ async function generateDynamicSeed(twinName, baseSeedName, baseSeedData, setupDe
53275
53605
  ],
53276
53606
  `
53277
53607
 
53278
- Hint: Dynamic seed generation failed for the "${twinName}" twin. Try adding a \`seed: <name>\` line to your scenario's Config section to use a pre-built seed instead.
53279
- Use a documented seed name for ${twinName}, or inspect the twin package assets in this repo.`
53608
+ Hint: Dynamic seed generation failed for the "${twinName}" clone. Try adding a \`seed: <name>\` line to your scenario's Config section to use a pre-built seed instead.
53609
+ Use a documented seed name for ${twinName}, or inspect the bundled clone assets in this repo.`
53280
53610
  );
53281
53611
  }
53282
53612
  var import_node_crypto14, SEED_CODEGEN_MAX_TOKENS, MAX_CODEGEN_ATTEMPTS, SEED_CACHE_PROMPT_TEMPLATE_VERSION, CODEGEN_TOTAL_BUDGET_MS, SYSTEM_PROMPT_HASH;
@@ -53299,7 +53629,7 @@ var init_dynamic_generator = __esm({
53299
53629
  init_seed_postprocessing();
53300
53630
  SEED_CODEGEN_MAX_TOKENS = 4096;
53301
53631
  MAX_CODEGEN_ATTEMPTS = 3;
53302
- SEED_CACHE_PROMPT_TEMPLATE_VERSION = 4;
53632
+ SEED_CACHE_PROMPT_TEMPLATE_VERSION = 5;
53303
53633
  CODEGEN_TOTAL_BUDGET_MS = 12e4;
53304
53634
  SYSTEM_PROMPT_HASH = (0, import_node_crypto14.createHash)("sha256").update(buildSeedPromptHashInput()).digest("hex").slice(0, 12);
53305
53635
  }
@@ -55121,7 +55451,7 @@ function computeStateDiff(before, after) {
55121
55451
  }
55122
55452
  return diff;
55123
55453
  }
55124
- function splitSqlTopLevel(input, separator) {
55454
+ function splitSqlTopLevel2(input, separator) {
55125
55455
  const parts = [];
55126
55456
  let depth = 0;
55127
55457
  let inQuote = false;
@@ -55150,11 +55480,11 @@ function splitSqlTopLevel(input, separator) {
55150
55480
  if (tail) parts.push(tail);
55151
55481
  return parts;
55152
55482
  }
55153
- function splitSqlStatements(sql) {
55483
+ function splitSqlStatements2(sql) {
55154
55484
  const stripped = sql.replace(/--.*$/gm, "");
55155
- return splitSqlTopLevel(stripped, ";").map((stmt) => stmt.trim()).filter((stmt) => stmt.length > 0);
55485
+ return splitSqlTopLevel2(stripped, ";").map((stmt) => stmt.trim()).filter((stmt) => stmt.length > 0);
55156
55486
  }
55157
- function normalizeSqlIdentifier(raw) {
55487
+ function normalizeSqlIdentifier2(raw) {
55158
55488
  const parts = raw.split(".").map((part) => part.trim().replace(/^"|"$/g, "").replace(/""/g, '"')).filter((part) => part.length > 0);
55159
55489
  return parts[parts.length - 1] ?? raw.trim();
55160
55490
  }
@@ -55173,7 +55503,7 @@ function parseSqlSeed(sql) {
55173
55503
  const seed = {};
55174
55504
  const tablesWithNumericId = /* @__PURE__ */ new Set();
55175
55505
  const nextIds = /* @__PURE__ */ new Map();
55176
- const statements = splitSqlStatements(sql);
55506
+ const statements = splitSqlStatements2(sql);
55177
55507
  for (const statement of statements) {
55178
55508
  const createMatch = statement.match(
55179
55509
  /^CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+([^\s(]+)\s*\(([\s\S]*)\)$/i
@@ -55182,7 +55512,7 @@ function parseSqlSeed(sql) {
55182
55512
  const tableNameCapture2 = createMatch[1];
55183
55513
  const schemaBodyCapture = createMatch[2];
55184
55514
  if (tableNameCapture2 === void 0 || schemaBodyCapture === void 0) continue;
55185
- const tableName2 = normalizeSqlIdentifier(tableNameCapture2);
55515
+ const tableName2 = normalizeSqlIdentifier2(tableNameCapture2);
55186
55516
  const schemaBody = schemaBodyCapture;
55187
55517
  if (/\bid\s+(?:serial|bigserial|integer|int|bigint)\b/i.test(schemaBody)) {
55188
55518
  tablesWithNumericId.add(tableName2);
@@ -55198,8 +55528,8 @@ function parseSqlSeed(sql) {
55198
55528
  const columnsCapture = insertMatch[2];
55199
55529
  const tuplesCapture = insertMatch[3];
55200
55530
  if (tableNameCapture === void 0 || columnsCapture === void 0 || tuplesCapture === void 0) continue;
55201
- const tableName = normalizeSqlIdentifier(tableNameCapture);
55202
- const columns = splitSqlTopLevel(columnsCapture, ",").map((column) => normalizeSqlIdentifier(column));
55531
+ const tableName = normalizeSqlIdentifier2(tableNameCapture);
55532
+ const columns = splitSqlTopLevel2(columnsCapture, ",").map((column) => normalizeSqlIdentifier2(column));
55203
55533
  const tuplesText = tuplesCapture;
55204
55534
  const tuples = [];
55205
55535
  let depth = 0;
@@ -55231,7 +55561,7 @@ function parseSqlSeed(sql) {
55231
55561
  const rows = seed[tableName] ?? [];
55232
55562
  let nextId = nextIds.get(tableName) ?? 1;
55233
55563
  for (const tuple2 of tuples) {
55234
- const rawValues = splitSqlTopLevel(tuple2, ",");
55564
+ const rawValues = splitSqlTopLevel2(tuple2, ",");
55235
55565
  const row = {};
55236
55566
  for (let i = 0; i < columns.length; i++) {
55237
55567
  const column = columns[i];
@@ -56141,7 +56471,6 @@ function writeLocalTwinCatalogJson() {
56141
56471
  process.stdout.write(JSON.stringify(
56142
56472
  KNOWN_CLONES.map((twin) => ({
56143
56473
  name: twin.name,
56144
- package: `@archal/twin-${twin.name}`,
56145
56474
  description: twin.description,
56146
56475
  toolCount: twin.toolCount
56147
56476
  })),
@@ -56179,14 +56508,12 @@ async function listCloneCatalog(json2) {
56179
56508
  }
56180
56509
  const rows2 = KNOWN_CLONES.map((twin) => [
56181
56510
  twin.displayName,
56182
- String(twin.toolCount),
56183
56511
  twin.description,
56184
56512
  availableTwinStatus()
56185
56513
  ]);
56186
- table(["Name", "Tools", "Description", "Status"], rows2);
56514
+ table(["Name", "Description", "Status"], rows2);
56187
56515
  warn("Could not reach server. Showing local clone list.");
56188
56516
  info(twinCatalogSummary(creds));
56189
- info(TOOL_COUNT_FOOTNOTE);
56190
56517
  return;
56191
56518
  }
56192
56519
  if (json2) {
@@ -56195,15 +56522,12 @@ async function listCloneCatalog(json2) {
56195
56522
  }
56196
56523
  const rows = result.data.map((twin) => [
56197
56524
  twin.name,
56198
- twin.toolCount != null ? String(twin.toolCount) : "-",
56199
56525
  twin.description,
56200
56526
  availableTwinStatus()
56201
56527
  ]);
56202
- table(["Name", "Tools", "Description", "Status"], rows);
56528
+ table(["Name", "Description", "Status"], rows);
56203
56529
  success2(twinCatalogSummary(creds));
56204
- info(TOOL_COUNT_FOOTNOTE);
56205
56530
  }
56206
- var TOOL_COUNT_FOOTNOTE;
56207
56531
  var init_clones = __esm({
56208
56532
  "src/commands/clones.ts"() {
56209
56533
  "use strict";
@@ -56213,7 +56537,6 @@ var init_clones = __esm({
56213
56537
  init_api_client();
56214
56538
  init_clone_catalog();
56215
56539
  init_ansi();
56216
- TOOL_COUNT_FOOTNOTE = "Tool counts shown are static SDK tools. Run 'archal clone tools <clone>' for the full live tool list.";
56217
56540
  }
56218
56541
  });
56219
56542
 
@@ -61445,7 +61768,7 @@ var parseExistenceAssertion = (ctx) => {
61445
61768
  const noneMatch = lower.match(/^(?:no|zero|none)\s+(.+?)(?:\s+(?:remain|exist|left|present|found))?\s*$/);
61446
61769
  if (noneMatch) {
61447
61770
  const noneSubject = noneMatch[1]?.trim() ?? "";
61448
- if (/\b(?:were|was|have|had|are|is|been|being|contains?|includes?|should|would|could)\b/.test(noneSubject)) {
61771
+ if (/\b(?:were|was|have|had|are|is|been|being|contains?|includes?|exceeds?|exceeded|should|would|could)\b/.test(noneSubject)) {
61449
61772
  return null;
61450
61773
  }
61451
61774
  return {
@@ -61841,11 +62164,16 @@ var parseNegatedCreationAssertion = (ctx) => {
61841
62164
  const { lower } = ctx;
61842
62165
  const noCreatedInMatch = lower.match(/^no\s+(.+?)\s+(?:were|was|have been|had been)\s+(?:created|processed|charged|posted|sent|made|transferred|issued)\s+(?:in|on|to|from|with|for|via)\s+(.+)$/);
61843
62166
  if (noCreatedInMatch) {
62167
+ const subject = noCreatedInMatch[1]?.trim() ?? "";
62168
+ const targetService = noCreatedInMatch[2]?.trim();
62169
+ if (/\brefunds?\b/.test(subject) && /^charge\b/.test(targetService ?? "")) {
62170
+ return null;
62171
+ }
61844
62172
  return {
61845
62173
  type: "exact_count",
61846
- subject: noCreatedInMatch[1]?.trim() ?? "",
62174
+ subject,
61847
62175
  value: 0,
61848
- targetService: noCreatedInMatch[2]?.trim()
62176
+ targetService
61849
62177
  };
61850
62178
  }
61851
62179
  return null;
@@ -62024,6 +62352,23 @@ function parseAssertion(description) {
62024
62352
  }
62025
62353
 
62026
62354
  // src/runner/scenario-parser.ts
62355
+ var explicitCriterionTag = /* @__PURE__ */ Symbol("explicitCriterionTag");
62356
+ function getExplicitCriterionTag(criterion) {
62357
+ return criterion[explicitCriterionTag];
62358
+ }
62359
+ function isUnsupportedDeterministicRefundAssertion(description) {
62360
+ const normalized = description.replace(/^\s*\[(?:critical|high|medium|low)\]\s*/i, "").replace(/`/g, "").toLowerCase().trim();
62361
+ if (!/\brefund(?:ed|s|ing)?\b/.test(normalized)) {
62362
+ return false;
62363
+ }
62364
+ if (/\brefunds?\b.*\bexceeds?\b.*\boriginal charge amount\b/.test(normalized)) {
62365
+ return true;
62366
+ }
62367
+ if (/\btotal refunded amount\b.*\b(?:on|for)\s+ch_[a-z0-9_]+\b.*\b(?:exactly|equals?|becomes)\b/.test(normalized)) {
62368
+ return true;
62369
+ }
62370
+ return /^no\s+stripe\s+refunds?\s+(?:are|were|was|have been|had been)\s+created\s+for\s+charge\s+ch_[a-z0-9_]+\b/.test(normalized);
62371
+ }
62027
62372
  var SECTION_ALIASES = {
62028
62373
  setup: "setup",
62029
62374
  context: "setup",
@@ -62099,20 +62444,28 @@ function parseCriterionLine(line, index) {
62099
62444
  if (!bulletStripped) return null;
62100
62445
  let type = "probabilistic";
62101
62446
  let description = bulletStripped;
62447
+ let explicitTag;
62102
62448
  const tagMatch = description.match(/^\[([DP])]\s*(.*)/i);
62103
62449
  if (tagMatch) {
62104
62450
  const tag = (tagMatch[1] ?? "").toUpperCase();
62451
+ explicitTag = tag === "D" ? "D" : "P";
62105
62452
  type = tag === "D" ? "deterministic" : "probabilistic";
62106
62453
  description = tagMatch[2]?.trim() ?? "";
62107
62454
  } else {
62108
62455
  type = inferCriterionType(description);
62109
62456
  }
62110
62457
  if (!description) return null;
62111
- return {
62458
+ const criterion = {
62112
62459
  id: `criterion-${index + 1}`,
62113
62460
  description,
62114
62461
  type
62115
62462
  };
62463
+ if (explicitTag) {
62464
+ Object.defineProperty(criterion, explicitCriterionTag, {
62465
+ value: explicitTag
62466
+ });
62467
+ }
62468
+ return criterion;
62116
62469
  }
62117
62470
  function inferCriterionType(description) {
62118
62471
  const deterministicPatterns = [
@@ -62371,6 +62724,11 @@ function validateScenario(scenario) {
62371
62724
  if (!criterion.description) {
62372
62725
  errors.push(`Criterion ${criterion.id} has an empty description`);
62373
62726
  }
62727
+ if (criterion.type === "deterministic" && getExplicitCriterionTag(criterion) === "D" && isUnsupportedDeterministicRefundAssertion(criterion.description) && parseAssertion(criterion.description) === null) {
62728
+ errors.push(
62729
+ `Criterion ${criterion.id} is tagged [D] but is not supported by the deterministic parser: "${criterion.description}"`
62730
+ );
62731
+ }
62374
62732
  }
62375
62733
  if (scenario.config.twins.length === 0) {
62376
62734
  errors.push("Scenario does not reference any known clones. Add `clones:` under ## Config or describe the service in ## Expected Behavior.");
@@ -63148,6 +63506,34 @@ var EVIDENCE_SENSITIVE_KEY_PARTS = /* @__PURE__ */ new Set([
63148
63506
  "accesskey"
63149
63507
  ]);
63150
63508
  var EVIDENCE_STRING_PATTERNS = [
63509
+ {
63510
+ pattern: /https?:\/\/[^\s"'<>]+\/runtime\/session\/[^\s"'<>]*/gi,
63511
+ replacement: "[SERVICE_ENDPOINT_REDACTED]"
63512
+ },
63513
+ {
63514
+ pattern: /\/runtime\/session\/[^\s"'<>]*/gi,
63515
+ replacement: "[SERVICE_ENDPOINT_REDACTED]"
63516
+ },
63517
+ {
63518
+ pattern: /\bx-archal-[\w-]+(?::|=)\s*["']?[^"',\s}]+/gi,
63519
+ replacement: "[SERVICE_HEADER_REDACTED]"
63520
+ },
63521
+ {
63522
+ pattern: /\barchal_mcp_servers\b/gi,
63523
+ replacement: "service_tool_servers"
63524
+ },
63525
+ {
63526
+ pattern: /\bARCHAL_(?:CLONE_URLS_PATH|TWIN_URLS_PATH|PROXY_EVENTS_PATH|METRICS_FILE|AGENT_TRACE_FILE|ENABLE_PROVIDER_EGRESS_PROXY)\b/g,
63527
+ replacement: "[SERVICE_RUNTIME_CONFIG_REDACTED]"
63528
+ },
63529
+ {
63530
+ pattern: /\/archal-artifacts\/[^\s"'<>]*/gi,
63531
+ replacement: "[SERVICE_ARTIFACT_PATH_REDACTED]"
63532
+ },
63533
+ {
63534
+ pattern: /\b(?:host\.docker\.internal|localhost|127\.0\.0\.1):9100\b/gi,
63535
+ replacement: "[SERVICE_PROXY_ENDPOINT_REDACTED]"
63536
+ },
63151
63537
  {
63152
63538
  pattern: /\b(Bearer\s+)[A-Za-z0-9._~+/=-]+\b/gi,
63153
63539
  replacement: `$1${EVIDENCE_REDACTED}`
@@ -63213,7 +63599,7 @@ var EVIDENCE_STRING_PATTERNS = [
63213
63599
  replacement: `$1${EVIDENCE_REDACTED}`
63214
63600
  }
63215
63601
  ];
63216
- var EVIDENCE_STRING_REDACTION_HINT = /(bearer\s+|gh[pousr]_|github_pat_|sk_(?:live|test)_|sk-proj-|sk-ant-|sk-|AIza|xox[baprs]-|x(?:app|oxa|oxc|oxp|oxs)-|(?:AKIA|ASIA)|eyJ|private key|(?:^|[?&])(?:token|access_token|refresh_token|client_secret|secret|api_key|apikey|password)=|\b"?(?:authorization|token|access_token|refresh_token|client_secret|secret|api_key|apikey|password|private_key|privatekey)"?\s*[:=]|\b[A-Z0-9_]*(?:API_KEY|SECRET_KEY|ACCESS_KEY|ACCESS_TOKEN|REFRESH_TOKEN|AUTH_TOKEN|BEARER_TOKEN|TOKEN|PASSWORD|SECRET|PRIVATE_KEY)[A-Z0-9_]*\s*[:=])/i;
63602
+ var EVIDENCE_STRING_REDACTION_HINT = /(\/runtime\/session\/|x-archal-|archal_mcp_servers|\bARCHAL_(?:CLONE_URLS_PATH|TWIN_URLS_PATH|PROXY_EVENTS_PATH|METRICS_FILE|AGENT_TRACE_FILE|ENABLE_PROVIDER_EGRESS_PROXY)\b|\/archal-artifacts\/|\b(?:host\.docker\.internal|localhost|127\.0\.0\.1):9100\b|bearer\s+|gh[pousr]_|github_pat_|sk_(?:live|test)_|sk-proj-|sk-ant-|sk-|AIza|xox[baprs]-|x(?:app|oxa|oxc|oxp|oxs)-|(?:AKIA|ASIA)|eyJ|private key|(?:^|[?&])(?:token|access_token|refresh_token|client_secret|secret|api_key|apikey|password)=|\b"?(?:authorization|token|access_token|refresh_token|client_secret|secret|api_key|apikey|password|private_key|privatekey)"?\s*[:=]|\b[A-Z0-9_]*(?:API_KEY|SECRET_KEY|ACCESS_KEY|ACCESS_TOKEN|REFRESH_TOKEN|AUTH_TOKEN|BEARER_TOKEN|TOKEN|PASSWORD|SECRET|PRIVATE_KEY)[A-Z0-9_]*\s*[:=])/i;
63217
63603
  function redactEvidenceString(value) {
63218
63604
  if (!EVIDENCE_STRING_REDACTION_HINT.test(value)) {
63219
63605
  return value;
@@ -67296,18 +67682,12 @@ var LEGACY_AGENT_CONTRACT_ENV = /* @__PURE__ */ new Set([
67296
67682
  "ARCHAL_METRICS_FILE",
67297
67683
  "ARCHAL_AGENT_TRACE_FILE"
67298
67684
  ]);
67299
- var ROUTED_CLONE_ENV_NAMES2 = /* @__PURE__ */ new Set([
67300
- "DISCORD",
67301
- "GITHUB",
67302
- "GOOGLE_WORKSPACE",
67303
- "JIRA",
67304
- "LINEAR",
67305
- "RAMP",
67306
- "SLACK",
67307
- "STRIPE",
67308
- "SUPABASE",
67309
- "TELEGRAM"
67310
- ]);
67685
+ var ROUTED_CLONE_ENV_NAMES2 = new Set(
67686
+ CLONE_NAMES.flatMap((cloneName) => {
67687
+ const segment = toServiceEnvSegment(cloneName);
67688
+ return segment ? [segment] : [];
67689
+ })
67690
+ );
67311
67691
  var ROUTED_CLONE_ENV_SUFFIXES2 = ["_BASE_URL", "_REST_URL", "_MCP_URL", "_URL"];
67312
67692
  function routedCloneEnvName2(key) {
67313
67693
  if (!key.startsWith("ARCHAL_")) {
@@ -67368,7 +67748,7 @@ function buildTrustedCloneRoutingEnv(input) {
67368
67748
  continue;
67369
67749
  }
67370
67750
  const restUrl = toTwinRestUrl(rawUrl);
67371
- const mcpUrl = toTwinMcpUrl(rawUrl);
67751
+ const mcpUrl = supportsHostedCloneMcp(cloneName) ? toTwinMcpUrl(rawUrl) : void 0;
67372
67752
  restUrls[cloneName] = restUrl;
67373
67753
  maybeSetEnv(env, toAgentServiceEnvVarName(cloneName, "BASE_URL"), restUrl);
67374
67754
  maybeSetEnv(env, toAgentServiceEnvVarName(cloneName, "URL"), restUrl);
@@ -71810,7 +72190,8 @@ function resolveLlmProviderKeys(env) {
71810
72190
  }
71811
72191
 
71812
72192
  // ../packages/sandbox-runtime/src/docker-harness/runner.ts
71813
- var OUTPUT_DIR_IN_CONTAINER = "/archal-out";
72193
+ var AGENT_OUTPUT_DIR_IN_CONTAINER = "/agent-output";
72194
+ var PROXY_CONFIG_DIR_IN_CONTAINER = "/service-runtime";
71814
72195
  var PROXY_IMAGE_NAME = "archal/sandbox";
71815
72196
  var BUILD_TIMEOUT_CAP_MS = 12e4;
71816
72197
  var PROXY_READY_TIMEOUT_MS = 1e4;
@@ -71842,21 +72223,21 @@ function fail(error49) {
71842
72223
  return { ok: false, exitCode: 1, stdout: "", stderr: "", timedOut: false, error: error49 };
71843
72224
  }
71844
72225
  function buildContainerEnv(config2) {
71845
- const caPath = `${OUTPUT_DIR_IN_CONTAINER}/ca.crt`;
72226
+ const caPath = `${AGENT_OUTPUT_DIR_IN_CONTAINER}/ca.crt`;
71846
72227
  const includeLegacyArchalEnv = shouldExposeLegacyAgentEnv2();
71847
72228
  const env = {
71848
72229
  [AGENT_RUN_MODE_ENV2]: "local",
71849
72230
  [AGENT_TASK_ENV2]: config2.task,
71850
- [AGENT_METRICS_FILE_ENV2]: `${OUTPUT_DIR_IN_CONTAINER}/metrics.json`,
71851
- [AGENT_TRACE_FILE_ENV2]: `${OUTPUT_DIR_IN_CONTAINER}/agent-trace.json`,
72231
+ [AGENT_METRICS_FILE_ENV2]: `${AGENT_OUTPUT_DIR_IN_CONTAINER}/metrics.json`,
72232
+ [AGENT_TRACE_FILE_ENV2]: `${AGENT_OUTPUT_DIR_IN_CONTAINER}/agent-trace.json`,
71852
72233
  ...BOOTSTRAP_SERVICE_CREDENTIALS2,
71853
72234
  ...buildTlsTrustEnvVars(caPath)
71854
72235
  };
71855
72236
  if (includeLegacyArchalEnv) {
71856
72237
  env["ARCHAL_ENGINE_MODE"] = "local";
71857
72238
  env["ARCHAL_ENGINE_TASK"] = config2.task;
71858
- env["ARCHAL_METRICS_FILE"] = `${OUTPUT_DIR_IN_CONTAINER}/metrics.json`;
71859
- env["ARCHAL_AGENT_TRACE_FILE"] = `${OUTPUT_DIR_IN_CONTAINER}/agent-trace.json`;
72239
+ env["ARCHAL_METRICS_FILE"] = `${AGENT_OUTPUT_DIR_IN_CONTAINER}/metrics.json`;
72240
+ env["ARCHAL_AGENT_TRACE_FILE"] = `${AGENT_OUTPUT_DIR_IN_CONTAINER}/agent-trace.json`;
71860
72241
  }
71861
72242
  if (config2.sessionId) {
71862
72243
  env[AGENT_SESSION_ID_ENV] = config2.sessionId;
@@ -71870,11 +72251,24 @@ function buildContainerEnv(config2) {
71870
72251
  env["ARCHAL_ENGINE_MODEL"] = config2.model.trim();
71871
72252
  }
71872
72253
  }
72254
+ const providerKeys = resolveLlmProviderKeys(process.env);
72255
+ if (providerKeys.openai) {
72256
+ env["OPENAI_API_KEY"] = SANDBOX_OPENAI_PLACEHOLDER_API_KEY;
72257
+ }
72258
+ if (providerKeys.anthropic) {
72259
+ env["ANTHROPIC_API_KEY"] = SANDBOX_ANTHROPIC_PLACEHOLDER_API_KEY;
72260
+ }
72261
+ if (process.env["GOOGLE_API_KEY"]?.trim()) {
72262
+ env["GOOGLE_API_KEY"] = SANDBOX_GOOGLE_PLACEHOLDER_API_KEY;
72263
+ }
72264
+ if (process.env["GEMINI_API_KEY"]?.trim()) {
72265
+ env["GEMINI_API_KEY"] = SANDBOX_GOOGLE_PLACEHOLDER_API_KEY;
72266
+ }
72267
+ if (providerKeys.openrouter) {
72268
+ env["OPENROUTER_API_KEY"] = SANDBOX_OPENROUTER_PLACEHOLDER_API_KEY;
72269
+ }
71873
72270
  const passthroughKeys = [
71874
72271
  ...includeLegacyArchalEnv ? ["ARCHAL_ENGINE_API_KEY"] : [],
71875
- "ANTHROPIC_API_KEY",
71876
- "OPENAI_API_KEY",
71877
- "GEMINI_API_KEY",
71878
72272
  "NODE_ENV"
71879
72273
  ];
71880
72274
  for (const key of passthroughKeys) {
@@ -72084,15 +72478,16 @@ async function runDockerHarness(config2) {
72084
72478
  const proxyContainerName = `archal-harness-proxy-${dockerIdSuffix}`;
72085
72479
  const networkName = `archal-harness-net-${dockerIdSuffix}`;
72086
72480
  const proxyEgressNetworkName = `archal-harness-egress-${dockerIdSuffix}`;
72087
- const mountedConfigDir = (0, import_node_path27.join)(runDir, "out");
72481
+ const agentOutputDir = (0, import_node_path27.join)(runDir, "agent-output");
72482
+ const proxyConfigDir = (0, import_node_path27.join)(runDir, "proxy");
72088
72483
  const envFilePath = (0, import_node_path27.join)(runDir, ".env");
72089
72484
  const proxyEnvFilePath = (0, import_node_path27.join)(runDir, ".proxy.env");
72090
- const cloneUrlsHostPath = (0, import_node_path27.join)(mountedConfigDir, "clone-urls.json");
72091
- const cloneUrlsContainerPath = `${OUTPUT_DIR_IN_CONTAINER}/clone-urls.json`;
72092
- const proxyCaPath = (0, import_node_path27.join)(mountedConfigDir, "ca.crt");
72093
- const proxyEventsPath = (0, import_node_path27.join)(mountedConfigDir, "proxy-events.ndjson");
72094
- const metricsPath = (0, import_node_path27.join)(mountedConfigDir, "metrics.json");
72095
- const agentTracePath = (0, import_node_path27.join)(mountedConfigDir, "agent-trace.json");
72485
+ const cloneUrlsHostPath = (0, import_node_path27.join)(proxyConfigDir, "service-map.json");
72486
+ const cloneUrlsContainerPath = `${PROXY_CONFIG_DIR_IN_CONTAINER}/service-map.json`;
72487
+ const proxyCaPath = (0, import_node_path27.join)(agentOutputDir, "ca.crt");
72488
+ const proxyEventsPath = (0, import_node_path27.join)(proxyConfigDir, "proxy-events.ndjson");
72489
+ const metricsPath = (0, import_node_path27.join)(agentOutputDir, "metrics.json");
72490
+ const agentTracePath = (0, import_node_path27.join)(agentOutputDir, "agent-trace.json");
72096
72491
  const ownImage = !config2.prebuiltImage;
72097
72492
  let cleanupDocker = false;
72098
72493
  const cleanupHandlers = registerDockerCleanupHandlers(containerName, imageName, ownImage, {
@@ -72128,7 +72523,8 @@ async function runDockerHarness(config2) {
72128
72523
  if (!proxyImage.ok) {
72129
72524
  return fail(stageError("TLS intercept sidecar unavailable", proxyImage.error));
72130
72525
  }
72131
- (0, import_node_fs30.mkdirSync)(mountedConfigDir, { recursive: true });
72526
+ (0, import_node_fs30.mkdirSync)(agentOutputDir, { recursive: true });
72527
+ (0, import_node_fs30.mkdirSync)(proxyConfigDir, { recursive: true });
72132
72528
  (0, import_node_fs30.writeFileSync)(cloneUrlsHostPath, JSON.stringify(config2.cloudTwinUrls), { encoding: "utf-8", mode: 384 });
72133
72529
  const networkResult = await runDockerCommand(["network", "create", "--internal", networkName], 1e4);
72134
72530
  if (networkResult.exitCode !== 0) {
@@ -72143,11 +72539,11 @@ async function runDockerHarness(config2) {
72143
72539
  ARCHAL_CLONE_URLS_PATH: cloneUrlsContainerPath,
72144
72540
  ARCHAL_TWIN_URLS_PATH: cloneUrlsContainerPath,
72145
72541
  ARCHAL_TOKEN: config2.authToken,
72146
- CA_CERT_PATH: `${OUTPUT_DIR_IN_CONTAINER}/ca.crt`,
72147
- CA_KEY_PATH: `${OUTPUT_DIR_IN_CONTAINER}/ca.key`,
72542
+ CA_CERT_PATH: `${AGENT_OUTPUT_DIR_IN_CONTAINER}/ca.crt`,
72543
+ CA_KEY_PATH: `${PROXY_CONFIG_DIR_IN_CONTAINER}/ca.key`,
72148
72544
  PROXY_PORT: "443",
72149
72545
  PROXY_HOST: "0.0.0.0",
72150
- ARCHAL_PROXY_EVENTS_PATH: `${OUTPUT_DIR_IN_CONTAINER}/proxy-events.ndjson`,
72546
+ ARCHAL_PROXY_EVENTS_PATH: `${PROXY_CONFIG_DIR_IN_CONTAINER}/proxy-events.ndjson`,
72151
72547
  ARCHAL_ENABLE_PROVIDER_EGRESS_PROXY: "1",
72152
72548
  ARCHAL_BLOCK_EGRESS: "1",
72153
72549
  ...proxyLlmEnv
@@ -72160,7 +72556,9 @@ async function runDockerHarness(config2) {
72160
72556
  "--network",
72161
72557
  networkName,
72162
72558
  "-v",
72163
- `${mountedConfigDir}:${OUTPUT_DIR_IN_CONTAINER}`,
72559
+ `${proxyConfigDir}:${PROXY_CONFIG_DIR_IN_CONTAINER}`,
72560
+ "-v",
72561
+ `${agentOutputDir}:${AGENT_OUTPUT_DIR_IN_CONTAINER}`,
72164
72562
  "--env-file",
72165
72563
  proxyEnvFilePath,
72166
72564
  "--entrypoint",
@@ -72194,7 +72592,7 @@ async function runDockerHarness(config2) {
72194
72592
  "--network",
72195
72593
  networkName,
72196
72594
  "-v",
72197
- `${mountedConfigDir}:${OUTPUT_DIR_IN_CONTAINER}`,
72595
+ `${agentOutputDir}:${AGENT_OUTPUT_DIR_IN_CONTAINER}`,
72198
72596
  ...buildAddHostArgs(config2.cloudTwinUrls, proxyIp, buildProviderDomainsForProxy(proxyLlmEnv)),
72199
72597
  ...envArgs,
72200
72598
  imageName
@@ -72361,6 +72759,9 @@ function isLikelyModelBackendFailure(text) {
72361
72759
  if (!text) return false;
72362
72760
  return OPENCLAW_BACKEND_FAILURE_PATTERNS.some((pattern) => pattern.test(text));
72363
72761
  }
72762
+ function sanitizeOpenClawGatewayDiagnostic(text) {
72763
+ return text.replace(/Authorization:\s*Bearer\s+\S+/gi, "Authorization: Bearer [REDACTED]").replace(/\bBearer\s+[A-Za-z0-9._~+/\-=]+/gi, "Bearer [REDACTED]").replace(/x-archal-[\w-]+(?::|=)\s*["']?[^"',\s}]+/gi, "[service header redacted]").replace(/https?:\/\/[^\s"'<>]+\/runtime\/session\/[^\s"'<>]*/gi, "[service endpoint redacted]").replace(/\/runtime\/session\/[^\s"'<>]*/gi, "[service endpoint redacted]").replace(/\barchal_mcp_servers\b/gi, "service_tool_servers").replace(/\barchal_(?:transport|eval_mode)\b/gi, "service_metadata").replace(/\bArchal\b/gi, "service runtime");
72764
+ }
72364
72765
  function buildModelBackendHint() {
72365
72766
  return "Hint: OpenClaw gateway could not get a model response. Verify model/provider credentials on the gateway host (e.g. ANTHROPIC_API_KEY / OPENAI_API_KEY / GEMINI_API_KEY), then restart the gateway.";
72366
72767
  }
@@ -72372,7 +72773,7 @@ function buildOpenClawFailureMessage(parsed) {
72372
72773
  const details = [errorMessage4, outputText].filter((value) => Boolean(value && value.trim())).join(" | ");
72373
72774
  if (details) {
72374
72775
  message += `
72375
- Gateway output: ${details.slice(0, 500)}`;
72776
+ Gateway output: ${sanitizeOpenClawGatewayDiagnostic(details).slice(0, 500)}`;
72376
72777
  }
72377
72778
  if (isLikelyModelBackendFailure(details)) {
72378
72779
  message += `
@@ -72538,12 +72939,13 @@ async function executeOpenClawRemote(remoteConfig, scenario, runId, taskMessage,
72538
72939
  if (!response.ok) {
72539
72940
  const rawBody = await response.text();
72540
72941
  const statusLine = `${response.status} ${response.statusText}`.trim();
72942
+ const sanitizedBody = sanitizeOpenClawGatewayDiagnostic(rawBody).slice(0, 500);
72541
72943
  const hint = response.status === 401 || response.status === 403 ? "\nHint: If using a raw LLM API (Anthropic, OpenAI), note that --engine-endpoint requires an OpenClaw-compatible gateway, not a direct model API." : "";
72542
72944
  return {
72543
72945
  exitCode: response.status,
72544
72946
  stdout: "",
72545
- stderr: `OpenClaw request failed: ${statusLine}
72546
- ${rawBody}${hint}`.trim(),
72947
+ stderr: `OpenClaw request failed: ${statusLine}${sanitizedBody ? `
72948
+ ${sanitizedBody}` : ""}${hint}`.trim(),
72547
72949
  timedOut: false,
72548
72950
  durationMs: Date.now() - startedAt
72549
72951
  };
@@ -72559,9 +72961,10 @@ ${rawBody}${hint}`.trim(),
72559
72961
  try {
72560
72962
  parsed = JSON.parse(rawBody);
72561
72963
  } catch {
72964
+ const sanitizedBody = sanitizeOpenClawGatewayDiagnostic(rawBody).slice(0, 500);
72562
72965
  return {
72563
72966
  exitCode: 1,
72564
- stdout: rawBody,
72967
+ stdout: sanitizedBody,
72565
72968
  stderr: "OpenClaw response was not valid JSON",
72566
72969
  timedOut: false,
72567
72970
  durationMs: Date.now() - startedAt
@@ -74112,65 +74515,10 @@ async function runLocalProxy(config2, task, canaryFiles) {
74112
74515
  "NODE_PATH",
74113
74516
  "NODE_OPTIONS"
74114
74517
  ]);
74115
- const DENYLIST = /* @__PURE__ */ new Set([
74116
- "AWS_ACCESS_KEY_ID",
74117
- "AWS_SECRET_ACCESS_KEY",
74118
- "AWS_SESSION_TOKEN",
74119
- "DATABASE_URL",
74120
- "DATABASE_PASSWORD",
74121
- "ARCHAL_TOKEN",
74122
- "ARCHAL_REST_CONFIG",
74123
- "ARCHAL_TWIN_URLS",
74124
- "ARCHAL_TWIN_URLS_PATH",
74125
- "ARCHAL_TWIN_NAMES",
74126
- "ARCHAL_CLONE_URLS",
74127
- "ARCHAL_CLONE_URLS_PATH",
74128
- "ARCHAL_CLONE_NAMES",
74129
- "ARCHAL_API_BASE_URLS",
74130
- "ARCHAL_API_PROXY_URL",
74131
- "ARCHAL_MCP_CONFIG",
74132
- "ARCHAL_MCP_SERVERS",
74133
- "MCP_CONFIG_PATH",
74134
- "HTTP_PROXY",
74135
- "HTTPS_PROXY",
74136
- "http_proxy",
74137
- "https_proxy",
74138
- "GITHUB_TOKEN",
74139
- // CI-injected, not for agents
74140
- "NPM_TOKEN",
74141
- // Real LLM keys are stripped — the proxy injects them.
74142
- "OPENAI_API_KEY",
74143
- "ANTHROPIC_API_KEY",
74144
- "GOOGLE_API_KEY",
74145
- "GEMINI_API_KEY",
74146
- "OPENROUTER_API_KEY"
74147
- ]);
74148
- const DENYLIST_PREFIXES = ["AWS_"];
74149
- const ROUTED_TWIN_ENV_NAMES = /* @__PURE__ */ new Set([
74150
- "DISCORD",
74151
- "GITHUB",
74152
- "GOOGLE_WORKSPACE",
74153
- "JIRA",
74154
- "LINEAR",
74155
- "RAMP",
74156
- "SLACK",
74157
- "STRIPE",
74158
- "SUPABASE",
74159
- "TELEGRAM"
74160
- ]);
74161
- const ROUTED_TWIN_ENV_SUFFIXES = ["_BASE_URL", "_REST_URL", "_MCP_URL", "_URL"];
74162
74518
  const filteredHostEnv = {};
74163
74519
  for (const [key, value] of Object.entries(process.env)) {
74164
74520
  if (value === void 0) continue;
74165
- if (key.startsWith("ARCHAL_")) continue;
74166
- if (DENYLIST.has(key)) continue;
74167
- if (DENYLIST_PREFIXES.some((prefix) => key.startsWith(prefix))) continue;
74168
- const routedTwinSuffix = key.startsWith("ARCHAL_") ? ROUTED_TWIN_ENV_SUFFIXES.find((suffix) => key.endsWith(suffix)) : void 0;
74169
- const routedTwinName = routedTwinSuffix ? key.slice("ARCHAL_".length, -routedTwinSuffix.length).replace(/_(API|TWIN|CLONE)$/, "") : void 0;
74170
- if (routedTwinName && ROUTED_TWIN_ENV_NAMES.has(routedTwinName)) continue;
74171
- const isInfra = INFRA_ALLOWLIST.has(key);
74172
- const isApiKey = key.endsWith("_API_KEY") || key.endsWith("_TOKEN");
74173
- if (isInfra || isApiKey) {
74521
+ if (INFRA_ALLOWLIST.has(key)) {
74174
74522
  filteredHostEnv[key] = value;
74175
74523
  }
74176
74524
  }
@@ -78775,6 +79123,47 @@ function formatEvaluationErrorSummary(reasons) {
78775
79123
  }
78776
79124
  return "LLM evaluation errors";
78777
79125
  }
79126
+ function toLlmEvalErrorReason(reason) {
79127
+ switch (reason) {
79128
+ case "rate_limited":
79129
+ return "rate_limited";
79130
+ case "missing_credentials":
79131
+ return "auth_error";
79132
+ case "provider_error":
79133
+ return "provider_error";
79134
+ case "context_too_large":
79135
+ case void 0:
79136
+ return void 0;
79137
+ default: {
79138
+ const _exhaustive = reason;
79139
+ return _exhaustive;
79140
+ }
79141
+ }
79142
+ }
79143
+ function normalizeFatalLlmFailureResult(result) {
79144
+ if (!result.fallbackFailureReason || !FATAL_LLM_FAILURE_REASONS.has(result.fallbackFailureReason)) {
79145
+ return result;
79146
+ }
79147
+ const errorReason = toLlmEvalErrorReason(result.fallbackFailureReason);
79148
+ return {
79149
+ ...result,
79150
+ status: "error",
79151
+ confidence: 0,
79152
+ ...errorReason ? { errorReason } : {}
79153
+ };
79154
+ }
79155
+ function buildUnevaluatedDeterministicFallback(criterion, deterministicResult, reason) {
79156
+ const errorReason = toLlmEvalErrorReason(reason);
79157
+ return {
79158
+ criterionId: criterion.id,
79159
+ status: "error",
79160
+ confidence: 0,
79161
+ explanation: `Could not evaluate deterministic criterion because parser fallback was unavailable (${formatFallbackFailureReason(reason)}). Satisfaction is incomplete for this criterion. Original parser result: ${deterministicResult.explanation}`,
79162
+ fallbackRecommended: true,
79163
+ fallbackFailureReason: reason,
79164
+ ...errorReason ? { errorReason } : {}
79165
+ };
79166
+ }
78778
79167
  function accumulateTokenUsage(total, next) {
78779
79168
  if (!next) return total;
78780
79169
  if (!total) return next;
@@ -78821,9 +79210,15 @@ async function evaluateRun(criteria, context, config2) {
78821
79210
  if (needsFallback) {
78822
79211
  if (deterministicFallbackFailureReason) {
78823
79212
  warn(
78824
- `LLM fallback skipped due to cached ${formatFallbackFailureReason(deterministicFallbackFailureReason)} - keeping deterministic result for "${criterion.id}"`
79213
+ `LLM fallback skipped due to cached ${formatFallbackFailureReason(deterministicFallbackFailureReason)} - marking deterministic criterion unevaluated for "${criterion.id}"`
79214
+ );
79215
+ evaluations.push(
79216
+ buildUnevaluatedDeterministicFallback(
79217
+ criterion,
79218
+ result,
79219
+ deterministicFallbackFailureReason
79220
+ )
78825
79221
  );
78826
- evaluations.push(result);
78827
79222
  continue;
78828
79223
  }
78829
79224
  warn(
@@ -78839,9 +79234,11 @@ async function evaluateRun(criteria, context, config2) {
78839
79234
  if (llmResult.fallbackFailureReason && FATAL_LLM_FAILURE_REASONS.has(llmResult.fallbackFailureReason)) {
78840
79235
  deterministicFallbackFailureReason = llmResult.fallbackFailureReason;
78841
79236
  warn(
78842
- `LLM fallback skipped due to ${formatFallbackFailureReason(llmResult.fallbackFailureReason)} - keeping deterministic result for "${criterion.id}"`
79237
+ `LLM fallback skipped due to ${formatFallbackFailureReason(llmResult.fallbackFailureReason)} - marking deterministic criterion unevaluated for "${criterion.id}"`
79238
+ );
79239
+ evaluations.push(
79240
+ buildUnevaluatedDeterministicFallback(criterion, result, llmResult.fallbackFailureReason)
78843
79241
  );
78844
- evaluations.push(result);
78845
79242
  } else {
78846
79243
  evaluations.push(llmResult);
78847
79244
  }
@@ -78855,9 +79252,12 @@ async function evaluateRun(criteria, context, config2) {
78855
79252
  }
78856
79253
  const allDeterministicFailed = deterministicCriteria.length > 0 && !deterministicFallbackFailureReason && evaluations.length > 0 && evaluations.every((e) => e.status === "fail");
78857
79254
  if (allDeterministicFailed && probabilisticCriteria.length > 0) {
78858
- info("All deterministic criteria failed - skipping probabilistic evaluation to avoid unnecessary judge spend", {
78859
- skippedCalls: String(probabilisticCriteria.length)
78860
- });
79255
+ info(
79256
+ "All deterministic criteria failed - skipping probabilistic evaluation to avoid unnecessary judge spend",
79257
+ {
79258
+ skippedCalls: String(probabilisticCriteria.length)
79259
+ }
79260
+ );
78861
79261
  for (const criterion of probabilisticCriteria) {
78862
79262
  evaluations.push({
78863
79263
  criterionId: criterion.id,
@@ -78879,19 +79279,18 @@ async function evaluateRun(criteria, context, config2) {
78879
79279
  } else if (!allDeterministicFailed) {
78880
79280
  if (deterministicFallbackFailureReason) {
78881
79281
  const cachedReason = deterministicFallbackFailureReason;
78882
- const isUpstreamLlmFailure = cachedReason === "rate_limited" || cachedReason === "provider_error" || cachedReason === "missing_credentials";
78883
- const errorReason = cachedReason === "rate_limited" ? "rate_limited" : cachedReason === "missing_credentials" ? "auth_error" : cachedReason === "provider_error" ? "provider_error" : void 0;
79282
+ const errorReason = toLlmEvalErrorReason(cachedReason);
78884
79283
  for (const criterion of probabilisticCriteria) {
78885
79284
  warn(
78886
79285
  `Probabilistic evaluation skipped due to cached ${formatFallbackFailureReason(cachedReason)} for "${criterion.id}"`
78887
79286
  );
78888
79287
  const skipEntry = {
78889
79288
  criterionId: criterion.id,
78890
- status: isUpstreamLlmFailure ? "error" : "fail",
79289
+ status: "error",
78891
79290
  confidence: 0,
78892
79291
  explanation: `Skipped. ${formatFallbackFailureReason(cachedReason)} already detected.`,
78893
79292
  fallbackFailureReason: cachedReason,
78894
- ...isUpstreamLlmFailure && errorReason ? { errorReason } : {}
79293
+ ...errorReason ? { errorReason } : {}
78895
79294
  };
78896
79295
  evaluations.push(skipEntry);
78897
79296
  }
@@ -78904,11 +79303,12 @@ async function evaluateRun(criteria, context, config2) {
78904
79303
  );
78905
79304
  judgeTokenUsage = accumulateTokenUsage(judgeTokenUsage, tokenUsage);
78906
79305
  for (const result of batchResults) {
78907
- evaluations.push(result);
79306
+ const normalizedResult = normalizeFatalLlmFailureResult(result);
79307
+ evaluations.push(normalizedResult);
78908
79308
  debug("Probabilistic evaluation (batch)", {
78909
- criterion: result.criterionId,
78910
- status: result.status,
78911
- confidence: result.confidence.toFixed(2)
79309
+ criterion: normalizedResult.criterionId,
79310
+ status: normalizedResult.status,
79311
+ confidence: normalizedResult.confidence.toFixed(2)
78912
79312
  });
78913
79313
  }
78914
79314
  } else if (probabilisticCriteria.length === 1) {
@@ -78916,11 +79316,12 @@ async function evaluateRun(criteria, context, config2) {
78916
79316
  progress(`Evaluating [P] ${criterion.description}`);
78917
79317
  const { evaluation: result, tokenUsage } = await evaluateWithLlm(criterion, context, config2);
78918
79318
  judgeTokenUsage = accumulateTokenUsage(judgeTokenUsage, tokenUsage);
78919
- evaluations.push(result);
79319
+ const normalizedResult = normalizeFatalLlmFailureResult(result);
79320
+ evaluations.push(normalizedResult);
78920
79321
  debug("Probabilistic evaluation", {
78921
79322
  criterion: criterion.id,
78922
- status: result.status,
78923
- confidence: result.confidence.toFixed(2)
79323
+ status: normalizedResult.status,
79324
+ confidence: normalizedResult.confidence.toFixed(2)
78924
79325
  });
78925
79326
  }
78926
79327
  }
@@ -79049,7 +79450,7 @@ function trimSnippet(value) {
79049
79450
  var REAL_SERVICE_HOST_RE = /\b(api\.github\.com|api\.stripe\.com|slack\.com\/api|api\.linear\.app|gmail\.googleapis\.com|www\.googleapis\.com|googleapis\.com|api\.tavily\.com|api\.apify\.com|atlassian\.net|supabase\.co)\b/i;
79050
79451
  var AUTH_FAILURE_RE = /\b(401|403|unauthorized|forbidden|bad credentials|requires authentication|invalid token|invalid api key|authentication failed|permission denied)\b/i;
79051
79452
  function isHarnessModelAuthFailure(message) {
79052
- return /Incorrect API key provided|invalid x-api-key|invalid api key|AuthenticationError|authentication error|API key.*(incorrect|invalid)|OPENAI_API_KEY|ANTHROPIC_API_KEY|GEMINI_API_KEY|401.*(openai|anthropic|gemini|api key)/i.test(message);
79453
+ return /Incorrect API key provided|invalid x-api-key|AuthenticationError|OPENAI_API_KEY|ANTHROPIC_API_KEY|GEMINI_API_KEY|401.*(openai|anthropic|gemini|api key)|(?:openai|anthropic|gemini)[\s\S]{0,120}(?:invalid api key|authentication error|api key.*(?:incorrect|invalid))/i.test(message);
79053
79454
  }
79054
79455
  function isLikelyRealServiceAuthFailure(message) {
79055
79456
  return REAL_SERVICE_HOST_RE.test(message) && AUTH_FAILURE_RE.test(message);
@@ -79070,6 +79471,9 @@ function classifyLocalHarnessFailure(message) {
79070
79471
  if (/better-sqlite3|NODE_MODULE_VERSION|ERR_DLOPEN_FAILED|compiled against a different Node\.js version|native module/i.test(message)) {
79071
79472
  return "native_dependency";
79072
79473
  }
79474
+ if (/(?:stripe|github|slack|linear|jira|supabase|discord|apify|tavily|google-workspace|googleapis)[\s\S]{0,160}(?:401|403|unauthorized|forbidden|bad credentials|invalid api key|authentication_error|requires authentication|permission denied)|(?:authentication_error|Invalid API Key provided)[\s\S]{0,160}(?:stripe|github|slack|linear|jira|supabase|discord|apify|tavily|google-workspace|googleapis)/i.test(message)) {
79475
+ return "clone_service_auth";
79476
+ }
79073
79477
  if (isHarnessModelAuthFailure(message)) {
79074
79478
  return "harness_model_auth";
79075
79479
  }
@@ -79101,6 +79505,10 @@ Fix: rebuild native modules for the Node runtime Archal uses before rerunning.`;
79101
79505
  Likely issue: the harness reached its model provider, but the configured provider key is missing or invalid.
79102
79506
  Fix: provide a valid provider key for the requested agent model before rerunning.
79103
79507
  If your harness does not use an LLM, run: archal run <scenario> --agent-model none`;
79508
+ case "clone_service_auth":
79509
+ return `${intro}
79510
+ Likely issue: the harness reached a clone service, but the request used missing or invalid service-shaped credentials.
79511
+ Fix: use the clone URL from AGENT_CLONE_URLS, add AGENT_ROUTE_HEADERS to the request, and keep the service Authorization header/API key from the matching injected credential such as GITHUB_TOKEN, SLACK_TOKEN, STRIPE_API_KEY, or SUPABASE_SERVICE_ROLE_KEY.`;
79104
79512
  case "service_bridge":
79105
79513
  return `${intro}
79106
79514
  Likely issue: the harness booted, but one of its service boundaries is still stubbed or app-only on the headless path.
@@ -79343,6 +79751,16 @@ function compactFailureExplanation(explanation) {
79343
79751
  if (firstLine.length <= 600) return firstLine;
79344
79752
  return `${firstLine.slice(0, 597)}...`;
79345
79753
  }
79754
+ var RESPONSE_ONLY_CRITERION_PATTERN = /\b(?:final\s+answer|agent\s+(?:response|reply|answer)|response\s+text|text\s+response|explains?\s+why|describes?\s+why|refusal|refuses?)\b/i;
79755
+ function hasConfiguredServiceClones(scenario) {
79756
+ return (scenario.config?.twins ?? []).length > 0;
79757
+ }
79758
+ function isExplicitTextResponseOnlyCriterion(criterion) {
79759
+ return RESPONSE_ONLY_CRITERION_PATTERN.test(criterion.description);
79760
+ }
79761
+ function allCriteriaAreExplicitTextResponseOnly(scenario) {
79762
+ return scenario.successCriteria.length > 0 && scenario.successCriteria.every(isExplicitTextResponseOnlyCriterion);
79763
+ }
79346
79764
  function buildFailedResult(ctx, fields) {
79347
79765
  const stateAfter = fields.stateAfter ?? fields.beforeState;
79348
79766
  const trace = annotateExpectedToolErrors(
@@ -79523,7 +79941,9 @@ function checkTraceViability(ctx, postRun, _startTime, _beforeState, agentLog, _
79523
79941
  if (agentToolCallCount === 0 && !agentResponseText) {
79524
79942
  const explanation = "Agent made no tool calls and produced no parseable response text, so the run has no evidence to evaluate.";
79525
79943
  const failureReasonCode = "agent_no_tool_calls";
79526
- warn(`Run ${ctx.runIndex + 1}: 0 tool calls and no final response. Marking the run incomplete.`);
79944
+ warn(
79945
+ `Run ${ctx.runIndex + 1}: 0 tool calls and no final response. Marking the run incomplete.`
79946
+ );
79527
79947
  return buildFailedResult(ctx, {
79528
79948
  explanation,
79529
79949
  error: `${explanation} (${failureReasonCode})
@@ -79540,6 +79960,27 @@ Agent log (stderr tail): ${agentLog?.slice(-1e3) || "(none)"}`,
79540
79960
  failureReasonCode
79541
79961
  });
79542
79962
  } else if (agentToolCallCount === 0 && agentResponseText) {
79963
+ if (hasConfiguredServiceClones(ctx.scenario) && !allCriteriaAreExplicitTextResponseOnly(ctx.scenario)) {
79964
+ const explanation = "Agent produced a text response but made no clone-observed tool calls. The service clone was not exercised, so clone-backed criteria cannot be scored from response text alone.";
79965
+ const failureReasonCode = "agent_no_tool_calls";
79966
+ warn(
79967
+ `Run ${ctx.runIndex + 1}: 0 clone-observed tool calls with response text for a service-clone scenario. Marking the run incomplete.`
79968
+ );
79969
+ return buildFailedResult(ctx, {
79970
+ explanation,
79971
+ error: `${explanation} (${failureReasonCode})`,
79972
+ outcome: "insufficient_action",
79973
+ startTime: _startTime,
79974
+ beforeState: _beforeState,
79975
+ stateAfter,
79976
+ trace,
79977
+ agentLog,
79978
+ agentTrace: _agentTrace,
79979
+ tokenUsage: _tokenUsage,
79980
+ events,
79981
+ failureReasonCode
79982
+ });
79983
+ }
79543
79984
  warn(
79544
79985
  "WARN: no clone-observed tool calls were recorded. Scenario was not exercised - score will be meaningful only for text-response checks. Continuing to evaluation because a text response was produced."
79545
79986
  );
@@ -79565,9 +80006,9 @@ async function evaluateAndBuildResult(ctx, evaluatorConfig, postRun, startTime,
79565
80006
  evaluatorConfig
79566
80007
  );
79567
80008
  const evals = evaluationResult.evaluations;
79568
- const allErrored = evals.length > 0 && evals.every((e) => e.status === "error");
79569
- const outcome = allErrored ? "degraded" : "completed";
79570
- const failureReasonCode = allErrored ? "evaluator_unavailable" : void 0;
80009
+ const hasEvaluatorError = evals.some((e) => e.status === "error");
80010
+ const outcome = hasEvaluatorError ? "degraded" : "completed";
80011
+ const failureReasonCode = hasEvaluatorError ? "evaluator_unavailable" : void 0;
79571
80012
  return {
79572
80013
  runIndex: ctx.runIndex,
79573
80014
  evaluations: evals,
@@ -79612,7 +80053,7 @@ init_src();
79612
80053
 
79613
80054
  // src/runner/mcp/aggregate-stdio-script.ts
79614
80055
  var AGGREGATE_MCP_STDIO_SCRIPT = String.raw`
79615
- const servers = JSON.parse(process.env.ARCHAL_AGGREGATE_MCP_SERVERS || '{}');
80056
+ const servers = JSON.parse(process.env.SERVICE_MCP_SERVERS || '{}');
79616
80057
  const state = { nextId: 1 };
79617
80058
 
79618
80059
  function namespaceToolName(serverName, toolName) {
@@ -79649,7 +80090,7 @@ async function fetchJson(url, options) {
79649
80090
  }
79650
80091
  }
79651
80092
  if (!response.ok) {
79652
- throw new Error('HTTP ' + response.status + ' from ' + url + ': ' + text.slice(0, 300));
80093
+ throw new Error('Service tool request failed.');
79653
80094
  }
79654
80095
  return body;
79655
80096
  }
@@ -79661,7 +80102,7 @@ async function listTools() {
79661
80102
  headers: server.headers || {},
79662
80103
  });
79663
80104
  if (!Array.isArray(list)) {
79664
- throw new Error('Expected /tools array from MCP server "' + serverName + '"');
80105
+ throw new Error('Service tool discovery failed.');
79665
80106
  }
79666
80107
  for (const tool of list) {
79667
80108
  const originalName = String(tool.name || '');
@@ -79679,7 +80120,7 @@ async function listTools() {
79679
80120
  async function callTool(params) {
79680
80121
  const parsed = parseNamespacedToolName(String(params && params.name || ''));
79681
80122
  if (!parsed || !servers[parsed.serverName]) {
79682
- throw new Error('Unknown namespaced MCP tool "' + String(params && params.name || '') + '"');
80123
+ throw new Error('Unknown service tool.');
79683
80124
  }
79684
80125
  const server = servers[parsed.serverName];
79685
80126
  return fetchJson(toolApiBaseUrl(server.url) + '/tools/call', {
@@ -79706,7 +80147,7 @@ async function handle(request) {
79706
80147
  result: {
79707
80148
  protocolVersion: request.params && request.params.protocolVersion || '2024-11-05',
79708
80149
  capabilities: { tools: {} },
79709
- serverInfo: { name: 'archal-mcp-aggregate', version: '1.0.0' },
80150
+ serverInfo: { name: 'service-tools', version: '1.0.0' },
79710
80151
  },
79711
80152
  };
79712
80153
  }
@@ -79726,7 +80167,7 @@ async function handle(request) {
79726
80167
  return {
79727
80168
  jsonrpc: '2.0',
79728
80169
  id,
79729
- error: { code: -32000, message: error && error.message ? error.message : String(error) },
80170
+ error: { code: -32000, message: error && error.message ? error.message : 'Service tool request failed.' },
79730
80171
  };
79731
80172
  }
79732
80173
  }
@@ -79750,7 +80191,7 @@ process.stdin.on('data', (chunk) => {
79750
80191
  process.stdout.write(JSON.stringify({
79751
80192
  jsonrpc: '2.0',
79752
80193
  id: null,
79753
- error: { code: -32700, message: error && error.message ? error.message : String(error) },
80194
+ error: { code: -32700, message: 'Invalid request.' },
79754
80195
  }) + '\n');
79755
80196
  });
79756
80197
  }
@@ -79765,11 +80206,11 @@ function writeAgentConfigs(runId, cloudCloneUrls, _authToken) {
79765
80206
  mcpServers[cloneName] = { url: mcpUrl };
79766
80207
  }
79767
80208
  const aggregateServer = Object.keys(mcpServers).length === 0 ? {} : {
79768
- archal: {
80209
+ services: {
79769
80210
  command: process.execPath,
79770
80211
  args: ["-e", AGGREGATE_MCP_STDIO_SCRIPT],
79771
80212
  env: {
79772
- ARCHAL_AGGREGATE_MCP_SERVERS: JSON.stringify(mcpServers)
80213
+ SERVICE_MCP_SERVERS: JSON.stringify(mcpServers)
79773
80214
  }
79774
80215
  }
79775
80216
  };
@@ -82349,9 +82790,13 @@ async function loadFileSeedsIntoTwins(config2, fetchTwin2, options = {}) {
82349
82790
  const loadingMarkerPath = getSeedLoadingMarkerPath(sessionKey);
82350
82791
  const loadedMarkerPath = getSeedLoadedMarkerPath(sessionKey);
82351
82792
  const markerDir = (0, import_node_path36.dirname)(loadedMarkerPath);
82793
+ const forceReload = options.forceReload === true;
82352
82794
  (0, import_node_fs38.mkdirSync)(markerDir, { recursive: true, mode: 448 });
82795
+ if (forceReload) {
82796
+ (0, import_node_fs38.rmSync)(loadedMarkerPath, { force: true });
82797
+ }
82353
82798
  while (true) {
82354
- if ((0, import_node_fs38.existsSync)(loadedMarkerPath)) {
82799
+ if (!forceReload && (0, import_node_fs38.existsSync)(loadedMarkerPath)) {
82355
82800
  return;
82356
82801
  }
82357
82802
  try {
@@ -82542,9 +82987,17 @@ function buildHostedTwinFetcher(cloudCloneUrls, bearerToken) {
82542
82987
  throw new Error(`Timed out waiting for hosted clone ${serviceName} to accept file seeds.`);
82543
82988
  };
82544
82989
  }
82545
- async function loadFileSeedsIntoHostedTwins(fileSeedPaths, cloudCloneUrls, bearerToken, sessionKey) {
82990
+ async function loadFileSeedsIntoHostedTwins(fileSeedPaths, cloudCloneUrls, bearerToken, sessionKey, options = {}) {
82546
82991
  const config2 = sessionKey ? buildSerializedSeedConfigWithSessionKey(fileSeedPaths, sessionKey) : buildSerializedSeedConfig(fileSeedPaths);
82547
- await loadFileSeedsIntoTwins(config2, buildHostedTwinFetcher(cloudCloneUrls, bearerToken));
82992
+ const runtimeOptions = {
82993
+ ...options.forceReload === true ? { forceReload: true } : {},
82994
+ ...options.hostedSessionId ? { hostedSessionId: options.hostedSessionId } : {}
82995
+ };
82996
+ await loadFileSeedsIntoTwins(
82997
+ config2,
82998
+ buildHostedTwinFetcher(cloudCloneUrls, bearerToken),
82999
+ Object.keys(runtimeOptions).length > 0 ? runtimeOptions : void 0
83000
+ );
82548
83001
  }
82549
83002
 
82550
83003
  // src/runner/runtime-prereqs.ts
@@ -87981,6 +88434,18 @@ Run 'archal usage' to inspect the workspace pool, or open https://www.archal.ai/
87981
88434
  }
87982
88435
 
87983
88436
  // src/runner/hosted-session/readiness.ts
88437
+ var TRANSIENT_READINESS_DETAIL_PATTERNS = [
88438
+ /\btimed?\s*out\b/i,
88439
+ /\btimeout\b/i,
88440
+ /(HTTP |status[=:]\s*)50[234]\b/i,
88441
+ /upstream\s+returned\s+50[234]\b/i,
88442
+ /\bECONNRESET\b/i,
88443
+ /\bECONNREFUSED\b/i,
88444
+ /\bETIMEDOUT\b/i,
88445
+ /\bEAI_AGAIN\b/i,
88446
+ /network\s+(error|failure)/i,
88447
+ /temporarily unavailable/i
88448
+ ];
87984
88449
  function isHostedSessionRequestError(error49) {
87985
88450
  return error49 instanceof HostedSessionRequestError || error49 instanceof Error && typeof error49.method === "string" && typeof error49.path === "string" && typeof error49.status === "number" && typeof error49.detail === "string";
87986
88451
  }
@@ -87988,6 +88453,28 @@ function getHostedSessionErrorDetail(error49) {
87988
88453
  const internalDetail = typeof error49.getInternalDetailForRetry === "function" ? error49.getInternalDetailForRetry() : void 0;
87989
88454
  return internalDetail ?? error49.detail;
87990
88455
  }
88456
+ function detailLooksTransient2(detail) {
88457
+ return TRANSIENT_READINESS_DETAIL_PATTERNS.some((pattern) => pattern.test(detail));
88458
+ }
88459
+ function isRetryableReadinessError(error49) {
88460
+ if (isHostedSessionRequestError(error49)) {
88461
+ if (error49.status === 408 || error49.status === 425 || error49.status === 429 || error49.status === 500 || error49.status === 502 || error49.status === 503 || error49.status === 504) {
88462
+ return true;
88463
+ }
88464
+ return detailLooksTransient2(getHostedSessionErrorDetail(error49));
88465
+ }
88466
+ const message = errorMessage(error49);
88467
+ if (message.startsWith("Hosted session timed out waiting for readiness")) {
88468
+ return true;
88469
+ }
88470
+ if (message.startsWith("Hosted session failed:")) {
88471
+ return detailLooksTransient2(message.slice("Hosted session failed:".length).trim());
88472
+ }
88473
+ if (message === "Hosted session failed") {
88474
+ return true;
88475
+ }
88476
+ return false;
88477
+ }
87991
88478
  async function waitForSessionReady(opts) {
87992
88479
  if (!opts.quiet) process.stderr.write("Starting cloud session...\n");
87993
88480
  const startedAt = Date.now();
@@ -88020,6 +88507,7 @@ async function waitForSessionReady(opts) {
88020
88507
  }
88021
88508
  return {
88022
88509
  ready: false,
88510
+ ...isRetryableReadinessError(error49) ? { retryable: true } : {},
88023
88511
  error: mapSessionStartError({
88024
88512
  ok: false,
88025
88513
  offline: false,
@@ -88031,10 +88519,19 @@ async function waitForSessionReady(opts) {
88031
88519
  };
88032
88520
  }
88033
88521
  const message = errorMessage(error49);
88522
+ const retryable = isRetryableReadinessError(error49);
88034
88523
  if (message.startsWith("Hosted session ")) {
88035
- return { ready: false, error: `session ${message.slice("Hosted session ".length)}` };
88524
+ return {
88525
+ ready: false,
88526
+ ...retryable ? { retryable: true } : {},
88527
+ error: `session ${message.slice("Hosted session ".length)}`
88528
+ };
88036
88529
  }
88037
- return { ready: false, error: `session poll failed: ${message}` };
88530
+ return {
88531
+ ready: false,
88532
+ ...retryable ? { retryable: true } : {},
88533
+ error: `session poll failed: ${message}`
88534
+ };
88038
88535
  }
88039
88536
  const warmupSec = Math.round((Date.now() - startedAt) / 1e3);
88040
88537
  if (!opts.quiet) process.stderr.write(`Cloud session ready (${warmupSec}s).
@@ -88546,6 +89043,7 @@ async function cleanupHostedSession(ctx) {
88546
89043
  }
88547
89044
 
88548
89045
  // src/runner/hosted-session-provisioning.ts
89046
+ var HOSTED_RUN_READINESS_ATTEMPTS = 2;
88549
89047
  function isLegacyRunConfigTimeoutRejection(result) {
88550
89048
  if (result.ok || result.status !== 400 || result.code !== "invalid_session_create_request") {
88551
89049
  return false;
@@ -88571,6 +89069,16 @@ function withoutLegacyUnsupportedRunConfig(input) {
88571
89069
  runConfig: Object.keys(runConfig).length > 0 ? runConfig : void 0
88572
89070
  };
88573
89071
  }
89072
+ async function bestEffortEndReadinessRetrySession(input) {
89073
+ const token = input.credentials?.token;
89074
+ if (!token || !input.sessionId) {
89075
+ return;
89076
+ }
89077
+ try {
89078
+ await endSession(token, input.sessionId);
89079
+ } catch {
89080
+ }
89081
+ }
88574
89082
  function resolveProvisionedSessionConnectionMaps(input) {
88575
89083
  const { sessionId, clones, endpoints, apiBaseUrls, runtimeBaseUrl } = input;
88576
89084
  const fallbackConnections = runtimeBaseUrl ? buildRuntimeTwinUrls(sessionId, clones, runtimeBaseUrl) : { endpoints: {}, apiBaseUrls: {} };
@@ -88677,103 +89185,121 @@ async function provisionHostedRunSession(input) {
88677
89185
  runProject: sessionStartConfig.runProject,
88678
89186
  source: "run"
88679
89187
  };
88680
- ctx.inFlightSessionStart = startSession(
88681
- credentials?.token ?? "",
88682
- sessionStartRequest,
88683
- sessionCreateIdempotencyKey
88684
- );
88685
- let sessionResult;
88686
- try {
88687
- sessionResult = await ctx.inFlightSessionStart;
88688
- } finally {
88689
- ctx.inFlightSessionStart = null;
88690
- }
88691
- if (!sessionResult.ok && isLegacyRunConfigTimeoutRejection(sessionResult)) {
89188
+ for (let attempt = 0; attempt < HOSTED_RUN_READINESS_ATTEMPTS; attempt += 1) {
89189
+ cloudCloneUrls = void 0;
89190
+ hostedResolvedSeeds = void 0;
89191
+ hostedApiBaseUrlOverrides = void 0;
89192
+ ctx.backendSessionId = void 0;
89193
+ ctx.sessionWorkspaceId = null;
89194
+ const idempotencyKey = attempt === 0 ? sessionCreateIdempotencyKey : `${sessionCreateIdempotencyKey}:readiness-retry:${attempt}`;
88692
89195
  ctx.inFlightSessionStart = startSession(
88693
89196
  credentials?.token ?? "",
88694
- withoutLegacyUnsupportedRunConfig(sessionStartRequest),
88695
- sessionCreateIdempotencyKey
89197
+ sessionStartRequest,
89198
+ idempotencyKey
88696
89199
  );
89200
+ let sessionResult;
88697
89201
  try {
88698
89202
  sessionResult = await ctx.inFlightSessionStart;
88699
89203
  } finally {
88700
89204
  ctx.inFlightSessionStart = null;
88701
89205
  }
88702
- }
88703
- if (!sessionResult.ok) {
88704
- ctx.runFailureMessage = mapSessionStartError(sessionResult);
88705
- return {
88706
- credentials,
88707
- cloudCloneUrls,
88708
- hostedResolvedSeeds,
88709
- hostedApiBaseUrlOverrides
88710
- };
88711
- }
88712
- credentials = getCredentials2() ?? credentials;
88713
- ctx.backendSessionId = sessionResult.data.sessionId;
88714
- ctx.sessionWorkspaceId = sessionResult.data.workspace?.id ?? null;
88715
- writeStatus({
88716
- stage: "provisioning",
88717
- sessionId: ctx.backendSessionId
88718
- });
88719
- emitSessionCreated({
88720
- scenario,
88721
- sessionId: ctx.backendSessionId,
88722
- isReused: false
88723
- });
88724
- if (!opts.quiet) {
88725
- printWorkspaceBreadcrumb({
88726
- kind: "session-pushed",
88727
- workspace: sessionResult.data.workspace ?? null
88728
- });
88729
- }
88730
- const serverResolvedSeeds = sessionResult.data.resolvedSeeds ?? {};
88731
- const { connections, missingClones } = resolveProvisionedSessionConnectionMaps({
88732
- sessionId: ctx.backendSessionId,
88733
- clones: scenario.config.twins,
88734
- endpoints: sessionResult.data.endpoints,
88735
- apiBaseUrls: sessionResult.data.apiBaseUrls,
88736
- runtimeBaseUrl: getConfiguredRuntimeBaseUrl2()
88737
- });
88738
- if (missingClones.length > 0) {
88739
- ctx.runFailureMessage = `Clone provisioning failed for: ${missingClones.join(", ")}. Try again or run: archal doctor`;
88740
- }
88741
- if (!ctx.runFailureMessage && Object.keys(connections.endpoints).length > 0) {
88742
- cloudCloneUrls = connections.endpoints;
88743
- hostedResolvedSeeds = serverResolvedSeeds;
88744
- }
88745
- if (!ctx.runFailureMessage && !opts.apiBaseUrls && Object.keys(connections.apiBaseUrls).length > 0) {
88746
- hostedApiBaseUrlOverrides = connections.apiBaseUrls;
88747
- }
88748
- const enginePlan = engine.plan;
88749
- if (!ctx.runFailureMessage && enginePlan.kind === "api" && !enginePlan.twinUrlsPath) {
88750
- ctx.generatedTwinUrlMapPath = (0, import_node_path45.resolve)(
88751
- `.archal-session-${ctx.backendSessionId}-engine-twin-urls.json`
88752
- );
88753
- const result = writeTempJsonMap(
88754
- ctx.generatedTwinUrlMapPath,
88755
- connections.endpoints,
88756
- "engine clone URL map"
88757
- );
88758
- if (!result.ok) {
88759
- ctx.runFailureMessage = result.error;
89206
+ if (!sessionResult.ok && isLegacyRunConfigTimeoutRejection(sessionResult)) {
89207
+ ctx.inFlightSessionStart = startSession(
89208
+ credentials?.token ?? "",
89209
+ withoutLegacyUnsupportedRunConfig(sessionStartRequest),
89210
+ idempotencyKey
89211
+ );
89212
+ try {
89213
+ sessionResult = await ctx.inFlightSessionStart;
89214
+ } finally {
89215
+ ctx.inFlightSessionStart = null;
89216
+ }
88760
89217
  }
88761
- }
88762
- if (!ctx.runFailureMessage) {
88763
- const readyResult = await waitForSessionReady({
89218
+ if (!sessionResult.ok) {
89219
+ ctx.runFailureMessage = mapSessionStartError(sessionResult);
89220
+ return {
89221
+ credentials,
89222
+ cloudCloneUrls,
89223
+ hostedResolvedSeeds,
89224
+ hostedApiBaseUrlOverrides
89225
+ };
89226
+ }
89227
+ credentials = getCredentials2() ?? credentials;
89228
+ ctx.backendSessionId = sessionResult.data.sessionId;
89229
+ ctx.sessionWorkspaceId = sessionResult.data.workspace?.id ?? null;
89230
+ writeStatus({
89231
+ stage: "provisioning",
89232
+ sessionId: ctx.backendSessionId
89233
+ });
89234
+ emitSessionCreated({
89235
+ scenario,
88764
89236
  sessionId: ctx.backendSessionId,
88765
- twins: scenario.config.twins,
88766
- quiet: opts.quiet,
88767
- credentials: credentials ?? { token: "" }
88768
- });
88769
- if (readyResult.ready) {
88770
- writeStatus({
88771
- stage: "session_ready",
88772
- sessionId: ctx.backendSessionId
89237
+ isReused: false
89238
+ });
89239
+ if (!opts.quiet) {
89240
+ printWorkspaceBreadcrumb({
89241
+ kind: "session-pushed",
89242
+ workspace: sessionResult.data.workspace ?? null
88773
89243
  });
88774
- } else {
88775
- ctx.runFailureMessage = readyResult.error;
88776
89244
  }
89245
+ const serverResolvedSeeds = sessionResult.data.resolvedSeeds ?? {};
89246
+ const { connections, missingClones } = resolveProvisionedSessionConnectionMaps({
89247
+ sessionId: ctx.backendSessionId,
89248
+ clones: scenario.config.twins,
89249
+ endpoints: sessionResult.data.endpoints,
89250
+ apiBaseUrls: sessionResult.data.apiBaseUrls,
89251
+ runtimeBaseUrl: getConfiguredRuntimeBaseUrl2()
89252
+ });
89253
+ if (missingClones.length > 0) {
89254
+ ctx.runFailureMessage = `Clone provisioning failed for: ${missingClones.join(", ")}. Try again or run: archal doctor`;
89255
+ }
89256
+ if (!ctx.runFailureMessage && Object.keys(connections.endpoints).length > 0) {
89257
+ cloudCloneUrls = connections.endpoints;
89258
+ hostedResolvedSeeds = serverResolvedSeeds;
89259
+ }
89260
+ if (!ctx.runFailureMessage && !opts.apiBaseUrls && Object.keys(connections.apiBaseUrls).length > 0) {
89261
+ hostedApiBaseUrlOverrides = connections.apiBaseUrls;
89262
+ }
89263
+ if (!ctx.runFailureMessage) {
89264
+ const readyResult = await waitForSessionReady({
89265
+ sessionId: ctx.backendSessionId,
89266
+ twins: scenario.config.twins,
89267
+ quiet: opts.quiet,
89268
+ credentials: credentials ?? { token: "" }
89269
+ });
89270
+ if (readyResult.ready) {
89271
+ const enginePlan = engine.plan;
89272
+ if (!ctx.runFailureMessage && enginePlan.kind === "api" && !enginePlan.twinUrlsPath) {
89273
+ ctx.generatedTwinUrlMapPath = (0, import_node_path45.resolve)(
89274
+ `.archal-session-${ctx.backendSessionId}-engine-twin-urls.json`
89275
+ );
89276
+ const result = writeTempJsonMap(
89277
+ ctx.generatedTwinUrlMapPath,
89278
+ connections.endpoints,
89279
+ "engine clone URL map"
89280
+ );
89281
+ if (!result.ok) {
89282
+ ctx.runFailureMessage = result.error;
89283
+ }
89284
+ }
89285
+ if (!ctx.runFailureMessage) {
89286
+ writeStatus({
89287
+ stage: "session_ready",
89288
+ sessionId: ctx.backendSessionId
89289
+ });
89290
+ }
89291
+ } else if (readyResult.retryable && attempt + 1 < HOSTED_RUN_READINESS_ATTEMPTS) {
89292
+ await bestEffortEndReadinessRetrySession({
89293
+ credentials,
89294
+ sessionId: ctx.backendSessionId
89295
+ });
89296
+ writeStatus({ stage: "provisioning" });
89297
+ continue;
89298
+ } else {
89299
+ ctx.runFailureMessage = readyResult.error;
89300
+ }
89301
+ }
89302
+ break;
88777
89303
  }
88778
89304
  return {
88779
89305
  credentials,
@@ -89272,7 +89798,11 @@ async function executeRunForScenario(scenarioArg, opts, command, configDefaults,
89272
89798
  fileSeedPaths,
89273
89799
  cloudCloneUrls,
89274
89800
  credentials?.token,
89275
- ctx.backendSessionId ?? runId
89801
+ ctx.backendSessionId ?? runId,
89802
+ {
89803
+ forceReload: runOpts.freshSeed === true,
89804
+ ...ctx.backendSessionId ? { hostedSessionId: ctx.backendSessionId } : {}
89805
+ }
89276
89806
  );
89277
89807
  info("File seeds loaded into clones");
89278
89808
  } catch (err) {
@@ -92080,6 +92610,7 @@ init_ansi();
92080
92610
 
92081
92611
  // src/commands/clone/shared.ts
92082
92612
  init_src2();
92613
+ init_src();
92083
92614
  init_auth();
92084
92615
  init_api_client();
92085
92616
  init_errors6();
@@ -92215,13 +92746,14 @@ function printReadyBanner(sessionId, twins, apiBaseUrls) {
92215
92746
  continue;
92216
92747
  }
92217
92748
  const restUrl = toRestUrl(url2);
92218
- const mcpUrl = toMcpUrl(url2);
92219
92749
  process.stderr.write(`${GREEN}\u2713${RESET} ${BOLD}${twin}${RESET} clone ready
92220
92750
  `);
92221
92751
  process.stderr.write(` REST: ${CYAN}${restUrl}${RESET}
92222
92752
  `);
92223
- process.stderr.write(` MCP: ${CYAN}${mcpUrl}${RESET}
92753
+ if (supportsHostedCloneMcp(twin)) {
92754
+ process.stderr.write(` MCP: ${CYAN}${toMcpUrl(url2)}${RESET}
92224
92755
  `);
92756
+ }
92225
92757
  process.stderr.write(
92226
92758
  ` Control auth: ${DIM}x-route-authorization: Bearer $ARCHAL_TOKEN${RESET}
92227
92759
  `
@@ -93917,7 +94449,7 @@ function buildOwnerTraceDetailUrl(baseUrl, rootTraceId) {
93917
94449
  return `${trimmed}/v1/traces/root/${encodeURIComponent(rootTraceId)}`;
93918
94450
  }
93919
94451
  function isEvalTrace(trace) {
93920
- return !trace.rootTraceId.startsWith("twin-session-");
94452
+ return !trace.rootTraceId.startsWith("session-proxy-") && !trace.rootTraceId.startsWith("twin-session-");
93921
94453
  }
93922
94454
  var LOCAL_RUN_ARTIFACTS_PATH = ".archal/cache/runs/";
93923
94455
  var NO_REMOTE_TRACES_MESSAGE = `No remote traces found. If you just ran a scenario without remote trace upload, local run artifacts are under ${LOCAL_RUN_ARTIFACTS_PATH}.`;