@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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/harness@0.59.8 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
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
  CLI Target: es2022
10
10
  ESM Build start
11
11
  ESM dist/isolate-F2PPSUL6.js 53.82 KB
12
- ESM dist/index.js 558.23 KB
13
- ESM ⚡️ Build success in 235ms
12
+ ESM dist/index.js 559.89 KB
13
+ ESM ⚡️ Build success in 238ms
14
14
  DTS Build start
15
- DTS ⚡️ Build success in 8463ms
16
- DTS dist/index.d.ts 101.66 KB
15
+ DTS ⚡️ Build success in 7368ms
16
+ DTS dist/index.d.ts 102.06 KB
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
- spanProcessors: [processor]
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
- ...input.conversationId ? { "gen_ai.conversation.id": input.conversationId } : {},
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
- const spanContext = trace.setSpan(otelContext.active(), rootSpan);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/harness",
3
- "version": "0.59.8",
3
+ "version": "0.59.10",
4
4
  "description": "Agent execution runtime - conversation loop, tool dispatch, streaming",
5
5
  "repository": {
6
6
  "type": "git",
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
- spanProcessors: [processor],
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
- ...(input.conversationId ? { "gen_ai.conversation.id": input.conversationId } : {}),
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
- const spanContext = trace.setSpan(otelContext.active(), rootSpan);
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);