@poncho-ai/harness 0.59.8 → 0.59.10
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 +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +44 -4
- package/package.json +1 -1
- package/src/config.ts +5 -0
- package/src/harness.ts +58 -4
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.59.
|
|
2
|
+
> @poncho-ai/harness@0.59.10 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
|
|
3
3
|
> node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[embed-docs] Generated poncho-docs.ts with 4 topics
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mESM[39m Build start
|
|
11
11
|
[32mESM[39m [1mdist/isolate-F2PPSUL6.js [22m[32m53.82 KB[39m
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m559.89 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 238ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 7368ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m102.06 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.59.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`fad3918`](https://github.com/cesr/poncho-ai/commit/fad3918302114f76a29080cf28e9c003c61ef0d9) Thanks [@cesr](https://github.com/cesr)! - Stamp `session.id` / `user.id` on EVERY span, not just the invoke_agent
|
|
8
|
+
root. Observability backends resolve a span's identity from its own
|
|
9
|
+
attributes — Latitude's console session/conversation views key on the LLM
|
|
10
|
+
generation spans, so root-only attributes grouped the API-level trace but
|
|
11
|
+
left the console showing one session per turn and no user. The identity now
|
|
12
|
+
rides the OTel Context and an IdentityAttributeSpanProcessor injects it
|
|
13
|
+
into every descendant span (LLM steps, tool executions) at start.
|
|
14
|
+
|
|
15
|
+
## 0.59.9
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [`eb9600a`](https://github.com/cesr/poncho-ai/commit/eb9600a111cf74968689f8b33835d8b58c64e375) Thanks [@cesr](https://github.com/cesr)! - Root trace spans now carry `session.id` (= conversationId) and `user.id`
|
|
20
|
+
(new `config.telemetry.userId`) alongside the existing
|
|
21
|
+
`gen_ai.conversation.id` — the attributes observability backends
|
|
22
|
+
(Latitude) key session grouping and user filtering on.
|
|
23
|
+
|
|
3
24
|
## 0.59.8
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -756,6 +756,11 @@ interface PonchoConfig extends McpConfig {
|
|
|
756
756
|
url: string;
|
|
757
757
|
headers?: Record<string, string>;
|
|
758
758
|
};
|
|
759
|
+
/** End-user identifier (e.g. an email) stamped as `user.id` on every
|
|
760
|
+
* root trace span — observability backends (Latitude) group and
|
|
761
|
+
* filter traces by it. Per-harness, so a harness-per-user consumer
|
|
762
|
+
* sets it once at construction. */
|
|
763
|
+
userId?: string;
|
|
759
764
|
handler?: (event: unknown) => Promise<void> | void;
|
|
760
765
|
};
|
|
761
766
|
skills?: Record<string, Record<string, unknown>>;
|
|
@@ -1442,6 +1447,8 @@ declare class AgentHarness {
|
|
|
1442
1447
|
private otlpSpanProcessor?;
|
|
1443
1448
|
private otlpTracerProvider?;
|
|
1444
1449
|
private hasOtlpExporter;
|
|
1450
|
+
/** End-user id (config.telemetry.userId) stamped as `user.id` on root spans. */
|
|
1451
|
+
private telemetryUserId?;
|
|
1445
1452
|
private _browserSession?;
|
|
1446
1453
|
private _browserMod?;
|
|
1447
1454
|
private parsedAgent?;
|
package/dist/index.js
CHANGED
|
@@ -8745,7 +8745,7 @@ var createSubagentTools = (manager) => [
|
|
|
8745
8745
|
];
|
|
8746
8746
|
|
|
8747
8747
|
// src/harness.ts
|
|
8748
|
-
import { trace, context as otelContext, SpanStatusCode, SpanKind, diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
|
|
8748
|
+
import { trace, context as otelContext, createContextKey, SpanStatusCode, SpanKind, diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
|
|
8749
8749
|
import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
8750
8750
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
8751
8751
|
|
|
@@ -8928,6 +8928,28 @@ var telemetryLog2 = createLogger7("telemetry");
|
|
|
8928
8928
|
var costLog = createLogger7("cost");
|
|
8929
8929
|
var mcpLog2 = createLogger7("mcp");
|
|
8930
8930
|
var modelLog = createLogger7("model");
|
|
8931
|
+
var TELEMETRY_SESSION_ID_KEY = createContextKey("poncho.telemetry.session_id");
|
|
8932
|
+
var TELEMETRY_USER_ID_KEY = createContextKey("poncho.telemetry.user_id");
|
|
8933
|
+
var IdentityAttributeSpanProcessor = class {
|
|
8934
|
+
onStart(span, parentContext) {
|
|
8935
|
+
const sessionId = parentContext.getValue(TELEMETRY_SESSION_ID_KEY);
|
|
8936
|
+
if (typeof sessionId === "string" && sessionId) {
|
|
8937
|
+
span.setAttribute("session.id", sessionId);
|
|
8938
|
+
}
|
|
8939
|
+
const userId = parentContext.getValue(TELEMETRY_USER_ID_KEY);
|
|
8940
|
+
if (typeof userId === "string" && userId) {
|
|
8941
|
+
span.setAttribute("user.id", userId);
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8944
|
+
onEnd() {
|
|
8945
|
+
}
|
|
8946
|
+
forceFlush() {
|
|
8947
|
+
return Promise.resolve();
|
|
8948
|
+
}
|
|
8949
|
+
shutdown() {
|
|
8950
|
+
return Promise.resolve();
|
|
8951
|
+
}
|
|
8952
|
+
};
|
|
8931
8953
|
function formatOtlpError(err) {
|
|
8932
8954
|
if (!(err instanceof Error)) return String(err);
|
|
8933
8955
|
const parts = [];
|
|
@@ -9511,6 +9533,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
9511
9533
|
otlpSpanProcessor;
|
|
9512
9534
|
otlpTracerProvider;
|
|
9513
9535
|
hasOtlpExporter = false;
|
|
9536
|
+
/** End-user id (config.telemetry.userId) stamped as `user.id` on root spans. */
|
|
9537
|
+
telemetryUserId;
|
|
9514
9538
|
_browserSession;
|
|
9515
9539
|
_browserMod;
|
|
9516
9540
|
parsedAgent;
|
|
@@ -10327,6 +10351,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
10327
10351
|
await this.refreshMcpTools("initialize");
|
|
10328
10352
|
const telemetryEnabled = config?.telemetry?.enabled !== false;
|
|
10329
10353
|
const otlpConfig = telemetryEnabled ? normalizeOtlp(config?.telemetry?.otlp) : void 0;
|
|
10354
|
+
this.telemetryUserId = config?.telemetry?.userId;
|
|
10330
10355
|
if (otlpConfig) {
|
|
10331
10356
|
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.WARN);
|
|
10332
10357
|
const exporter = new OTLPTraceExporter({
|
|
@@ -10336,7 +10361,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
10336
10361
|
const processor = new BatchSpanProcessor(exporter);
|
|
10337
10362
|
this.otlpSpanProcessor = processor;
|
|
10338
10363
|
const provider2 = new NodeTracerProvider({
|
|
10339
|
-
|
|
10364
|
+
// Identity injector FIRST so every span (root, LLM steps, tool
|
|
10365
|
+
// executions) carries session.id/user.id before batching/export.
|
|
10366
|
+
spanProcessors: [new IdentityAttributeSpanProcessor(), processor]
|
|
10340
10367
|
});
|
|
10341
10368
|
provider2.register();
|
|
10342
10369
|
this.otlpTracerProvider = provider2;
|
|
@@ -10492,11 +10519,24 @@ var AgentHarness = class _AgentHarness {
|
|
|
10492
10519
|
kind: SpanKind.INTERNAL,
|
|
10493
10520
|
attributes: {
|
|
10494
10521
|
"gen_ai.operation.name": "invoke_agent",
|
|
10495
|
-
|
|
10522
|
+
// `session.id` / `user.id` are the attributes observability
|
|
10523
|
+
// backends (Latitude) key session grouping and user filtering on.
|
|
10524
|
+
// gen_ai.conversation.id is kept for the GenAI semantic convention.
|
|
10525
|
+
...input.conversationId ? {
|
|
10526
|
+
"gen_ai.conversation.id": input.conversationId,
|
|
10527
|
+
"session.id": input.conversationId
|
|
10528
|
+
} : {},
|
|
10529
|
+
...this.telemetryUserId ? { "user.id": this.telemetryUserId } : {},
|
|
10496
10530
|
...input.tenantId ? { "tenant.id": input.tenantId } : {}
|
|
10497
10531
|
}
|
|
10498
10532
|
});
|
|
10499
|
-
|
|
10533
|
+
let spanContext = trace.setSpan(otelContext.active(), rootSpan);
|
|
10534
|
+
if (input.conversationId) {
|
|
10535
|
+
spanContext = spanContext.setValue(TELEMETRY_SESSION_ID_KEY, input.conversationId);
|
|
10536
|
+
}
|
|
10537
|
+
if (this.telemetryUserId) {
|
|
10538
|
+
spanContext = spanContext.setValue(TELEMETRY_USER_ID_KEY, this.telemetryUserId);
|
|
10539
|
+
}
|
|
10500
10540
|
try {
|
|
10501
10541
|
const gen = this.run(input);
|
|
10502
10542
|
let next;
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -231,6 +231,11 @@ export interface PonchoConfig extends McpConfig {
|
|
|
231
231
|
url: string;
|
|
232
232
|
headers?: Record<string, string>;
|
|
233
233
|
};
|
|
234
|
+
/** End-user identifier (e.g. an email) stamped as `user.id` on every
|
|
235
|
+
* root trace span — observability backends (Latitude) group and
|
|
236
|
+
* filter traces by it. Per-harness, so a harness-per-user consumer
|
|
237
|
+
* sets it once at construction. */
|
|
238
|
+
userId?: string;
|
|
234
239
|
handler?: (event: unknown) => Promise<void> | void;
|
|
235
240
|
};
|
|
236
241
|
skills?: Record<string, Record<string, unknown>>;
|
package/src/harness.ts
CHANGED
|
@@ -67,9 +67,41 @@ import { createSkillTools, normalizeScriptPolicyPath } from "./skill-tools.js";
|
|
|
67
67
|
import { createSearchTools } from "./search-tools.js";
|
|
68
68
|
import { createSubagentTools } from "./subagent-tools.js";
|
|
69
69
|
import type { SubagentManager } from "./subagent-manager.js";
|
|
70
|
-
import { trace, context as otelContext, SpanStatusCode, SpanKind, diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
|
|
70
|
+
import { trace, context as otelContext, createContextKey, type Context as OtelContextType, SpanStatusCode, SpanKind, diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
|
|
71
|
+
import type { Span as OtelSdkSpan, SpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
71
72
|
import { NodeTracerProvider, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
72
73
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
74
|
+
|
|
75
|
+
// ── Telemetry identity propagation ──────────────────────────────────────────
|
|
76
|
+
// Observability backends (Latitude) resolve a span's session/user from the
|
|
77
|
+
// span's OWN attributes — the console's session & conversation views key on
|
|
78
|
+
// the LLM generation spans, not the root span. So stamping `session.id` /
|
|
79
|
+
// `user.id` only on the invoke_agent root groups the API-level trace but
|
|
80
|
+
// leaves the console treating every turn as its own session. The fix is the
|
|
81
|
+
// same one vendor SDKs use: carry the identity in the OTel Context and have
|
|
82
|
+
// a SpanProcessor stamp it onto EVERY span at start.
|
|
83
|
+
const TELEMETRY_SESSION_ID_KEY = createContextKey("poncho.telemetry.session_id");
|
|
84
|
+
const TELEMETRY_USER_ID_KEY = createContextKey("poncho.telemetry.user_id");
|
|
85
|
+
|
|
86
|
+
class IdentityAttributeSpanProcessor implements SpanProcessor {
|
|
87
|
+
onStart(span: OtelSdkSpan, parentContext: OtelContextType): void {
|
|
88
|
+
const sessionId = parentContext.getValue(TELEMETRY_SESSION_ID_KEY);
|
|
89
|
+
if (typeof sessionId === "string" && sessionId) {
|
|
90
|
+
span.setAttribute("session.id", sessionId);
|
|
91
|
+
}
|
|
92
|
+
const userId = parentContext.getValue(TELEMETRY_USER_ID_KEY);
|
|
93
|
+
if (typeof userId === "string" && userId) {
|
|
94
|
+
span.setAttribute("user.id", userId);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
onEnd(): void {}
|
|
98
|
+
forceFlush(): Promise<void> {
|
|
99
|
+
return Promise.resolve();
|
|
100
|
+
}
|
|
101
|
+
shutdown(): Promise<void> {
|
|
102
|
+
return Promise.resolve();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
73
105
|
import { normalizeOtlp } from "./telemetry.js";
|
|
74
106
|
|
|
75
107
|
/** Extract useful details from OTLPExporterError (has .code + .data) or plain Error. */
|
|
@@ -881,6 +913,8 @@ export class AgentHarness {
|
|
|
881
913
|
private otlpSpanProcessor?: BatchSpanProcessor;
|
|
882
914
|
private otlpTracerProvider?: NodeTracerProvider;
|
|
883
915
|
private hasOtlpExporter = false;
|
|
916
|
+
/** End-user id (config.telemetry.userId) stamped as `user.id` on root spans. */
|
|
917
|
+
private telemetryUserId?: string;
|
|
884
918
|
private _browserSession?: unknown;
|
|
885
919
|
private _browserMod?: {
|
|
886
920
|
createBrowserTools: (getSession: () => unknown, getConversationId?: () => string) => ToolDefinition[];
|
|
@@ -1871,6 +1905,7 @@ export class AgentHarness {
|
|
|
1871
1905
|
|
|
1872
1906
|
const telemetryEnabled = config?.telemetry?.enabled !== false;
|
|
1873
1907
|
const otlpConfig = telemetryEnabled ? normalizeOtlp(config?.telemetry?.otlp) : undefined;
|
|
1908
|
+
this.telemetryUserId = config?.telemetry?.userId;
|
|
1874
1909
|
if (otlpConfig) {
|
|
1875
1910
|
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.WARN);
|
|
1876
1911
|
const exporter = new OTLPTraceExporter({
|
|
@@ -1880,7 +1915,9 @@ export class AgentHarness {
|
|
|
1880
1915
|
const processor = new BatchSpanProcessor(exporter);
|
|
1881
1916
|
this.otlpSpanProcessor = processor;
|
|
1882
1917
|
const provider = new NodeTracerProvider({
|
|
1883
|
-
|
|
1918
|
+
// Identity injector FIRST so every span (root, LLM steps, tool
|
|
1919
|
+
// executions) carries session.id/user.id before batching/export.
|
|
1920
|
+
spanProcessors: [new IdentityAttributeSpanProcessor(), processor],
|
|
1884
1921
|
});
|
|
1885
1922
|
provider.register();
|
|
1886
1923
|
this.otlpTracerProvider = provider;
|
|
@@ -2057,12 +2094,29 @@ export class AgentHarness {
|
|
|
2057
2094
|
kind: SpanKind.INTERNAL,
|
|
2058
2095
|
attributes: {
|
|
2059
2096
|
"gen_ai.operation.name": "invoke_agent",
|
|
2060
|
-
|
|
2097
|
+
// `session.id` / `user.id` are the attributes observability
|
|
2098
|
+
// backends (Latitude) key session grouping and user filtering on.
|
|
2099
|
+
// gen_ai.conversation.id is kept for the GenAI semantic convention.
|
|
2100
|
+
...(input.conversationId
|
|
2101
|
+
? {
|
|
2102
|
+
"gen_ai.conversation.id": input.conversationId,
|
|
2103
|
+
"session.id": input.conversationId,
|
|
2104
|
+
}
|
|
2105
|
+
: {}),
|
|
2106
|
+
...(this.telemetryUserId ? { "user.id": this.telemetryUserId } : {}),
|
|
2061
2107
|
...(input.tenantId ? { "tenant.id": input.tenantId } : {}),
|
|
2062
2108
|
},
|
|
2063
2109
|
});
|
|
2064
2110
|
|
|
2065
|
-
|
|
2111
|
+
let spanContext = trace.setSpan(otelContext.active(), rootSpan);
|
|
2112
|
+
// Identity rides the context so IdentityAttributeSpanProcessor stamps
|
|
2113
|
+
// session.id/user.id on every descendant span (see processor docs).
|
|
2114
|
+
if (input.conversationId) {
|
|
2115
|
+
spanContext = spanContext.setValue(TELEMETRY_SESSION_ID_KEY, input.conversationId);
|
|
2116
|
+
}
|
|
2117
|
+
if (this.telemetryUserId) {
|
|
2118
|
+
spanContext = spanContext.setValue(TELEMETRY_USER_ID_KEY, this.telemetryUserId);
|
|
2119
|
+
}
|
|
2066
2120
|
|
|
2067
2121
|
try {
|
|
2068
2122
|
const gen = this.run(input);
|