@poncho-ai/harness 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +4 -42
- package/dist/index.js +76 -246
- package/package.json +1 -2
- package/src/agent-parser.ts +7 -0
- package/src/config.ts +0 -6
- package/src/harness.ts +69 -215
- package/src/index.ts +0 -1
- package/src/reminder-store.ts +13 -4
- package/src/telemetry.ts +0 -8
- package/test/agent-parser.test.ts +74 -0
- package/test/telemetry.test.ts +0 -21
- package/.turbo/turbo-lint.log +0 -6
- package/.turbo/turbo-test.log +0 -34
- package/src/latitude-capture.ts +0 -48
package/dist/index.js
CHANGED
|
@@ -113,11 +113,13 @@ var parseCronJobs = (value) => {
|
|
|
113
113
|
validateTimezone(timezone, path);
|
|
114
114
|
}
|
|
115
115
|
const channel = typeof jobValue.channel === "string" && jobValue.channel.trim() ? jobValue.channel.trim() : void 0;
|
|
116
|
+
const maxRuns = typeof jobValue.maxRuns === "number" && jobValue.maxRuns > 0 ? Math.max(1, Math.floor(jobValue.maxRuns)) : void 0;
|
|
116
117
|
jobs[jobName] = {
|
|
117
118
|
schedule: jobValue.schedule.trim(),
|
|
118
119
|
task: jobValue.task,
|
|
119
120
|
timezone,
|
|
120
|
-
channel
|
|
121
|
+
channel,
|
|
122
|
+
maxRuns
|
|
121
123
|
};
|
|
122
124
|
}
|
|
123
125
|
return jobs;
|
|
@@ -1467,8 +1469,6 @@ All credentials in \`poncho.config.js\` use **env var name** fields (\`*Env\` su
|
|
|
1467
1469
|
| \`auth.tokenEnv\` | \`PONCHO_AUTH_TOKEN\` | Auth passphrase / bearer token |
|
|
1468
1470
|
| \`storage.urlEnv\` | \`UPSTASH_REDIS_REST_URL\` / \`REDIS_URL\` | Storage connection URL |
|
|
1469
1471
|
| \`storage.tokenEnv\` | \`UPSTASH_REDIS_REST_TOKEN\` | Upstash REST token |
|
|
1470
|
-
| \`telemetry.latitude.apiKeyEnv\` | \`LATITUDE_API_KEY\` | Latitude API key |
|
|
1471
|
-
| \`telemetry.latitude.projectIdEnv\` | \`LATITUDE_PROJECT_ID\` | Latitude project ID |
|
|
1472
1472
|
| \`messaging[].botTokenEnv\` | \`SLACK_BOT_TOKEN\` | Slack bot token |
|
|
1473
1473
|
| \`messaging[].signingSecretEnv\` | \`SLACK_SIGNING_SECRET\` | Slack signing secret |
|
|
1474
1474
|
| \`messaging[].botTokenEnv\` | \`TELEGRAM_BOT_TOKEN\` | Telegram bot token |
|
|
@@ -1557,7 +1557,7 @@ export default {
|
|
|
1557
1557
|
},
|
|
1558
1558
|
},
|
|
1559
1559
|
|
|
1560
|
-
// Telemetry
|
|
1560
|
+
// Telemetry \u2014 send OpenTelemetry traces to any OTLP-compatible collector
|
|
1561
1561
|
telemetry: {
|
|
1562
1562
|
enabled: true,
|
|
1563
1563
|
// Generic OTLP: string shorthand or { url, headers? } object
|
|
@@ -1567,12 +1567,6 @@ export default {
|
|
|
1567
1567
|
// url: 'https://api.honeycomb.io/v1/traces',
|
|
1568
1568
|
// headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },
|
|
1569
1569
|
// },
|
|
1570
|
-
// Latitude (reads from LATITUDE_API_KEY and LATITUDE_PROJECT_ID env vars by default)
|
|
1571
|
-
latitude: {
|
|
1572
|
-
// apiKeyEnv: 'LATITUDE_API_KEY', // default
|
|
1573
|
-
// projectIdEnv: 'LATITUDE_PROJECT_ID', // default
|
|
1574
|
-
path: 'your/prompt-path', // optional, defaults to agent name
|
|
1575
|
-
},
|
|
1576
1570
|
},
|
|
1577
1571
|
|
|
1578
1572
|
// Messaging platform integrations
|
|
@@ -1652,9 +1646,6 @@ Remote storage keys are namespaced and versioned, for example \`poncho:v1:<agent
|
|
|
1652
1646
|
| \`PONCHO_INTERNAL_SECRET\` | No | Shared secret used by internal serverless callbacks (recommended for Vercel/Lambda) |
|
|
1653
1647
|
| \`PONCHO_SELF_BASE_URL\` | No | Explicit base URL for internal self-callbacks when auto-detection is unavailable |
|
|
1654
1648
|
| \`OTEL_EXPORTER_OTLP_ENDPOINT\` | No | OTLP trace endpoint (Jaeger, Tempo, Honeycomb, etc.) |
|
|
1655
|
-
| \`LATITUDE_API_KEY\` | No | Latitude dashboard integration |
|
|
1656
|
-
| \`LATITUDE_PROJECT_ID\` | No | Latitude project identifier for capture traces |
|
|
1657
|
-
| \`LATITUDE_PATH\` | No | Latitude prompt path for grouping traces |
|
|
1658
1649
|
| \`KV_REST_API_URL\` | No | Upstash REST URL (Vercel Marketplace naming) |
|
|
1659
1650
|
| \`KV_REST_API_TOKEN\` | No | Upstash REST write token (Vercel Marketplace naming) |
|
|
1660
1651
|
| \`UPSTASH_REDIS_REST_URL\` | No | Upstash REST URL (direct Upstash naming) |
|
|
@@ -1732,30 +1723,6 @@ export default {
|
|
|
1732
1723
|
}
|
|
1733
1724
|
\`\`\`
|
|
1734
1725
|
|
|
1735
|
-
### Latitude integration (optional)
|
|
1736
|
-
|
|
1737
|
-
Send traces to [Latitude](https://latitude.so) for a dashboard with cost tracking and prompt management:
|
|
1738
|
-
|
|
1739
|
-
\`\`\`bash
|
|
1740
|
-
LATITUDE_API_KEY=lat_xxx
|
|
1741
|
-
LATITUDE_PROJECT_ID=123
|
|
1742
|
-
LATITUDE_PATH=agents/my-agent/run
|
|
1743
|
-
\`\`\`
|
|
1744
|
-
|
|
1745
|
-
Or configure via \`poncho.config.js\`:
|
|
1746
|
-
|
|
1747
|
-
\`\`\`javascript
|
|
1748
|
-
telemetry: {
|
|
1749
|
-
latitude: {
|
|
1750
|
-
// apiKeyEnv: 'LATITUDE_API_KEY', // default
|
|
1751
|
-
// projectIdEnv: 'LATITUDE_PROJECT_ID', // default
|
|
1752
|
-
path: 'your/prompt-path',
|
|
1753
|
-
},
|
|
1754
|
-
}
|
|
1755
|
-
\`\`\`
|
|
1756
|
-
|
|
1757
|
-
Both \`otlp\` and \`latitude\` can be configured simultaneously \u2014 all spans flow to both destinations.
|
|
1758
|
-
|
|
1759
1726
|
## Security
|
|
1760
1727
|
|
|
1761
1728
|
### Protect your endpoint
|
|
@@ -3220,13 +3187,14 @@ var parseReminderList = (raw) => {
|
|
|
3220
3187
|
var pruneStale = (reminders) => {
|
|
3221
3188
|
const cutoff = Date.now() - STALE_CANCELLED_MS;
|
|
3222
3189
|
return reminders.filter(
|
|
3223
|
-
(r) => r.status === "pending" || r.createdAt > cutoff
|
|
3190
|
+
(r) => r.status === "pending" || r.status === "cancelled" && r.createdAt > cutoff
|
|
3224
3191
|
);
|
|
3225
3192
|
};
|
|
3226
3193
|
var generateId2 = () => (globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`).slice(0, 8);
|
|
3227
3194
|
var InMemoryReminderStore = class {
|
|
3228
3195
|
reminders = [];
|
|
3229
3196
|
async list() {
|
|
3197
|
+
this.reminders = pruneStale(this.reminders);
|
|
3230
3198
|
return [...this.reminders];
|
|
3231
3199
|
}
|
|
3232
3200
|
async create(input) {
|
|
@@ -3283,7 +3251,10 @@ var FileReminderStore = class {
|
|
|
3283
3251
|
await writeJsonAtomic3(fp, reminders);
|
|
3284
3252
|
}
|
|
3285
3253
|
async list() {
|
|
3286
|
-
|
|
3254
|
+
const all = await this.readAll();
|
|
3255
|
+
const pruned = pruneStale(all);
|
|
3256
|
+
if (pruned.length !== all.length) await this.writeAll(pruned);
|
|
3257
|
+
return pruned;
|
|
3287
3258
|
}
|
|
3288
3259
|
async create(input) {
|
|
3289
3260
|
const reminder = {
|
|
@@ -3349,7 +3320,10 @@ var KVBackedReminderStore = class {
|
|
|
3349
3320
|
}
|
|
3350
3321
|
}
|
|
3351
3322
|
async list() {
|
|
3352
|
-
|
|
3323
|
+
const all = await this.readAll();
|
|
3324
|
+
const pruned = pruneStale(all);
|
|
3325
|
+
if (pruned.length !== all.length) await this.writeAll(pruned);
|
|
3326
|
+
return pruned;
|
|
3353
3327
|
}
|
|
3354
3328
|
async create(input) {
|
|
3355
3329
|
let reminders;
|
|
@@ -5369,8 +5343,7 @@ var createSubagentTools = (manager) => [
|
|
|
5369
5343
|
];
|
|
5370
5344
|
|
|
5371
5345
|
// src/harness.ts
|
|
5372
|
-
import {
|
|
5373
|
-
import { trace, context as otelContext, SpanStatusCode } from "@opentelemetry/api";
|
|
5346
|
+
import { trace, context as otelContext, SpanStatusCode, SpanKind } from "@opentelemetry/api";
|
|
5374
5347
|
import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
5375
5348
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
5376
5349
|
|
|
@@ -5903,25 +5876,17 @@ When modifying \`AGENT.md\`, follow these rules strictly:
|
|
|
5903
5876
|
|
|
5904
5877
|
## Telemetry Configuration (\`poncho.config.js\`)
|
|
5905
5878
|
|
|
5906
|
-
|
|
5879
|
+
Send OpenTelemetry traces to any OTLP-compatible collector (Jaeger, Grafana Tempo, Honeycomb, Datadog, etc.):
|
|
5907
5880
|
|
|
5908
5881
|
\`\`\`javascript
|
|
5909
5882
|
telemetry: {
|
|
5910
5883
|
enabled: true,
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
path: "your/prompt-path", // optional, defaults to agent name
|
|
5915
|
-
},
|
|
5884
|
+
otlp: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
5885
|
+
// Or with auth headers:
|
|
5886
|
+
// otlp: { url: "https://api.honeycomb.io/v1/traces", headers: { "x-honeycomb-team": process.env.HONEYCOMB_API_KEY } },
|
|
5916
5887
|
},
|
|
5917
5888
|
\`\`\`
|
|
5918
5889
|
|
|
5919
|
-
- \`apiKeyEnv\` specifies the environment variable name for the Latitude API key (defaults to \`"LATITUDE_API_KEY"\`).
|
|
5920
|
-
- \`projectIdEnv\` specifies the environment variable name for the project ID (defaults to \`"LATITUDE_PROJECT_ID"\`).
|
|
5921
|
-
- With defaults, you only need \`telemetry: { latitude: {} }\` if the env vars are already named \`LATITUDE_API_KEY\` and \`LATITUDE_PROJECT_ID\`.
|
|
5922
|
-
- \`path\` must only contain letters, numbers, hyphens, underscores, dots, and slashes.
|
|
5923
|
-
- For a generic OTLP endpoint instead: \`telemetry: { otlp: process.env.OTEL_EXPORTER_OTLP_ENDPOINT }\`.
|
|
5924
|
-
|
|
5925
5890
|
## Credential Configuration Pattern
|
|
5926
5891
|
|
|
5927
5892
|
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.
|
|
@@ -5946,10 +5911,7 @@ export default {
|
|
|
5946
5911
|
tokenEnv: "UPSTASH_REDIS_REST_TOKEN", // default (falls back to KV_REST_API_TOKEN)
|
|
5947
5912
|
},
|
|
5948
5913
|
telemetry: {
|
|
5949
|
-
|
|
5950
|
-
apiKeyEnv: "LATITUDE_API_KEY", // default
|
|
5951
|
-
projectIdEnv: "LATITUDE_PROJECT_ID", // default
|
|
5952
|
-
},
|
|
5914
|
+
otlp: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
5953
5915
|
},
|
|
5954
5916
|
messaging: [{ platform: "slack" }], // reads SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET by default
|
|
5955
5917
|
}
|
|
@@ -6028,11 +5990,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
6028
5990
|
lastSkillRefreshAt = 0;
|
|
6029
5991
|
activeSkillNames = /* @__PURE__ */ new Set();
|
|
6030
5992
|
registeredMcpToolNames = /* @__PURE__ */ new Set();
|
|
6031
|
-
latitudeTelemetry;
|
|
6032
5993
|
otlpSpanProcessor;
|
|
6033
5994
|
otlpTracerProvider;
|
|
6034
5995
|
hasOtlpExporter = false;
|
|
6035
|
-
insideTelemetryCapture = false;
|
|
6036
5996
|
_browserSession;
|
|
6037
5997
|
_browserMod;
|
|
6038
5998
|
parsedAgent;
|
|
@@ -6641,22 +6601,6 @@ var AgentHarness = class _AgentHarness {
|
|
|
6641
6601
|
await bridge.discoverTools();
|
|
6642
6602
|
await this.refreshMcpTools("initialize");
|
|
6643
6603
|
const telemetryEnabled = config?.telemetry?.enabled !== false;
|
|
6644
|
-
const latitudeBlock = config?.telemetry?.latitude;
|
|
6645
|
-
const latApiKeyEnv = latitudeBlock?.apiKeyEnv ?? "LATITUDE_API_KEY";
|
|
6646
|
-
const latProjectIdEnv = latitudeBlock?.projectIdEnv ?? "LATITUDE_PROJECT_ID";
|
|
6647
|
-
const latitudeApiKey = process.env[latApiKeyEnv];
|
|
6648
|
-
const rawProjectId = process.env[latProjectIdEnv];
|
|
6649
|
-
const latitudeProjectId = rawProjectId ? parseInt(rawProjectId, 10) : void 0;
|
|
6650
|
-
if (telemetryEnabled && latitudeApiKey && latitudeProjectId) {
|
|
6651
|
-
this.latitudeTelemetry = new LatitudeTelemetry(latitudeApiKey);
|
|
6652
|
-
} else if (telemetryEnabled && latitudeBlock && (!latitudeApiKey || !latitudeProjectId)) {
|
|
6653
|
-
const missing = [];
|
|
6654
|
-
if (!latitudeApiKey) missing.push(`${latApiKeyEnv} env var`);
|
|
6655
|
-
if (!latitudeProjectId) missing.push(`${latProjectIdEnv} env var`);
|
|
6656
|
-
console.warn(
|
|
6657
|
-
`[poncho][telemetry] Latitude telemetry is configured but missing: ${missing.join(", ")}. Traces will NOT be sent.`
|
|
6658
|
-
);
|
|
6659
|
-
}
|
|
6660
6604
|
const otlpConfig = telemetryEnabled ? normalizeOtlp(config?.telemetry?.otlp) : void 0;
|
|
6661
6605
|
if (otlpConfig) {
|
|
6662
6606
|
const exporter = new OTLPTraceExporter({
|
|
@@ -6665,22 +6609,13 @@ var AgentHarness = class _AgentHarness {
|
|
|
6665
6609
|
});
|
|
6666
6610
|
const processor = new BatchSpanProcessor(exporter);
|
|
6667
6611
|
this.otlpSpanProcessor = processor;
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
}
|
|
6674
|
-
console.info(`[poncho][telemetry] OTLP exporter added (piggybacking on Latitude provider) \u2192 ${otlpConfig.url}`);
|
|
6675
|
-
} else {
|
|
6676
|
-
const provider2 = new NodeTracerProvider({
|
|
6677
|
-
spanProcessors: [processor]
|
|
6678
|
-
});
|
|
6679
|
-
provider2.register();
|
|
6680
|
-
this.otlpTracerProvider = provider2;
|
|
6681
|
-
console.info(`[poncho][telemetry] OTLP exporter active (standalone provider) \u2192 ${otlpConfig.url}`);
|
|
6682
|
-
}
|
|
6612
|
+
const provider2 = new NodeTracerProvider({
|
|
6613
|
+
spanProcessors: [processor]
|
|
6614
|
+
});
|
|
6615
|
+
provider2.register();
|
|
6616
|
+
this.otlpTracerProvider = provider2;
|
|
6683
6617
|
this.hasOtlpExporter = true;
|
|
6618
|
+
console.info(`[poncho][telemetry] OTLP exporter active \u2192 ${otlpConfig.url}`);
|
|
6684
6619
|
}
|
|
6685
6620
|
}
|
|
6686
6621
|
async buildBrowserStoragePersistence(config, sessionId) {
|
|
@@ -6824,14 +6759,6 @@ var AgentHarness = class _AgentHarness {
|
|
|
6824
6759
|
this._browserSession = void 0;
|
|
6825
6760
|
}
|
|
6826
6761
|
await this.mcpBridge?.stopLocalServers();
|
|
6827
|
-
if (this.latitudeTelemetry) {
|
|
6828
|
-
await this.latitudeTelemetry.shutdown().catch((err) => {
|
|
6829
|
-
console.warn(
|
|
6830
|
-
`[poncho][telemetry] Latitude telemetry shutdown error: ${err instanceof Error ? err.message : String(err)}`
|
|
6831
|
-
);
|
|
6832
|
-
});
|
|
6833
|
-
this.latitudeTelemetry = void 0;
|
|
6834
|
-
}
|
|
6835
6762
|
if (this.otlpSpanProcessor) {
|
|
6836
6763
|
await this.otlpSpanProcessor.shutdown().catch((err) => {
|
|
6837
6764
|
console.warn(
|
|
@@ -6854,85 +6781,21 @@ var AgentHarness = class _AgentHarness {
|
|
|
6854
6781
|
return this.dispatcher.list();
|
|
6855
6782
|
}
|
|
6856
6783
|
/**
|
|
6857
|
-
* Wraps the run() generator with
|
|
6858
|
-
*
|
|
6859
|
-
* Streams events in real-time using an event queue pattern.
|
|
6784
|
+
* Wraps the run() generator with an OTel root span (invoke_agent) so all
|
|
6785
|
+
* child spans (LLM calls via AI SDK, tool execution) group under one trace.
|
|
6860
6786
|
*/
|
|
6861
6787
|
async *runWithTelemetry(input) {
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
const
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6871
|
-
const conversationUuid = rawConversationId && UUID_RE.test(rawConversationId) ? rawConversationId : void 0;
|
|
6872
|
-
console.info(
|
|
6873
|
-
`[poncho][telemetry] Latitude telemetry active \u2013 projectId=${projectId}, path="${path}"${conversationUuid ? `, conversation="${conversationUuid}"` : ""}`
|
|
6874
|
-
);
|
|
6875
|
-
const eventQueue = [];
|
|
6876
|
-
let queueResolve = null;
|
|
6877
|
-
let generatorDone = false;
|
|
6878
|
-
let generatorError = null;
|
|
6879
|
-
const capturePromise = telemetry.capture({ projectId, path, conversationUuid }, async () => {
|
|
6880
|
-
this.insideTelemetryCapture = true;
|
|
6881
|
-
try {
|
|
6882
|
-
for await (const event of this.run(input)) {
|
|
6883
|
-
eventQueue.push(event);
|
|
6884
|
-
if (queueResolve) {
|
|
6885
|
-
const resolve14 = queueResolve;
|
|
6886
|
-
queueResolve = null;
|
|
6887
|
-
resolve14();
|
|
6888
|
-
}
|
|
6889
|
-
}
|
|
6890
|
-
} catch (error) {
|
|
6891
|
-
generatorError = error;
|
|
6892
|
-
} finally {
|
|
6893
|
-
this.insideTelemetryCapture = false;
|
|
6894
|
-
generatorDone = true;
|
|
6895
|
-
if (queueResolve) {
|
|
6896
|
-
queueResolve();
|
|
6897
|
-
queueResolve = null;
|
|
6898
|
-
}
|
|
6788
|
+
if (this.hasOtlpExporter) {
|
|
6789
|
+
const tracer = trace.getTracer("gen_ai");
|
|
6790
|
+
const agentName = this.parsedAgent?.frontmatter.name ?? "agent";
|
|
6791
|
+
const rootSpan = tracer.startSpan(`invoke_agent ${agentName}`, {
|
|
6792
|
+
kind: SpanKind.INTERNAL,
|
|
6793
|
+
attributes: {
|
|
6794
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
6795
|
+
...input.conversationId ? { "gen_ai.conversation.id": input.conversationId } : {}
|
|
6899
6796
|
}
|
|
6900
6797
|
});
|
|
6901
|
-
try {
|
|
6902
|
-
while (!generatorDone || eventQueue.length > 0) {
|
|
6903
|
-
if (eventQueue.length > 0) {
|
|
6904
|
-
yield eventQueue.shift();
|
|
6905
|
-
} else if (!generatorDone) {
|
|
6906
|
-
await new Promise((resolve14) => {
|
|
6907
|
-
queueResolve = resolve14;
|
|
6908
|
-
});
|
|
6909
|
-
}
|
|
6910
|
-
}
|
|
6911
|
-
if (generatorError) {
|
|
6912
|
-
throw generatorError;
|
|
6913
|
-
}
|
|
6914
|
-
} finally {
|
|
6915
|
-
try {
|
|
6916
|
-
await capturePromise;
|
|
6917
|
-
} finally {
|
|
6918
|
-
try {
|
|
6919
|
-
await telemetry.flush();
|
|
6920
|
-
console.info("[poncho][telemetry] flush completed");
|
|
6921
|
-
} catch (flushErr) {
|
|
6922
|
-
console.error("[poncho][telemetry] flush failed:", flushErr);
|
|
6923
|
-
}
|
|
6924
|
-
}
|
|
6925
|
-
}
|
|
6926
|
-
} else if (this.hasOtlpExporter) {
|
|
6927
|
-
const tracer = trace.getTracer("poncho");
|
|
6928
|
-
const agentName = this.parsedAgent?.frontmatter.name ?? "agent";
|
|
6929
|
-
const rootSpan = tracer.startSpan(`agent.run ${agentName}`);
|
|
6930
|
-
rootSpan.setAttribute("poncho.agent.name", agentName);
|
|
6931
|
-
if (input.conversationId) {
|
|
6932
|
-
rootSpan.setAttribute("poncho.conversation.id", input.conversationId);
|
|
6933
|
-
}
|
|
6934
6798
|
const spanContext = trace.setSpan(otelContext.active(), rootSpan);
|
|
6935
|
-
this.insideTelemetryCapture = true;
|
|
6936
6799
|
try {
|
|
6937
6800
|
const gen = this.run(input);
|
|
6938
6801
|
let next;
|
|
@@ -6949,7 +6812,6 @@ var AgentHarness = class _AgentHarness {
|
|
|
6949
6812
|
rootSpan.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
6950
6813
|
throw error;
|
|
6951
6814
|
} finally {
|
|
6952
|
-
this.insideTelemetryCapture = false;
|
|
6953
6815
|
rootSpan.end();
|
|
6954
6816
|
try {
|
|
6955
6817
|
await this.otlpSpanProcessor?.forceFlush();
|
|
@@ -6975,6 +6837,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
6975
6837
|
await this.initialize();
|
|
6976
6838
|
}
|
|
6977
6839
|
const memoryPromise = this.memoryStore ? this.memoryStore.getMainMemory() : void 0;
|
|
6840
|
+
const todosPromise = this.todoStore ? this.todoStore.get(input.conversationId ?? "__default__") : void 0;
|
|
6978
6841
|
await this.refreshAgentIfChanged();
|
|
6979
6842
|
await this.refreshSkillsIfChanged();
|
|
6980
6843
|
let agent = this.parsedAgent;
|
|
@@ -7046,6 +6909,14 @@ Each conversation gets its own browser tab sharing a single browser instance. Ca
|
|
|
7046
6909
|
## Persistent Memory
|
|
7047
6910
|
|
|
7048
6911
|
${boundedMainMemory.trim()}` : "";
|
|
6912
|
+
const openTodos = (await todosPromise)?.filter(
|
|
6913
|
+
(t) => t.status === "pending" || t.status === "in_progress"
|
|
6914
|
+
) ?? [];
|
|
6915
|
+
const todoContext = openTodos.length > 0 ? `
|
|
6916
|
+
|
|
6917
|
+
## Open Tasks
|
|
6918
|
+
|
|
6919
|
+
${openTodos.map((t) => `- [${t.status === "in_progress" ? "IN PROGRESS" : "PENDING"}] ${t.content} (id: ${t.id})`).join("\n")}` : "";
|
|
7049
6920
|
const buildSystemPrompt = () => {
|
|
7050
6921
|
const agentPrompt = renderCurrentAgentPrompt();
|
|
7051
6922
|
const promptWithSkills = this.skillContextWindow ? `${agentPrompt}${developmentContext}
|
|
@@ -7054,16 +6925,9 @@ ${this.skillContextWindow}${browserContext}` : `${agentPrompt}${developmentConte
|
|
|
7054
6925
|
const timeContext = this.reminderStore ? `
|
|
7055
6926
|
|
|
7056
6927
|
Current UTC time: ${(/* @__PURE__ */ new Date()).toISOString()}` : "";
|
|
7057
|
-
return `${promptWithSkills}${memoryContext}${timeContext}
|
|
7058
|
-
|
|
7059
|
-
## Execution Integrity
|
|
7060
|
-
|
|
7061
|
-
- Do not claim that you executed a tool unless you actually emitted a tool call in this run.
|
|
7062
|
-
- Do not fabricate "Tool Used" or "Tool Result" logs as plain text.
|
|
7063
|
-
- Never output faux execution transcripts, markdown tool logs, or "Tool Used/Result" sections.
|
|
7064
|
-
- If no suitable tool is available, explicitly say that and ask for guidance.`;
|
|
6928
|
+
return `${promptWithSkills}${memoryContext}${todoContext}${timeContext}`;
|
|
7065
6929
|
};
|
|
7066
|
-
let
|
|
6930
|
+
let systemPrompt = buildSystemPrompt();
|
|
7067
6931
|
let lastPromptFingerprint = `${this.agentFileFingerprint}
|
|
7068
6932
|
${this.skillFingerprint}`;
|
|
7069
6933
|
const pushEvent = (event) => {
|
|
@@ -7225,7 +7089,7 @@ ${this.skillFingerprint}`;
|
|
|
7225
7089
|
inputSchema: t.inputSchema
|
|
7226
7090
|
}))
|
|
7227
7091
|
);
|
|
7228
|
-
const requestTokenEstimate = estimateTotalTokens(
|
|
7092
|
+
const requestTokenEstimate = estimateTotalTokens(systemPrompt, messages, toolDefsJsonForEstimate);
|
|
7229
7093
|
yield pushEvent({ type: "model:request", tokens: requestTokenEstimate });
|
|
7230
7094
|
const convertMessage = async (msg) => {
|
|
7231
7095
|
if (msg.role === "tool") {
|
|
@@ -7417,7 +7281,7 @@ ${textContent}` };
|
|
|
7417
7281
|
const modelInstance = this.modelProvider(modelName);
|
|
7418
7282
|
const compactionConfig = resolveCompactionConfig(agent.frontmatter.compaction);
|
|
7419
7283
|
if (compactionConfig.enabled && (step === 1 || step % COMPACTION_CHECK_INTERVAL_STEPS === 0)) {
|
|
7420
|
-
const estimated = estimateTotalTokens(
|
|
7284
|
+
const estimated = estimateTotalTokens(systemPrompt, messages, toolDefsJsonForEstimate);
|
|
7421
7285
|
const lastReportedInput = totalInputTokens > 0 ? totalInputTokens : 0;
|
|
7422
7286
|
const effectiveTokens = Math.max(estimated, lastReportedInput);
|
|
7423
7287
|
if (effectiveTokens > compactionConfig.trigger * contextWindow) {
|
|
@@ -7437,7 +7301,7 @@ ${textContent}` };
|
|
|
7437
7301
|
emittedMessages.pop();
|
|
7438
7302
|
}
|
|
7439
7303
|
}
|
|
7440
|
-
const tokensAfterCompaction = estimateTotalTokens(
|
|
7304
|
+
const tokensAfterCompaction = estimateTotalTokens(systemPrompt, messages, toolDefsJsonForEstimate);
|
|
7441
7305
|
latestContextTokens = tokensAfterCompaction;
|
|
7442
7306
|
toolOutputEstimateSinceModel = 0;
|
|
7443
7307
|
yield pushEvent({
|
|
@@ -7468,14 +7332,14 @@ ${textContent}` };
|
|
|
7468
7332
|
const telemetryEnabled = this.loadedConfig?.telemetry?.enabled !== false;
|
|
7469
7333
|
const result = await streamText({
|
|
7470
7334
|
model: modelInstance,
|
|
7471
|
-
system:
|
|
7335
|
+
system: systemPrompt,
|
|
7472
7336
|
messages: cachedMessages,
|
|
7473
7337
|
tools,
|
|
7474
7338
|
temperature,
|
|
7475
7339
|
abortSignal: input.abortSignal,
|
|
7476
7340
|
...typeof maxTokens === "number" ? { maxTokens } : {},
|
|
7477
7341
|
experimental_telemetry: {
|
|
7478
|
-
isEnabled: telemetryEnabled &&
|
|
7342
|
+
isEnabled: telemetryEnabled && this.hasOtlpExporter,
|
|
7479
7343
|
recordInputs: true,
|
|
7480
7344
|
recordOutputs: true
|
|
7481
7345
|
}
|
|
@@ -7820,36 +7684,21 @@ ${textContent}` };
|
|
|
7820
7684
|
return;
|
|
7821
7685
|
}
|
|
7822
7686
|
const toolSpans = /* @__PURE__ */ new Map();
|
|
7823
|
-
if (this.
|
|
7824
|
-
|
|
7825
|
-
toolSpans.set(
|
|
7826
|
-
call.id,
|
|
7827
|
-
this.latitudeTelemetry.span.tool({
|
|
7828
|
-
name: call.name,
|
|
7829
|
-
call: { id: call.id, arguments: call.input }
|
|
7830
|
-
})
|
|
7831
|
-
);
|
|
7832
|
-
}
|
|
7833
|
-
} else if (this.insideTelemetryCapture && this.hasOtlpExporter) {
|
|
7834
|
-
const tracer = trace.getTracer("poncho");
|
|
7687
|
+
if (this.hasOtlpExporter) {
|
|
7688
|
+
const tracer = trace.getTracer("gen_ai");
|
|
7835
7689
|
for (const call of approvedCalls) {
|
|
7836
|
-
const
|
|
7690
|
+
const toolDef = this.dispatcher.get(call.name);
|
|
7691
|
+
toolSpans.set(call.id, tracer.startSpan(`execute_tool ${call.name}`, {
|
|
7692
|
+
kind: SpanKind.INTERNAL,
|
|
7837
7693
|
attributes: {
|
|
7838
|
-
"
|
|
7839
|
-
"
|
|
7840
|
-
"
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
end(opts) {
|
|
7845
|
-
if (opts.result.isError) {
|
|
7846
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: String(opts.result.value) });
|
|
7847
|
-
} else {
|
|
7848
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
7849
|
-
}
|
|
7850
|
-
span.end();
|
|
7694
|
+
"gen_ai.operation.name": "execute_tool",
|
|
7695
|
+
"gen_ai.tool.name": call.name,
|
|
7696
|
+
"gen_ai.tool.call.id": call.id,
|
|
7697
|
+
"gen_ai.tool.type": "function",
|
|
7698
|
+
...toolDef?.description ? { "gen_ai.tool.description": toolDef.description } : {},
|
|
7699
|
+
"gen_ai.tool.call.arguments": JSON.stringify(call.input)
|
|
7851
7700
|
}
|
|
7852
|
-
});
|
|
7701
|
+
}));
|
|
7853
7702
|
}
|
|
7854
7703
|
}
|
|
7855
7704
|
const TOOL_DEADLINE_SENTINEL = /* @__PURE__ */ Symbol("tool_deadline");
|
|
@@ -7909,7 +7758,12 @@ ${textContent}` };
|
|
|
7909
7758
|
for (const result2 of batchResults) {
|
|
7910
7759
|
const span = toolSpans.get(result2.callId);
|
|
7911
7760
|
if (result2.error) {
|
|
7912
|
-
span
|
|
7761
|
+
if (span) {
|
|
7762
|
+
span.setAttribute("error.type", "Error");
|
|
7763
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: result2.error });
|
|
7764
|
+
span.recordException(new Error(result2.error));
|
|
7765
|
+
span.end();
|
|
7766
|
+
}
|
|
7913
7767
|
yield pushEvent({
|
|
7914
7768
|
type: "tool:error",
|
|
7915
7769
|
tool: result2.tool,
|
|
@@ -7943,7 +7797,11 @@ ${textContent}` };
|
|
|
7943
7797
|
output: { type: "json", value: { error: result2.error } }
|
|
7944
7798
|
});
|
|
7945
7799
|
} else {
|
|
7946
|
-
span
|
|
7800
|
+
if (span) {
|
|
7801
|
+
span.setAttribute("gen_ai.tool.call.result", JSON.stringify(result2.output ?? null));
|
|
7802
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
7803
|
+
span.end();
|
|
7804
|
+
}
|
|
7947
7805
|
const serialized = JSON.stringify(result2.output ?? null);
|
|
7948
7806
|
const outputTokenEstimate = Math.ceil(serialized.length / 4);
|
|
7949
7807
|
toolOutputEstimateSinceModel += outputTokenEstimate;
|
|
@@ -8053,7 +7911,7 @@ ${textContent}` };
|
|
|
8053
7911
|
const currentFingerprint = `${this.agentFileFingerprint}
|
|
8054
7912
|
${this.skillFingerprint}`;
|
|
8055
7913
|
if (currentFingerprint !== lastPromptFingerprint) {
|
|
8056
|
-
|
|
7914
|
+
systemPrompt = buildSystemPrompt();
|
|
8057
7915
|
lastPromptFingerprint = currentFingerprint;
|
|
8058
7916
|
}
|
|
8059
7917
|
}
|
|
@@ -8225,33 +8083,6 @@ ${this.skillFingerprint}`;
|
|
|
8225
8083
|
}
|
|
8226
8084
|
};
|
|
8227
8085
|
|
|
8228
|
-
// src/latitude-capture.ts
|
|
8229
|
-
var LatitudeCapture = class {
|
|
8230
|
-
apiKey;
|
|
8231
|
-
projectId;
|
|
8232
|
-
path;
|
|
8233
|
-
constructor(config) {
|
|
8234
|
-
const apiKeyEnv = config?.apiKeyEnv ?? "LATITUDE_API_KEY";
|
|
8235
|
-
this.apiKey = process.env[apiKeyEnv];
|
|
8236
|
-
const projectIdEnv = config?.projectIdEnv ?? "LATITUDE_PROJECT_ID";
|
|
8237
|
-
const rawProjectId = process.env[projectIdEnv];
|
|
8238
|
-
const projectIdNumber = rawProjectId ? Number.parseInt(rawProjectId, 10) : Number.NaN;
|
|
8239
|
-
this.projectId = Number.isFinite(projectIdNumber) ? projectIdNumber : void 0;
|
|
8240
|
-
const rawPath = config?.path ?? process.env.LATITUDE_PATH ?? process.env.LATITUDE_DOCUMENT_PATH ?? config?.defaultPath;
|
|
8241
|
-
this.path = rawPath;
|
|
8242
|
-
}
|
|
8243
|
-
isConfigured() {
|
|
8244
|
-
return !!(this.apiKey && this.projectId && this.path);
|
|
8245
|
-
}
|
|
8246
|
-
getConfig() {
|
|
8247
|
-
return {
|
|
8248
|
-
apiKey: this.apiKey,
|
|
8249
|
-
projectId: this.projectId,
|
|
8250
|
-
path: this.path
|
|
8251
|
-
};
|
|
8252
|
-
}
|
|
8253
|
-
};
|
|
8254
|
-
|
|
8255
8086
|
// src/state.ts
|
|
8256
8087
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8257
8088
|
import { mkdir as mkdir7, readFile as readFile11, readdir as readdir4, rename as rename4, rm as rm4, writeFile as writeFile8 } from "fs/promises";
|
|
@@ -9425,7 +9256,6 @@ export {
|
|
|
9425
9256
|
AgentHarness,
|
|
9426
9257
|
InMemoryConversationStore,
|
|
9427
9258
|
InMemoryStateStore,
|
|
9428
|
-
LatitudeCapture,
|
|
9429
9259
|
LocalMcpBridge,
|
|
9430
9260
|
LocalUploadStore,
|
|
9431
9261
|
OPENAI_CODEX_CLIENT_ID,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poncho-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"@ai-sdk/anthropic": "^3.0.44",
|
|
24
24
|
"@ai-sdk/openai": "^3.0.29",
|
|
25
25
|
"@aws-sdk/client-dynamodb": "^3.988.0",
|
|
26
|
-
"@latitude-data/telemetry": "^2.0.4",
|
|
27
26
|
"@opentelemetry/api": "1.9.0",
|
|
28
27
|
"@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
|
|
29
28
|
"@opentelemetry/sdk-trace-node": "^2.6.0",
|
package/src/agent-parser.ts
CHANGED
|
@@ -28,6 +28,7 @@ export interface CronJobConfig {
|
|
|
28
28
|
task: string;
|
|
29
29
|
timezone?: string;
|
|
30
30
|
channel?: string;
|
|
31
|
+
maxRuns?: number;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export interface AgentFrontmatter {
|
|
@@ -144,11 +145,17 @@ const parseCronJobs = (
|
|
|
144
145
|
? jobValue.channel.trim()
|
|
145
146
|
: undefined;
|
|
146
147
|
|
|
148
|
+
const maxRuns =
|
|
149
|
+
typeof jobValue.maxRuns === "number" && jobValue.maxRuns > 0
|
|
150
|
+
? Math.max(1, Math.floor(jobValue.maxRuns))
|
|
151
|
+
: undefined;
|
|
152
|
+
|
|
147
153
|
jobs[jobName] = {
|
|
148
154
|
schedule: jobValue.schedule.trim(),
|
|
149
155
|
task: jobValue.task,
|
|
150
156
|
timezone,
|
|
151
157
|
channel,
|
|
158
|
+
maxRuns,
|
|
152
159
|
};
|
|
153
160
|
}
|
|
154
161
|
return jobs;
|
package/src/config.ts
CHANGED
|
@@ -115,12 +115,6 @@ export interface PonchoConfig extends McpConfig {
|
|
|
115
115
|
url: string;
|
|
116
116
|
headers?: Record<string, string>;
|
|
117
117
|
};
|
|
118
|
-
latitude?: {
|
|
119
|
-
apiKeyEnv?: string;
|
|
120
|
-
projectIdEnv?: string;
|
|
121
|
-
path?: string;
|
|
122
|
-
documentPath?: string;
|
|
123
|
-
};
|
|
124
118
|
handler?: (event: unknown) => Promise<void> | void;
|
|
125
119
|
};
|
|
126
120
|
skills?: Record<string, Record<string, unknown>>;
|