@poncho-ai/harness 0.14.0 → 0.14.2

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/index.js CHANGED
@@ -330,8 +330,8 @@ var resolveStateConfig = (config) => {
330
330
  if (config?.storage) {
331
331
  return {
332
332
  provider: config.storage.provider,
333
- url: config.storage.url,
334
- token: config.storage.token,
333
+ urlEnv: config.storage.urlEnv,
334
+ tokenEnv: config.storage.tokenEnv,
335
335
  table: config.storage.table,
336
336
  region: config.storage.region,
337
337
  ttl: resolveTtl(config.storage.ttl, "conversations")
@@ -344,8 +344,8 @@ var resolveMemoryConfig = (config) => {
344
344
  return {
345
345
  enabled: config.storage.memory?.enabled ?? config.memory?.enabled,
346
346
  provider: config.storage.provider,
347
- url: config.storage.url,
348
- token: config.storage.token,
347
+ urlEnv: config.storage.urlEnv,
348
+ tokenEnv: config.storage.tokenEnv,
349
349
  table: config.storage.table,
350
350
  region: config.storage.region,
351
351
  ttl: resolveTtl(config.storage.ttl, "memory"),
@@ -1121,8 +1121,10 @@ var createMemoryStore = (agentId, config, options) => {
1121
1121
  return new InMemoryMemoryStore(ttl);
1122
1122
  }
1123
1123
  if (provider === "upstash") {
1124
- const url = config?.url ?? process.env.UPSTASH_REDIS_REST_URL ?? process.env.KV_REST_API_URL ?? "";
1125
- const token = config?.token ?? process.env.UPSTASH_REDIS_REST_TOKEN ?? process.env.KV_REST_API_TOKEN ?? "";
1124
+ const urlEnv = config?.urlEnv ?? (process.env.UPSTASH_REDIS_REST_URL ? "UPSTASH_REDIS_REST_URL" : "KV_REST_API_URL");
1125
+ const tokenEnv = config?.tokenEnv ?? (process.env.UPSTASH_REDIS_REST_TOKEN ? "UPSTASH_REDIS_REST_TOKEN" : "KV_REST_API_TOKEN");
1126
+ const url = process.env[urlEnv] ?? "";
1127
+ const token = process.env[tokenEnv] ?? "";
1126
1128
  if (url && token) {
1127
1129
  return new UpstashMemoryStore({
1128
1130
  baseUrl: url,
@@ -1134,7 +1136,8 @@ var createMemoryStore = (agentId, config, options) => {
1134
1136
  return new InMemoryMemoryStore(ttl);
1135
1137
  }
1136
1138
  if (provider === "redis") {
1137
- const url = config?.url ?? process.env.REDIS_URL ?? "";
1139
+ const urlEnv = config?.urlEnv ?? "REDIS_URL";
1140
+ const url = process.env[urlEnv] ?? "";
1138
1141
  if (url) {
1139
1142
  return new RedisMemoryStore({
1140
1143
  url,
@@ -1763,16 +1766,18 @@ var getModelContextWindow = (modelName) => {
1763
1766
  }
1764
1767
  return best ? MODEL_CONTEXT_WINDOWS[best] : DEFAULT_CONTEXT_WINDOW;
1765
1768
  };
1766
- var createModelProvider = (provider) => {
1769
+ var createModelProvider = (provider, config) => {
1767
1770
  const normalized = (provider ?? "anthropic").toLowerCase();
1768
1771
  if (normalized === "openai") {
1772
+ const apiKeyEnv2 = config?.openai?.apiKeyEnv ?? "OPENAI_API_KEY";
1769
1773
  const openai = createOpenAI({
1770
- apiKey: process.env.OPENAI_API_KEY
1774
+ apiKey: process.env[apiKeyEnv2]
1771
1775
  });
1772
1776
  return (modelName) => openai(modelName);
1773
1777
  }
1778
+ const apiKeyEnv = config?.anthropic?.apiKeyEnv ?? "ANTHROPIC_API_KEY";
1774
1779
  const anthropic = createAnthropic({
1775
- apiKey: process.env.ANTHROPIC_API_KEY
1780
+ apiKey: process.env[apiKeyEnv]
1776
1781
  });
1777
1782
  return (modelName) => anthropic(modelName);
1778
1783
  };
@@ -2812,18 +2817,51 @@ When configuring Latitude telemetry, use **exactly** these field names:
2812
2817
  telemetry: {
2813
2818
  enabled: true,
2814
2819
  latitude: {
2815
- apiKey: process.env.LATITUDE_API_KEY, // NOT "apiKeyEnv"
2816
- projectId: process.env.LATITUDE_PROJECT_ID, // string or number
2817
- path: "your/prompt-path", // optional, defaults to agent name
2820
+ apiKeyEnv: "LATITUDE_API_KEY", // env var name (default)
2821
+ projectIdEnv: "LATITUDE_PROJECT_ID", // env var name (default)
2822
+ path: "your/prompt-path", // optional, defaults to agent name
2818
2823
  },
2819
2824
  },
2820
2825
  \`\`\`
2821
2826
 
2822
- - The field is \`apiKey\` (not \`apiKeyEnv\`, \`api_key\`, or \`key\`).
2823
- - The field is \`projectId\` (not \`project_id\`, \`projectID\`, or \`project\`).
2827
+ - \`apiKeyEnv\` specifies the environment variable name for the Latitude API key (defaults to \`"LATITUDE_API_KEY"\`).
2828
+ - \`projectIdEnv\` specifies the environment variable name for the project ID (defaults to \`"LATITUDE_PROJECT_ID"\`).
2829
+ - With defaults, you only need \`telemetry: { latitude: {} }\` if the env vars are already named \`LATITUDE_API_KEY\` and \`LATITUDE_PROJECT_ID\`.
2824
2830
  - \`path\` must only contain letters, numbers, hyphens, underscores, dots, and slashes.
2825
2831
  - For a generic OTLP endpoint instead: \`telemetry: { otlp: process.env.OTEL_EXPORTER_OTLP_ENDPOINT }\`.
2826
- - Always read the env vars from \`process.env\` \u2014 do not hardcode secrets in \`poncho.config.js\`.
2832
+
2833
+ ## Credential Configuration Pattern
2834
+
2835
+ All credentials in \`poncho.config.js\` use the **env var name** pattern (\`*Env\` fields). Config specifies which environment variable to read \u2014 never the secret itself. Sensible defaults mean zero config when using conventional env var names.
2836
+
2837
+ \`\`\`javascript
2838
+ // poncho.config.js \u2014 credentials use *Env fields with defaults
2839
+ export default {
2840
+ // Model provider API keys (optional, defaults shown)
2841
+ providers: {
2842
+ anthropic: { apiKeyEnv: "ANTHROPIC_API_KEY" },
2843
+ openai: { apiKeyEnv: "OPENAI_API_KEY" },
2844
+ },
2845
+ auth: {
2846
+ required: true,
2847
+ tokenEnv: "PONCHO_AUTH_TOKEN", // default
2848
+ },
2849
+ storage: {
2850
+ provider: "upstash",
2851
+ urlEnv: "UPSTASH_REDIS_REST_URL", // default (falls back to KV_REST_API_URL)
2852
+ tokenEnv: "UPSTASH_REDIS_REST_TOKEN", // default (falls back to KV_REST_API_TOKEN)
2853
+ },
2854
+ telemetry: {
2855
+ latitude: {
2856
+ apiKeyEnv: "LATITUDE_API_KEY", // default
2857
+ projectIdEnv: "LATITUDE_PROJECT_ID", // default
2858
+ },
2859
+ },
2860
+ messaging: [{ platform: "slack" }], // reads SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET by default
2861
+ }
2862
+ \`\`\`
2863
+
2864
+ Since all fields have defaults, you only need to specify \`*Env\` when your env var name differs from the convention.
2827
2865
 
2828
2866
  ## When users ask about customization:
2829
2867
 
@@ -2838,6 +2876,7 @@ telemetry: {
2838
2876
  - To scope tools to a skill: keep server config in \`poncho.config.js\`, add desired \`allowed-tools\`/ \`approval-required\` patterns in that skill's \`SKILL.md\`, and remove global \`AGENT.md\` patterns if you do not want global availability.
2839
2877
  - Do not invent unsupported top-level config keys (for example \`model\` in \`poncho.config.js\`). Keep existing config structure unless README/spec explicitly says otherwise.
2840
2878
  - Keep \`poncho.config.js\` valid JavaScript and preserve existing imports/types/comments. If there is a JSDoc type import, do not rewrite it to a different package name.
2879
+ - Credentials always use \`*Env\` fields (env var names), never raw \`process.env.*\` values. For example, use \`apiKeyEnv: "MY_KEY"\` not \`apiKey: process.env.MY_KEY\`.
2841
2880
  - Preferred MCP config shape in \`poncho.config.js\`:
2842
2881
  \`mcp: [{ name: "linear", url: "https://mcp.linear.app/mcp", auth: { type: "bearer", tokenEnv: "LINEAR_TOKEN" } }]\`
2843
2882
  - If shell/CLI access exists, you can use \`poncho mcp add --url ... --name ... --auth-bearer-env ...\`, then \`poncho mcp tools list <server>\` and \`poncho mcp tools select <server>\`.
@@ -2851,7 +2890,6 @@ var AgentHarness = class {
2851
2890
  modelProvider;
2852
2891
  modelProviderInjected;
2853
2892
  dispatcher = new ToolDispatcher();
2854
- approvalHandler;
2855
2893
  uploadStore;
2856
2894
  skillContextWindow = "";
2857
2895
  memoryStore;
@@ -2925,7 +2963,6 @@ var AgentHarness = class {
2925
2963
  this.environment = options.environment ?? "development";
2926
2964
  this.modelProviderInjected = !!options.modelProvider;
2927
2965
  this.modelProvider = options.modelProvider ?? createModelProvider("anthropic");
2928
- this.approvalHandler = options.approvalHandler;
2929
2966
  this.uploadStore = options.uploadStore;
2930
2967
  if (options.toolDefinitions?.length) {
2931
2968
  this.dispatcher.registerMany(options.toolDefinitions);
@@ -3159,7 +3196,7 @@ var AgentHarness = class {
3159
3196
  const provider = this.parsedAgent.frontmatter.model?.provider ?? "anthropic";
3160
3197
  const memoryConfig = resolveMemoryConfig(config);
3161
3198
  if (!this.modelProviderInjected) {
3162
- this.modelProvider = createModelProvider(provider);
3199
+ this.modelProvider = createModelProvider(provider, config?.providers);
3163
3200
  }
3164
3201
  const bridge = new LocalMcpBridge(config);
3165
3202
  this.mcpBridge = bridge;
@@ -3186,10 +3223,12 @@ var AgentHarness = class {
3186
3223
  await bridge.discoverTools();
3187
3224
  await this.refreshMcpTools("initialize");
3188
3225
  const telemetryEnabled = config?.telemetry?.enabled !== false;
3189
- const latitudeApiKey = config?.telemetry?.latitude?.apiKey;
3190
- const rawProjectId = config?.telemetry?.latitude?.projectId;
3191
- const latitudeProjectId = typeof rawProjectId === "string" ? parseInt(rawProjectId, 10) : rawProjectId;
3192
3226
  const latitudeBlock = config?.telemetry?.latitude;
3227
+ const latApiKeyEnv = latitudeBlock?.apiKeyEnv ?? "LATITUDE_API_KEY";
3228
+ const latProjectIdEnv = latitudeBlock?.projectIdEnv ?? "LATITUDE_PROJECT_ID";
3229
+ const latitudeApiKey = process.env[latApiKeyEnv];
3230
+ const rawProjectId = process.env[latProjectIdEnv];
3231
+ const latitudeProjectId = rawProjectId ? parseInt(rawProjectId, 10) : void 0;
3193
3232
  if (telemetryEnabled && latitudeApiKey && latitudeProjectId) {
3194
3233
  diag.setLogger(
3195
3234
  {
@@ -3211,14 +3250,10 @@ var AgentHarness = class {
3211
3250
  this.latitudeTelemetry = new LatitudeTelemetry(latitudeApiKey, { disableBatch: true });
3212
3251
  } else if (telemetryEnabled && latitudeBlock && (!latitudeApiKey || !latitudeProjectId)) {
3213
3252
  const missing = [];
3214
- if (!latitudeApiKey) missing.push("apiKey");
3215
- if (!latitudeProjectId) missing.push("projectId");
3216
- const unknownKeys = Object.keys(latitudeBlock).filter(
3217
- (k) => !["apiKey", "projectId", "path", "documentPath"].includes(k)
3218
- );
3219
- const hint = unknownKeys.length > 0 ? ` (found unknown key${unknownKeys.length > 1 ? "s" : ""}: ${unknownKeys.join(", ")} \u2013 did you mean "apiKey"?)` : "";
3253
+ if (!latitudeApiKey) missing.push(`${latApiKeyEnv} env var`);
3254
+ if (!latitudeProjectId) missing.push(`${latProjectIdEnv} env var`);
3220
3255
  console.warn(
3221
- `[poncho][telemetry] Latitude telemetry is configured but missing: ${missing.join(", ")}${hint}. Traces will NOT be sent.`
3256
+ `[poncho][telemetry] Latitude telemetry is configured but missing: ${missing.join(", ")}. Traces will NOT be sent.`
3222
3257
  );
3223
3258
  }
3224
3259
  }
@@ -3244,8 +3279,8 @@ var AgentHarness = class {
3244
3279
  const config = this.loadedConfig;
3245
3280
  const telemetry = this.latitudeTelemetry;
3246
3281
  if (telemetry) {
3247
- const rawProjectId = config?.telemetry?.latitude?.projectId;
3248
- const projectId = typeof rawProjectId === "string" ? parseInt(rawProjectId, 10) : rawProjectId;
3282
+ const latProjectIdEnv2 = config?.telemetry?.latitude?.projectIdEnv ?? "LATITUDE_PROJECT_ID";
3283
+ const projectId = parseInt(process.env[latProjectIdEnv2] ?? "", 10);
3249
3284
  const rawPath = config?.telemetry?.latitude?.path ?? this.parsedAgent?.frontmatter.name ?? "agent";
3250
3285
  const path = rawPath.replace(/[^\w\-./]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "") || "agent";
3251
3286
  const conversationUuid = input.conversationId ?? (typeof input.parameters?.__activeConversationId === "string" ? input.parameters.__activeConversationId : void 0);
@@ -3317,6 +3352,7 @@ var AgentHarness = class {
3317
3352
  const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
3318
3353
  const softDeadlineMs = platformMaxDurationSec > 0 ? platformMaxDurationSec * 800 : 0;
3319
3354
  const messages = [...input.messages ?? []];
3355
+ const inputMessageCount = messages.length;
3320
3356
  const events = [];
3321
3357
  const systemPrompt = renderAgentPrompt(agent, {
3322
3358
  parameters: input.parameters,
@@ -3366,41 +3402,43 @@ ${boundedMainMemory.trim()}` : "";
3366
3402
  agentId: agent.frontmatter.id ?? agent.frontmatter.name,
3367
3403
  contextWindow
3368
3404
  });
3369
- if (input.files && input.files.length > 0) {
3370
- const parts = [
3371
- { type: "text", text: input.task }
3372
- ];
3373
- for (const file of input.files) {
3374
- if (this.uploadStore) {
3375
- const buf = Buffer.from(file.data, "base64");
3376
- const key = deriveUploadKey(buf, file.mediaType);
3377
- const ref = await this.uploadStore.put(key, buf, file.mediaType);
3378
- parts.push({
3379
- type: "file",
3380
- data: ref,
3381
- mediaType: file.mediaType,
3382
- filename: file.filename
3383
- });
3384
- } else {
3385
- parts.push({
3386
- type: "file",
3387
- data: file.data,
3388
- mediaType: file.mediaType,
3389
- filename: file.filename
3390
- });
3405
+ if (input.task != null) {
3406
+ if (input.files && input.files.length > 0) {
3407
+ const parts = [
3408
+ { type: "text", text: input.task }
3409
+ ];
3410
+ for (const file of input.files) {
3411
+ if (this.uploadStore) {
3412
+ const buf = Buffer.from(file.data, "base64");
3413
+ const key = deriveUploadKey(buf, file.mediaType);
3414
+ const ref = await this.uploadStore.put(key, buf, file.mediaType);
3415
+ parts.push({
3416
+ type: "file",
3417
+ data: ref,
3418
+ mediaType: file.mediaType,
3419
+ filename: file.filename
3420
+ });
3421
+ } else {
3422
+ parts.push({
3423
+ type: "file",
3424
+ data: file.data,
3425
+ mediaType: file.mediaType,
3426
+ filename: file.filename
3427
+ });
3428
+ }
3391
3429
  }
3430
+ messages.push({
3431
+ role: "user",
3432
+ content: parts,
3433
+ metadata: { timestamp: now(), id: randomUUID3() }
3434
+ });
3435
+ } else {
3436
+ messages.push({
3437
+ role: "user",
3438
+ content: input.task,
3439
+ metadata: { timestamp: now(), id: randomUUID3() }
3440
+ });
3392
3441
  }
3393
- messages.push({
3394
- role: "user",
3395
- content: parts,
3396
- metadata: { timestamp: now(), id: randomUUID3() }
3397
- });
3398
- } else {
3399
- messages.push({
3400
- role: "user",
3401
- content: input.task,
3402
- metadata: { timestamp: now(), id: randomUUID3() }
3403
- });
3404
3442
  }
3405
3443
  let responseText = "";
3406
3444
  let totalInputTokens = 0;
@@ -3640,7 +3678,6 @@ ${textContent}` };
3640
3678
  const modelInstance = this.modelProvider(modelName);
3641
3679
  const cachedMessages = addPromptCacheBreakpoints(coreMessages, modelInstance);
3642
3680
  const telemetryEnabled = this.loadedConfig?.telemetry?.enabled !== false;
3643
- const latitudeApiKey = this.loadedConfig?.telemetry?.latitude?.apiKey;
3644
3681
  const result = await streamText({
3645
3682
  model: modelInstance,
3646
3683
  system: integrityPrompt,
@@ -3650,7 +3687,7 @@ ${textContent}` };
3650
3687
  abortSignal: input.abortSignal,
3651
3688
  ...typeof maxTokens === "number" ? { maxTokens } : {},
3652
3689
  experimental_telemetry: {
3653
- isEnabled: telemetryEnabled && !!latitudeApiKey
3690
+ isEnabled: telemetryEnabled && !!this.latitudeTelemetry
3654
3691
  }
3655
3692
  });
3656
3693
  let fullText = "";
@@ -3843,45 +3880,34 @@ ${textContent}` };
3843
3880
  input: call.input,
3844
3881
  approvalId
3845
3882
  });
3846
- const approved = this.approvalHandler ? await this.approvalHandler({
3883
+ const assistantContent2 = JSON.stringify({
3884
+ text: fullText,
3885
+ tool_calls: toolCalls.map((tc) => ({
3886
+ id: tc.id,
3887
+ name: exposedToolNames.get(tc.name) ?? tc.name,
3888
+ input: tc.input
3889
+ }))
3890
+ });
3891
+ const assistantMsg = {
3892
+ role: "assistant",
3893
+ content: assistantContent2,
3894
+ metadata: { timestamp: now(), id: randomUUID3(), step }
3895
+ };
3896
+ const deltaMessages = [...messages.slice(inputMessageCount), assistantMsg];
3897
+ yield pushEvent({
3898
+ type: "tool:approval:checkpoint",
3899
+ approvalId,
3847
3900
  tool: runtimeToolName,
3901
+ toolCallId: call.id,
3848
3902
  input: call.input,
3849
- runId,
3850
- step,
3851
- approvalId
3852
- }) : false;
3853
- if (isCancelled()) {
3854
- yield emitCancellation();
3855
- return;
3856
- }
3857
- if (!approved) {
3858
- if (this.insideTelemetryCapture && this.latitudeTelemetry) {
3859
- const deniedSpan = this.latitudeTelemetry.span.tool({
3860
- name: runtimeToolName,
3861
- call: { id: call.id, arguments: call.input }
3862
- });
3863
- deniedSpan.end({ result: { value: "Tool execution denied by approval policy", isError: true } });
3864
- }
3865
- yield pushEvent({
3866
- type: "tool:approval:denied",
3867
- approvalId,
3868
- reason: "No approval handler granted execution"
3869
- });
3870
- yield pushEvent({
3871
- type: "tool:error",
3872
- tool: call.name,
3873
- error: "Tool execution denied by approval policy",
3874
- recoverable: true
3875
- });
3876
- toolResultsForModel.push({
3877
- type: "tool_result",
3878
- tool_use_id: call.id,
3879
- tool_name: runtimeToolName,
3880
- content: "Tool error: Tool execution denied by approval policy"
3881
- });
3882
- continue;
3883
- }
3884
- yield pushEvent({ type: "tool:approval:granted", approvalId });
3903
+ checkpointMessages: deltaMessages,
3904
+ pendingToolCalls: toolCalls.map((tc) => ({
3905
+ id: tc.id,
3906
+ name: exposedToolNames.get(tc.name) ?? tc.name,
3907
+ input: tc.input
3908
+ }))
3909
+ });
3910
+ return;
3885
3911
  }
3886
3912
  approvedCalls.push({
3887
3913
  id: call.id,
@@ -4015,12 +4041,64 @@ ${textContent}` };
4015
4041
  });
4016
4042
  }
4017
4043
  }
4044
+ async executeTools(calls, context) {
4045
+ return this.dispatcher.executeBatch(calls, context);
4046
+ }
4047
+ async *continueFromToolResult(input) {
4048
+ const messages = [...input.messages];
4049
+ const lastMsg = messages[messages.length - 1];
4050
+ if (!lastMsg || lastMsg.role !== "assistant") {
4051
+ throw new Error("continueFromToolResult: last message must be an assistant message with tool calls");
4052
+ }
4053
+ let allToolCalls = [];
4054
+ try {
4055
+ const parsed = JSON.parse(typeof lastMsg.content === "string" ? lastMsg.content : "");
4056
+ allToolCalls = parsed.tool_calls ?? [];
4057
+ } catch {
4058
+ throw new Error("continueFromToolResult: could not parse tool calls from last assistant message");
4059
+ }
4060
+ const providedMap = new Map(
4061
+ input.toolResults.map((r) => [r.callId, r])
4062
+ );
4063
+ const toolResultsForModel = [];
4064
+ for (const tc of allToolCalls) {
4065
+ const provided = providedMap.get(tc.id);
4066
+ if (provided) {
4067
+ toolResultsForModel.push({
4068
+ type: "tool_result",
4069
+ tool_use_id: tc.id,
4070
+ tool_name: provided.toolName,
4071
+ content: provided.error ? `Tool error: ${provided.error}` : JSON.stringify(provided.result ?? null)
4072
+ });
4073
+ } else {
4074
+ toolResultsForModel.push({
4075
+ type: "tool_result",
4076
+ tool_use_id: tc.id,
4077
+ tool_name: tc.name,
4078
+ content: "Tool error: Tool execution deferred (pending approval checkpoint)"
4079
+ });
4080
+ }
4081
+ }
4082
+ messages.push({
4083
+ role: "tool",
4084
+ content: JSON.stringify(toolResultsForModel),
4085
+ metadata: { timestamp: Date.now(), id: randomUUID3() }
4086
+ });
4087
+ yield* this.runWithTelemetry({
4088
+ messages,
4089
+ conversationId: input.conversationId,
4090
+ parameters: input.parameters,
4091
+ abortSignal: input.abortSignal
4092
+ });
4093
+ }
4018
4094
  async runToCompletion(input) {
4019
4095
  const events = [];
4020
4096
  let runId = "";
4021
4097
  let finalResult;
4022
4098
  const messages = [...input.messages ?? []];
4023
- messages.push({ role: "user", content: input.task });
4099
+ if (input.task != null) {
4100
+ messages.push({ role: "user", content: input.task });
4101
+ }
4024
4102
  for await (const event of this.runWithTelemetry(input)) {
4025
4103
  events.push(event);
4026
4104
  if (event.type === "run:started") {
@@ -4072,9 +4150,11 @@ var LatitudeCapture = class {
4072
4150
  projectId;
4073
4151
  path;
4074
4152
  constructor(config) {
4075
- this.apiKey = config?.apiKey ?? process.env.LATITUDE_API_KEY;
4076
- const rawProjectId = config?.projectId ?? process.env.LATITUDE_PROJECT_ID;
4077
- const projectIdNumber = typeof rawProjectId === "number" ? rawProjectId : rawProjectId ? Number.parseInt(rawProjectId, 10) : Number.NaN;
4153
+ const apiKeyEnv = config?.apiKeyEnv ?? "LATITUDE_API_KEY";
4154
+ this.apiKey = process.env[apiKeyEnv];
4155
+ const projectIdEnv = config?.projectIdEnv ?? "LATITUDE_PROJECT_ID";
4156
+ const rawProjectId = process.env[projectIdEnv];
4157
+ const projectIdNumber = rawProjectId ? Number.parseInt(rawProjectId, 10) : Number.NaN;
4078
4158
  this.projectId = Number.isFinite(projectIdNumber) ? projectIdNumber : void 0;
4079
4159
  const rawPath = config?.path ?? process.env.LATITUDE_PATH ?? process.env.LATITUDE_DOCUMENT_PATH ?? config?.defaultPath;
4080
4160
  this.path = rawPath;
@@ -4982,15 +5062,18 @@ var createStateStore = (config, options) => {
4982
5062
  return new InMemoryStateStore(ttl);
4983
5063
  }
4984
5064
  if (provider === "upstash") {
4985
- const url = config?.url ?? process.env.UPSTASH_REDIS_REST_URL ?? "";
4986
- const token = config?.token ?? process.env.UPSTASH_REDIS_REST_TOKEN ?? "";
5065
+ const urlEnv = config?.urlEnv ?? (process.env.UPSTASH_REDIS_REST_URL ? "UPSTASH_REDIS_REST_URL" : "KV_REST_API_URL");
5066
+ const tokenEnv = config?.tokenEnv ?? (process.env.UPSTASH_REDIS_REST_TOKEN ? "UPSTASH_REDIS_REST_TOKEN" : "KV_REST_API_TOKEN");
5067
+ const url = process.env[urlEnv] ?? "";
5068
+ const token = process.env[tokenEnv] ?? "";
4987
5069
  if (url && token) {
4988
5070
  return new UpstashStateStore(url, token, ttl);
4989
5071
  }
4990
5072
  return new InMemoryStateStore(ttl);
4991
5073
  }
4992
5074
  if (provider === "redis") {
4993
- const url = config?.url ?? process.env.REDIS_URL ?? "";
5075
+ const urlEnv = config?.urlEnv ?? "REDIS_URL";
5076
+ const url = process.env[urlEnv] ?? "";
4994
5077
  if (url) {
4995
5078
  return new RedisLikeStateStore(url, ttl);
4996
5079
  }
@@ -5016,15 +5099,18 @@ var createConversationStore = (config, options) => {
5016
5099
  return new InMemoryConversationStore(ttl);
5017
5100
  }
5018
5101
  if (provider === "upstash") {
5019
- const url = config?.url ?? (process.env.UPSTASH_REDIS_REST_URL || process.env.KV_REST_API_URL || "");
5020
- const token = config?.token ?? (process.env.UPSTASH_REDIS_REST_TOKEN || process.env.KV_REST_API_TOKEN || "");
5102
+ const urlEnv = config?.urlEnv ?? (process.env.UPSTASH_REDIS_REST_URL ? "UPSTASH_REDIS_REST_URL" : "KV_REST_API_URL");
5103
+ const tokenEnv = config?.tokenEnv ?? (process.env.UPSTASH_REDIS_REST_TOKEN ? "UPSTASH_REDIS_REST_TOKEN" : "KV_REST_API_TOKEN");
5104
+ const url = process.env[urlEnv] ?? "";
5105
+ const token = process.env[tokenEnv] ?? "";
5021
5106
  if (url && token) {
5022
5107
  return new UpstashConversationStore(url, token, workingDir, ttl, options?.agentId);
5023
5108
  }
5024
5109
  return new InMemoryConversationStore(ttl);
5025
5110
  }
5026
5111
  if (provider === "redis") {
5027
- const url = config?.url ?? process.env.REDIS_URL ?? "";
5112
+ const urlEnv = config?.urlEnv ?? "REDIS_URL";
5113
+ const url = process.env[urlEnv] ?? "";
5028
5114
  if (url) {
5029
5115
  return new RedisLikeConversationStore(url, workingDir, ttl, options?.agentId);
5030
5116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/harness",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,7 +31,7 @@
31
31
  "redis": "^5.10.0",
32
32
  "yaml": "^2.4.0",
33
33
  "zod": "^3.22.0",
34
- "@poncho-ai/sdk": "1.0.1"
34
+ "@poncho-ai/sdk": "1.0.3"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/mustache": "^4.2.6",
package/src/config.ts CHANGED
@@ -7,8 +7,8 @@ import type { StateConfig } from "./state.js";
7
7
 
8
8
  export interface StorageConfig {
9
9
  provider?: "local" | "memory" | "redis" | "upstash" | "dynamodb";
10
- url?: string;
11
- token?: string;
10
+ urlEnv?: string;
11
+ tokenEnv?: string;
12
12
  table?: string;
13
13
  region?: string;
14
14
  ttl?:
@@ -76,6 +76,7 @@ export interface PonchoConfig extends McpConfig {
76
76
  required?: boolean;
77
77
  type?: "bearer" | "header" | "custom";
78
78
  headerName?: string;
79
+ tokenEnv?: string;
79
80
  validate?: (token: string, req?: unknown) => Promise<boolean> | boolean;
80
81
  };
81
82
  state?: {
@@ -85,12 +86,16 @@ export interface PonchoConfig extends McpConfig {
85
86
  };
86
87
  memory?: MemoryConfig;
87
88
  storage?: StorageConfig;
89
+ providers?: {
90
+ openai?: { apiKeyEnv?: string };
91
+ anthropic?: { apiKeyEnv?: string };
92
+ };
88
93
  telemetry?: {
89
94
  enabled?: boolean;
90
95
  otlp?: string;
91
96
  latitude?: {
92
- apiKey?: string;
93
- projectId?: string | number;
97
+ apiKeyEnv?: string;
98
+ projectIdEnv?: string;
94
99
  path?: string;
95
100
  documentPath?: string;
96
101
  };
@@ -130,8 +135,8 @@ export const resolveStateConfig = (
130
135
  if (config?.storage) {
131
136
  return {
132
137
  provider: config.storage.provider,
133
- url: config.storage.url,
134
- token: config.storage.token,
138
+ urlEnv: config.storage.urlEnv,
139
+ tokenEnv: config.storage.tokenEnv,
135
140
  table: config.storage.table,
136
141
  region: config.storage.region,
137
142
  ttl: resolveTtl(config.storage.ttl, "conversations"),
@@ -147,8 +152,8 @@ export const resolveMemoryConfig = (
147
152
  return {
148
153
  enabled: config.storage.memory?.enabled ?? config.memory?.enabled,
149
154
  provider: config.storage.provider,
150
- url: config.storage.url,
151
- token: config.storage.token,
155
+ urlEnv: config.storage.urlEnv,
156
+ tokenEnv: config.storage.tokenEnv,
152
157
  table: config.storage.table,
153
158
  region: config.storage.region,
154
159
  ttl: resolveTtl(config.storage.ttl, "memory"),