perchai-cli 2.4.14 → 2.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/perch.mjs +70 -30
  2. package/package.json +4 -2
package/dist/perch.mjs CHANGED
@@ -83131,7 +83131,6 @@ function truncateHistoryLine(value, max2) {
83131
83131
  }
83132
83132
  var init_operatorTruth = __esm({
83133
83133
  "features/perchTerminal/runtime/operatorTruth.ts"() {
83134
- "use strict";
83135
83134
  }
83136
83135
  });
83137
83136
 
@@ -134243,7 +134242,7 @@ function validateArgs(name, args) {
134243
134242
  return "run_sandbox_code.command must be a non-empty string when provided.";
134244
134243
  if (args.language !== void 0 && args.language !== "python" && args.language !== "node")
134245
134244
  return 'run_sandbox_code.language must be either "python" or "node" when provided.';
134246
- if (args.code !== void 0 && !hasCode)
134245
+ if (args.code !== void 0 && !hasCode && !hasCommand)
134247
134246
  return "run_sandbox_code.code must be a non-empty string when provided.";
134248
134247
  }
134249
134248
  if (args.label !== void 0 && typeof args.label !== "string")
@@ -199728,7 +199727,6 @@ function containsBrowserDeliveryTask(tasks) {
199728
199727
  var BROWSER_DELIVERY_ROLE_IDS;
199729
199728
  var init_browserDeliveryLock = __esm({
199730
199729
  "features/perchTerminal/agentPlatform/browserDeliveryLock.ts"() {
199731
- "use strict";
199732
199730
  BROWSER_DELIVERY_ROLE_IDS = /* @__PURE__ */ new Set([
199733
199731
  "doc_writer",
199734
199732
  "email_sender",
@@ -213392,11 +213390,14 @@ var init_config2 = __esm({
213392
213390
 
213393
213391
  // features/perchTerminal/knowledge/knowledgeApiClient.ts
213394
213392
  async function searchKnowledgeViaApi(input, options = {}) {
213395
- const route = options.route ?? "/api/perch-terminal/knowledge/search";
213393
+ const route = resolveKnowledgeRoute(options.route ?? "/api/perch-terminal/knowledge/search");
213396
213394
  const fetchImpl = options.fetchImpl ?? fetch;
213395
+ const headers = { "content-type": "application/json" };
213396
+ const token = process.env.PERCH_MODEL_CALL_PROXY_TOKEN?.trim();
213397
+ if (token) headers.authorization = `Bearer ${token}`;
213397
213398
  const response = await fetchImpl(route, {
213398
213399
  method: "POST",
213399
- headers: { "content-type": "application/json" },
213400
+ headers,
213400
213401
  body: JSON.stringify(input)
213401
213402
  });
213402
213403
  const payload = await response.json().catch(() => ({}));
@@ -213408,6 +213409,13 @@ async function searchKnowledgeViaApi(input, options = {}) {
213408
213409
  }
213409
213410
  return payload;
213410
213411
  }
213412
+ function resolveKnowledgeRoute(route) {
213413
+ if (!route.startsWith("/")) return route;
213414
+ if (typeof window !== "undefined") return route;
213415
+ const appUrl = process.env.PERCH_MODEL_CALL_PROXY_URL?.trim() || process.env.PERCH_CLI_APP_URL?.trim() || process.env.PERCH_APP_URL?.trim();
213416
+ if (!appUrl) return route;
213417
+ return new URL(route, appUrl).toString();
213418
+ }
213411
213419
  var KnowledgeApiClientError;
213412
213420
  var init_knowledgeApiClient = __esm({
213413
213421
  "features/perchTerminal/knowledge/knowledgeApiClient.ts"() {
@@ -214630,13 +214638,22 @@ async function computeLiveSignals(store, strategyId, asOf) {
214630
214638
  }
214631
214639
  return out;
214632
214640
  }
214641
+ function ledgerFields(result2) {
214642
+ return result2.error ? { ledgerRecorded: result2.recorded, ledgerError: result2.error } : { ledgerRecorded: result2.recorded };
214643
+ }
214633
214644
  async function recordLedger(ctx, row) {
214634
- if (!ctx.supabase) return false;
214645
+ if (!ctx.supabase) {
214646
+ return { recorded: false, error: "supabase_unavailable" };
214647
+ }
214635
214648
  try {
214636
214649
  const { error } = await ctx.supabase.from("perch_ai_market_backtest_runs").insert(row);
214637
- return !error;
214638
- } catch {
214639
- return false;
214650
+ if (error) return { recorded: false, error: error.message };
214651
+ return { recorded: true };
214652
+ } catch (error) {
214653
+ return {
214654
+ recorded: false,
214655
+ error: error instanceof Error ? error.message : String(error)
214656
+ };
214640
214657
  }
214641
214658
  }
214642
214659
  var NOT_ADVICE, getMarketSignalTool, queryMarketSignalLogTool, explainMarketSignalTool, listMarketStrategiesTool, MAX_PERIOD_DAYS, ISO_DATE, runMarketBacktestTool, getMarketTrackRecordTool, marketDeskTools;
@@ -214900,7 +214917,8 @@ var init_marketDesk = __esm({
214900
214917
  holdout_acknowledged: acknowledgeHoldout,
214901
214918
  initiator: "tool",
214902
214919
  workspace_id: ctx.workspaceId ?? null,
214903
- thread_id: ctx.threadId ?? null
214920
+ thread_id: ctx.threadId ?? null,
214921
+ user_id: ctx.userId ?? null
214904
214922
  };
214905
214923
  const wf = args.walkForward && typeof args.walkForward === "object" ? args.walkForward : void 0;
214906
214924
  try {
@@ -214923,10 +214941,10 @@ var init_marketDesk = __esm({
214923
214941
  paramGrid
214924
214942
  });
214925
214943
  if (!result3.ok) {
214926
- await recordLedger(ctx, { ...ledgerBase, status: "failed", error: result3.error, holdout_overlap: false });
214927
- return { ok: false, errorCode: "walk_forward_failed", message: result3.error };
214944
+ const ledger3 = await recordLedger(ctx, { ...ledgerBase, status: "failed", error: result3.error, holdout_overlap: false });
214945
+ return { ok: false, errorCode: "walk_forward_failed", message: result3.error, ...ledgerFields(ledger3) };
214928
214946
  }
214929
- const ledgerRecorded2 = await recordLedger(ctx, {
214947
+ const ledger2 = await recordLedger(ctx, {
214930
214948
  ...ledgerBase,
214931
214949
  status: "completed",
214932
214950
  holdout_overlap: to >= resolveHoldoutStart(),
@@ -214951,17 +214969,17 @@ var init_marketDesk = __esm({
214951
214969
  testMaxDrawdown: round(f.testMetrics.maxDrawdown)
214952
214970
  })),
214953
214971
  oosMetrics: compactMetrics(result3.oosMetrics),
214954
- ledgerRecorded: ledgerRecorded2,
214972
+ ...ledgerFields(ledger2),
214955
214973
  note: "Out-of-sample (oosMetrics) is the only number that counts; train-window performance is selection, not evidence.",
214956
214974
  disclaimer: NOT_ADVICE
214957
214975
  };
214958
214976
  }
214959
214977
  const result2 = await runBacktest(store, backtestCfg);
214960
214978
  if (!result2.ok) {
214961
- await recordLedger(ctx, { ...ledgerBase, status: "failed", error: result2.error, holdout_overlap: false });
214962
- return { ok: false, errorCode: "backtest_failed", message: result2.error };
214979
+ const ledger2 = await recordLedger(ctx, { ...ledgerBase, status: "failed", error: result2.error, holdout_overlap: false });
214980
+ return { ok: false, errorCode: "backtest_failed", message: result2.error, ...ledgerFields(ledger2) };
214963
214981
  }
214964
- const ledgerRecorded = await recordLedger(ctx, {
214982
+ const ledger = await recordLedger(ctx, {
214965
214983
  ...ledgerBase,
214966
214984
  status: "completed",
214967
214985
  holdout_overlap: result2.holdout.overlapped,
@@ -214990,13 +215008,13 @@ var init_marketDesk = __esm({
214990
215008
  signalCounts: result2.signalCounts,
214991
215009
  equityCurve: downsample(result2.equityCurve, 60).map((p) => ({ date: p.date, equity: round(p.equity, 4) })),
214992
215010
  trades: { closed: result2.trades.filter((t) => t.netReturn !== null).length, open: result2.trades.filter((t) => t.exitDate === null && t.netReturn === null).length },
214993
- ledgerRecorded,
215011
+ ...ledgerFields(ledger),
214994
215012
  note: "Hypothetical backtest with modeled fees/slippage; past simulated performance does not predict future results. Every run (including this one) is recorded in the experiment ledger.",
214995
215013
  disclaimer: NOT_ADVICE
214996
215014
  };
214997
215015
  } catch (error) {
214998
215016
  if (error instanceof HoldoutViolationError) {
214999
- await recordLedger(ctx, {
215017
+ const ledger2 = await recordLedger(ctx, {
215000
215018
  ...ledgerBase,
215001
215019
  status: "failed",
215002
215020
  error: "holdout_violation",
@@ -215007,12 +215025,13 @@ var init_marketDesk = __esm({
215007
215025
  errorCode: "holdout_violation",
215008
215026
  message: error.message,
215009
215027
  holdoutStart: error.holdoutStart,
215010
- hint: "The holdout window is reserved for final evaluation. Re-run with acknowledgeHoldout: true ONLY if the user explicitly wants to spend a holdout look; the run gets flagged in the ledger either way."
215028
+ hint: "The holdout window is reserved for final evaluation. Re-run with acknowledgeHoldout: true ONLY if the user explicitly wants to spend a holdout look; the run gets flagged in the ledger either way.",
215029
+ ...ledgerFields(ledger2)
215011
215030
  };
215012
215031
  }
215013
215032
  const message = error instanceof Error ? error.message : String(error);
215014
- await recordLedger(ctx, { ...ledgerBase, status: "failed", error: message, holdout_overlap: false });
215015
- return { ok: false, errorCode: "backtest_error", message };
215033
+ const ledger = await recordLedger(ctx, { ...ledgerBase, status: "failed", error: message, holdout_overlap: false });
215034
+ return { ok: false, errorCode: "backtest_error", message, ...ledgerFields(ledger) };
215016
215035
  }
215017
215036
  }
215018
215037
  };
@@ -215190,11 +215209,22 @@ var init_registry5 = __esm({
215190
215209
  async function executeRegisteredTool(name, args, ctx) {
215191
215210
  const mod = getRegisteredTool(name);
215192
215211
  if (!mod) return { handled: false };
215212
+ if (MARKET_DESK_TOOL_NAMES.has(name) && !isMarketDeskEnabled()) {
215213
+ return {
215214
+ handled: true,
215215
+ result: {
215216
+ ok: false,
215217
+ errorCode: "market_desk_disabled",
215218
+ message: "Market Desk tools are disabled. Set PERCH_MARKET_DESK=1 to enable them."
215219
+ }
215220
+ };
215221
+ }
215193
215222
  return { handled: true, result: await mod.handler(args, ctx) };
215194
215223
  }
215195
215224
  var init_executeTool = __esm({
215196
215225
  "features/perchTerminal/runtime/toolSystem/executeTool.ts"() {
215197
215226
  "use strict";
215227
+ init_marketDeskAccess();
215198
215228
  init_registry5();
215199
215229
  }
215200
215230
  });
@@ -219674,17 +219704,18 @@ async function runLiveAgentsLoop(input) {
219674
219704
  }
219675
219705
  const mode = effectiveChatMode;
219676
219706
  const loopTools = tools;
219677
- const loopSystemPrompt = appendLiveTurnContract(context.systemPrompt, {
219707
+ const sandboxExecutionRequired = sandboxExecutionRequiredByUserText(turn.trimmedInput, loopTools);
219708
+ const loopSystemPromptBase = appendLiveTurnContract(context.systemPrompt, {
219678
219709
  chatMode: mode,
219679
219710
  toolsAvailable: loopTools.length > 0,
219680
219711
  userObjective: turn.trimmedInput,
219681
219712
  planGateRejected
219682
219713
  });
219714
+ const loopSystemPrompt = sandboxExecutionRequired ? appendSandboxExecutionRequiredContract(loopSystemPromptBase) : loopSystemPromptBase;
219683
219715
  const threadSession = turn.threadId ? getThreadSessionFromMemory(turn.threadId) : null;
219684
219716
  const contextCompaction = threadSession?.contextCompaction ?? null;
219685
219717
  const approvedToolCall = approvedToolCallFromState(workflowState);
219686
219718
  const runLoop = input.loopRunner ?? runModelToolLoop;
219687
- const sandboxExecutionRequired = sandboxExecutionRequiredByUserText(turn.trimmedInput, loopTools);
219688
219719
  const loopInput = {
219689
219720
  signal: deps.signal ?? void 0,
219690
219721
  lane: loopLane,
@@ -219715,7 +219746,7 @@ async function runLiveAgentsLoop(input) {
219715
219746
  forcedInitialToolCall: approvedToolCall ?? turn.forcedInitialToolCall ?? null,
219716
219747
  forcedInitialToolCallApproved: Boolean(approvedToolCall),
219717
219748
  stopAfterForcedInitialToolCall: Boolean(approvedToolCall),
219718
- forceToolUse: turn.forceToolUse === true || sandboxExecutionRequired,
219749
+ forceToolUse: turn.forceToolUse === true,
219719
219750
  mcpTools: turn.mcpTools ?? [],
219720
219751
  maxIterations: turnHasBrowserOperatorTools(loopTools) ? 24 : turn.coordinatorSessionActive ? 20 : void 0,
219721
219752
  attachOperatorScreenshots: turnHasBrowserOperatorTools(loopTools),
@@ -219812,10 +219843,11 @@ async function runLiveAgentsLoop(input) {
219812
219843
  if (sandboxExecutionRequired && loopResult.ok && !hasRunSandboxCodeToolCall(loopResult)) {
219813
219844
  loopResult = await runLoop({
219814
219845
  ...loopInput,
219815
- forceToolUse: true,
219816
- systemPrompt: `${loopInput.systemPrompt.trim()}
219817
-
219818
- Sandbox execution is explicitly required by the user. This turn is incomplete until you call run_sandbox_code. Use the command field when possible; pass source file paths in sources and read copied files from input/. The sandbox tool result already captures stdout/stderr and produced files; do not try to read output/report.json from the host filesystem after the run. Do not mention internal model, provider, or lane details in the user-facing answer. Do not finish with prose before a sandbox run.`
219846
+ forceToolUse: false,
219847
+ systemPrompt: appendSandboxExecutionRequiredContract(
219848
+ loopInput.systemPrompt,
219849
+ true
219850
+ )
219819
219851
  });
219820
219852
  }
219821
219853
  if (sandboxExecutionRequired && loopResult.ok && !hasRunSandboxCodeToolCall(loopResult)) {
@@ -219946,6 +219978,12 @@ function sandboxExecutionRequiredByUserText(userText, tools) {
219946
219978
  if (!mentionsSandbox) return false;
219947
219979
  return /\b(use|run|execute|call|primary|required|mandatory|must|final numbers|must come from|do not stop)\b/.test(normalized) || /sandbox.{0,80}(primary|required|mandatory|must|execute|run|numbers|code)/.test(normalized) || /(primary|required|mandatory|must|execute|run|numbers|code).{0,80}sandbox/.test(normalized);
219948
219980
  }
219981
+ function appendSandboxExecutionRequiredContract(systemPrompt, retry = false) {
219982
+ const prefix = retry ? "The previous attempt did not call run_sandbox_code." : "Sandbox execution is explicitly required by the user.";
219983
+ return `${systemPrompt.trim()}
219984
+
219985
+ ${prefix} This turn is incomplete until you call run_sandbox_code. Use the command field when possible; pass source file paths in sources and read copied files from input/. The sandbox tool result already captures stdout/stderr and produced files; do not try to read output/report.json from the host filesystem after the run. Do not mention internal model, provider, or lane details in the user-facing answer. Do not finish with prose before a sandbox run.`;
219986
+ }
219949
219987
  function hasRunSandboxCodeToolCall(loopResult) {
219950
219988
  return loopResult.toolCalls.some((call) => call.toolName === TOOL_NAMES.runSandboxCode);
219951
219989
  }
@@ -222782,7 +222820,9 @@ function maybeWarnCategoryDrift(input) {
222782
222820
  const drift = sum - input.headlineTokens;
222783
222821
  const allowed = Math.max(256, Math.ceil(input.wireOverheadTokens ?? 0) + 64);
222784
222822
  if (Math.abs(drift) <= allowed) return;
222785
- if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
222823
+ if (typeof process === "undefined" || process.env.PERCH_CONTEXT_METER_DEBUG !== "1") {
222824
+ return;
222825
+ }
222786
222826
  console.warn(
222787
222827
  `[context-meter] Category total drift in ${input.source}: categories=${sum}, headline=${input.headlineTokens}, drift=${drift}`
222788
222828
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perchai-cli",
3
- "version": "2.4.14",
3
+ "version": "2.4.16",
4
4
  "description": "Perch AI command-line interface",
5
5
  "bin": {
6
6
  "perch": "bin/perch"
@@ -12,7 +12,9 @@
12
12
  "LICENSE"
13
13
  ],
14
14
  "dependencies": {
15
- "@napi-rs/canvas": "^0.1.100"
15
+ "@mozilla/readability": "^0.6.0",
16
+ "@napi-rs/canvas": "^0.1.100",
17
+ "jsdom": "^29.1.1"
16
18
  },
17
19
  "engines": {
18
20
  "node": ">=20"