@parall/agent-core 1.31.0 → 1.32.1

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 (72) hide show
  1. package/dist/bridge-workspace.d.ts +1 -1
  2. package/dist/bridge-workspace.d.ts.map +1 -1
  3. package/dist/bridge-workspace.js +13 -13
  4. package/dist/dispatch-adapter.d.ts +15 -8
  5. package/dist/dispatch-adapter.d.ts.map +1 -1
  6. package/dist/event-format.d.ts +1 -1
  7. package/dist/event-format.d.ts.map +1 -1
  8. package/dist/event-format.js +68 -25
  9. package/dist/gateway-base.d.ts +14 -13
  10. package/dist/gateway-base.d.ts.map +1 -1
  11. package/dist/gateway-base.js +650 -313
  12. package/dist/index.d.ts +15 -13
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +13 -12
  15. package/dist/internal/attachment-input.d.ts +3 -3
  16. package/dist/internal/attachment-input.d.ts.map +1 -1
  17. package/dist/internal/attachment-input.js +61 -58
  18. package/dist/logger.d.ts +1 -1
  19. package/dist/platform-config.d.ts +28 -2
  20. package/dist/platform-config.d.ts.map +1 -1
  21. package/dist/platform-config.js +42 -11
  22. package/dist/prompt-fragments.d.ts +2 -2
  23. package/dist/prompt-fragments.d.ts.map +1 -1
  24. package/dist/prompt-fragments.js +37 -14
  25. package/dist/provider-config.d.ts +9 -0
  26. package/dist/provider-config.d.ts.map +1 -1
  27. package/dist/provider-config.js +13 -2
  28. package/dist/routing.d.ts +5 -5
  29. package/dist/routing.js +6 -6
  30. package/dist/session-state.d.ts +16 -0
  31. package/dist/session-state.d.ts.map +1 -1
  32. package/dist/session-state.js +45 -0
  33. package/dist/skills/index.d.ts +5 -4
  34. package/dist/skills/index.d.ts.map +1 -1
  35. package/dist/skills/index.js +28 -21
  36. package/dist/skills/parall-clips.d.ts +2 -0
  37. package/dist/skills/parall-clips.d.ts.map +1 -0
  38. package/dist/skills/parall-clips.js +44 -0
  39. package/dist/skills/parall-platform.d.ts +1 -1
  40. package/dist/skills/parall-platform.d.ts.map +1 -1
  41. package/dist/skills/parall-platform.js +6 -2
  42. package/dist/skills/parall-tasks.d.ts +1 -1
  43. package/dist/skills/parall-tasks.d.ts.map +1 -1
  44. package/dist/skills/parall-tasks.js +1 -1
  45. package/dist/skills/parall-wiki.d.ts +1 -1
  46. package/dist/skills/parall-wiki.d.ts.map +1 -1
  47. package/dist/skills/parall-wiki.js +1 -1
  48. package/dist/telemetry.d.ts +27 -0
  49. package/dist/telemetry.d.ts.map +1 -0
  50. package/dist/telemetry.js +205 -0
  51. package/dist/types.d.ts +18 -2
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +11 -2
  54. package/src/bridge-workspace.ts +13 -13
  55. package/src/dispatch-adapter.ts +31 -8
  56. package/src/event-format.ts +80 -30
  57. package/src/gateway-base.ts +988 -445
  58. package/src/index.ts +23 -13
  59. package/src/internal/attachment-input.ts +127 -100
  60. package/src/logger.ts +1 -1
  61. package/src/platform-config.ts +61 -16
  62. package/src/prompt-fragments.ts +37 -14
  63. package/src/provider-config.ts +14 -2
  64. package/src/routing.ts +11 -11
  65. package/src/session-state.ts +62 -0
  66. package/src/skills/index.ts +34 -23
  67. package/src/skills/parall-clips.ts +44 -0
  68. package/src/skills/parall-platform.ts +6 -2
  69. package/src/skills/parall-tasks.ts +1 -1
  70. package/src/skills/parall-wiki.ts +1 -1
  71. package/src/telemetry.ts +252 -0
  72. package/src/types.ts +18 -2
@@ -0,0 +1,252 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import {
3
+ trace,
4
+ metrics,
5
+ type Span,
6
+ type Tracer,
7
+ type Counter,
8
+ type Histogram,
9
+ SpanStatusCode,
10
+ } from '@opentelemetry/api';
11
+ import { type Logger, SeverityNumber } from '@opentelemetry/api-logs';
12
+ import type { GatewayLogger } from './dispatch-adapter.js';
13
+ import type { DispatchMetrics } from './session-state.js';
14
+ import type { ParallEvent } from './types.js';
15
+
16
+ let initialized = false;
17
+ let shutdownFn: (() => Promise<void>) | null = null;
18
+
19
+ let tracer: Tracer | null = null;
20
+ let dispatchCounter: Counter | null = null;
21
+ let dispatchDuration: Histogram | null = null;
22
+ let missingReplyCounter: Counter | null = null;
23
+ let otelLogger: Logger | null = null;
24
+
25
+ function resolveTargetType(targetId: string): string {
26
+ if (targetId.startsWith('cht_')) return 'chat';
27
+ if (targetId.startsWith('tsk_')) return 'task';
28
+ if (targetId.startsWith('sch_')) return 'schedule';
29
+ return 'unknown';
30
+ }
31
+
32
+ export interface TelemetryHandle {
33
+ shutdown: () => Promise<void>;
34
+ }
35
+
36
+ /**
37
+ * Initialize agent telemetry. All agents export OTLP to the Parall
38
+ * telemetry-service (`PRLL_API_URL/otel`), authenticated with `PRLL_API_KEY`.
39
+ * The service canonicalizes identity from the token and proxies to SigNoz.
40
+ *
41
+ * Resource attributes include machine/agent/org identity from env.
42
+ * Returns a no-op handle when `PRLL_API_URL` is absent (local dev).
43
+ */
44
+ export async function initAgentTelemetry(
45
+ serviceName: string,
46
+ runtimeType: string,
47
+ ): Promise<TelemetryHandle> {
48
+ const noopHandle: TelemetryHandle = { shutdown: async () => {} };
49
+
50
+ const apiUrl = process.env.PRLL_API_URL;
51
+ const apiKey = process.env.PRLL_API_KEY;
52
+ if (!apiUrl || !apiKey) {
53
+ return noopHandle;
54
+ }
55
+
56
+ try {
57
+ const otelEndpoint = apiUrl.replace(/\/$/, '') + '/otel';
58
+
59
+ const { OTLPTraceExporter } = await import('@opentelemetry/exporter-trace-otlp-proto');
60
+ const { OTLPMetricExporter } = await import('@opentelemetry/exporter-metrics-otlp-proto');
61
+ const { OTLPLogExporter } = await import('@opentelemetry/exporter-logs-otlp-proto');
62
+ const { NodeTracerProvider, BatchSpanProcessor } = await import(
63
+ '@opentelemetry/sdk-trace-node'
64
+ );
65
+ const { MeterProvider, PeriodicExportingMetricReader } = await import(
66
+ '@opentelemetry/sdk-metrics'
67
+ );
68
+ const { LoggerProvider, BatchLogRecordProcessor } = await import('@opentelemetry/sdk-logs');
69
+ const { Resource } = await import('@opentelemetry/resources');
70
+
71
+ const resource = new Resource({
72
+ 'service.name': serviceName,
73
+ 'service.version': process.env.npm_package_version || 'unknown',
74
+ 'deployment.environment.name':
75
+ process.env.PRLL_SERVER_ENV || process.env.NODE_ENV || 'development',
76
+ 'parall.runtime_type': runtimeType,
77
+ 'parall.agent_id': process.env.PRLL_AGENT_ID || '',
78
+ 'parall.machine_id': process.env.PRLL_MACHINE_ID || '',
79
+ 'parall.org_id': process.env.PRLL_ORG_ID || '',
80
+ 'parall.daemon_mode': process.env.PRLL_DAEMON_MODE === '1',
81
+ });
82
+
83
+ const authHeaders = { Authorization: `Bearer ${apiKey}` };
84
+
85
+ const traceExporter = new OTLPTraceExporter({
86
+ url: `${otelEndpoint}/v1/traces`,
87
+ headers: authHeaders,
88
+ });
89
+ const tracerProvider = new NodeTracerProvider({ resource });
90
+ tracerProvider.addSpanProcessor(new BatchSpanProcessor(traceExporter));
91
+ tracerProvider.register();
92
+
93
+ const metricExporter = new OTLPMetricExporter({
94
+ url: `${otelEndpoint}/v1/metrics`,
95
+ headers: authHeaders,
96
+ });
97
+ const metricReader = new PeriodicExportingMetricReader({
98
+ exporter: metricExporter,
99
+ exportIntervalMillis: 15_000,
100
+ });
101
+ const meterProvider = new MeterProvider({ resource, readers: [metricReader] });
102
+ metrics.setGlobalMeterProvider(meterProvider);
103
+
104
+ const logExporter = new OTLPLogExporter({
105
+ url: `${otelEndpoint}/v1/logs`,
106
+ headers: authHeaders,
107
+ });
108
+ const loggerProvider = new LoggerProvider({ resource });
109
+ loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
110
+
111
+ const meter = metrics.getMeter('parall.agent');
112
+ tracer = trace.getTracer('parall.agent');
113
+ otelLogger = loggerProvider.getLogger('parall.agent');
114
+ dispatchCounter = meter.createCounter('parall.dispatch.count', {
115
+ description: 'Number of dispatch cycles completed',
116
+ });
117
+ dispatchDuration = meter.createHistogram('parall.dispatch.duration', {
118
+ description: 'Dispatch cycle duration in milliseconds',
119
+ unit: 'ms',
120
+ });
121
+ missingReplyCounter = meter.createCounter('parall.dispatch.missing_reply', {
122
+ description: 'Dispatches where agent produced text but sent no reply message',
123
+ });
124
+
125
+ initialized = true;
126
+ shutdownFn = async () => {
127
+ await tracerProvider.forceFlush();
128
+ await meterProvider.forceFlush();
129
+ await loggerProvider.forceFlush();
130
+ await tracerProvider.shutdown();
131
+ await meterProvider.shutdown();
132
+ await loggerProvider.shutdown();
133
+ };
134
+
135
+ return {
136
+ shutdown: async () => {
137
+ if (shutdownFn) await shutdownFn();
138
+ },
139
+ };
140
+ } catch {
141
+ return noopHandle;
142
+ }
143
+ }
144
+
145
+ export function startDispatchSpan(
146
+ event: ParallEvent,
147
+ runtimeType: string,
148
+ sessionKey: string,
149
+ ): Span | null {
150
+ if (!initialized || !tracer) return null;
151
+ return tracer.startSpan('parall.dispatch', {
152
+ attributes: {
153
+ 'dispatch.target_type': resolveTargetType(event.targetId),
154
+ 'dispatch.event_type': event.type,
155
+ 'dispatch.runtime_type': runtimeType,
156
+ 'dispatch.session_key': sessionKey,
157
+ 'dispatch.message_id': event.messageId,
158
+ 'dispatch.target_id': event.targetId,
159
+ },
160
+ });
161
+ }
162
+
163
+ export function endDispatchSpan(
164
+ span: Span | null,
165
+ metricsSnapshot: DispatchMetrics | undefined,
166
+ error?: unknown,
167
+ ): void {
168
+ if (!span) return;
169
+ if (metricsSnapshot) {
170
+ span.setAttributes({
171
+ 'dispatch.deliver_text_chunks': metricsSnapshot.deliver_text_chunks,
172
+ 'dispatch.deliver_text_chars': metricsSnapshot.deliver_text_chars,
173
+ 'dispatch.message_send_attempts': metricsSnapshot.message_send_attempts,
174
+ 'dispatch.message_send_successes': metricsSnapshot.message_send_successes,
175
+ 'dispatch.no_reply_called': metricsSnapshot.no_reply_called,
176
+ 'dispatch.tool_call_count': metricsSnapshot.tool_call_count,
177
+ 'dispatch.duration_ms': Date.now() - metricsSnapshot.started_at,
178
+ });
179
+ }
180
+ if (error) {
181
+ span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
182
+ span.recordException(error instanceof Error ? error : new Error(String(error)));
183
+ }
184
+ span.end();
185
+ }
186
+
187
+ export function recordDispatchMetric(
188
+ event: ParallEvent,
189
+ runtimeType: string,
190
+ durationMs: number,
191
+ ): void {
192
+ if (!initialized) return;
193
+ const attrs = {
194
+ target_type: resolveTargetType(event.targetId),
195
+ event_type: event.type,
196
+ runtime_type: runtimeType,
197
+ };
198
+ dispatchCounter?.add(1, attrs);
199
+ dispatchDuration?.record(durationMs, attrs);
200
+ }
201
+
202
+ export function recordMissingReply(runtimeType: string): void {
203
+ if (!initialized) return;
204
+ missingReplyCounter?.add(1, { runtime_type: runtimeType });
205
+ }
206
+
207
+ const sessionKeyStorage = new AsyncLocalStorage<string>();
208
+
209
+ export function runWithSessionKey<T>(sessionKey: string, fn: () => T): T {
210
+ return sessionKeyStorage.run(sessionKey, fn);
211
+ }
212
+
213
+ /**
214
+ * Create a GatewayLogger that forwards all levels to OTLP logs.
215
+ * Two layers: "agent" (runtime) and "daemon".
216
+ */
217
+ export function createOtelLogger(layer: 'agent' | 'daemon', prefix: string): GatewayLogger {
218
+ const ts = () => new Date().toISOString();
219
+ const emit = (severity: 'INFO' | 'WARN' | 'ERROR', msg: string) => {
220
+ if (!otelLogger) return;
221
+ const severityNumber =
222
+ severity === 'ERROR'
223
+ ? SeverityNumber.ERROR
224
+ : severity === 'WARN'
225
+ ? SeverityNumber.WARN
226
+ : SeverityNumber.INFO;
227
+ const attrs: Record<string, string> = { 'log.layer': layer, 'log.prefix': prefix };
228
+ const sk = sessionKeyStorage.getStore();
229
+ if (sk) attrs['session.key'] = sk;
230
+ otelLogger.emit({
231
+ severityNumber,
232
+ severityText: severity,
233
+ body: msg,
234
+ attributes: attrs,
235
+ });
236
+ };
237
+ return {
238
+ info: (msg: string) => {
239
+ console.log(`${ts()} [${prefix}] ${msg}`);
240
+ emit('INFO', msg);
241
+ },
242
+ warn: (msg: string) => {
243
+ console.warn(`${ts()} [${prefix}] ${msg}`);
244
+ emit('WARN', msg);
245
+ },
246
+ error: (msg: string) => {
247
+ console.error(`${ts()} [${prefix}] ${msg}`);
248
+ emit('ERROR', msg);
249
+ },
250
+ child: (sub: string) => createOtelLogger(layer, `${prefix}:${sub}`),
251
+ };
252
+ }
package/src/types.ts CHANGED
@@ -24,10 +24,17 @@ export type DispatchState = {
24
24
 
25
25
  /** Normalized inbound event from Parall. */
26
26
  export type ParallEvent = {
27
- type: "message" | "task" | "task_comment" | "schedule" | "approval";
27
+ type: 'message' | 'task' | 'task_comment' | 'wiki_comment' | 'schedule' | 'approval';
28
28
  targetId: string;
29
29
  targetName?: string;
30
30
  targetType?: string;
31
+ /**
32
+ * Full `prll://` target URI to reply on, used for wiki_comment events where
33
+ * the reply goes back to the same wiki page / changeset via
34
+ * `parall comments add --target <uri>`. Carried separately from `targetId`
35
+ * (a bare entity id) because the reply needs the full URI incl. path/anchor.
36
+ */
37
+ replyTargetUri?: string;
31
38
  deliveryReason?: string;
32
39
  senderId: string;
33
40
  senderName: string;
@@ -48,6 +55,15 @@ export type ParallEvent = {
48
55
  /** Original event timestamp (e.g., message.created_at). When present,
49
56
  * input steps use this instead of server insertion time for ordering. */
50
57
  sentAt?: string;
51
- ackSourceType?: "message" | "task_activity" | "comment" | "schedule_run";
58
+ ackSourceType?: 'message' | 'task_activity' | 'comment' | 'schedule_run';
52
59
  ackSourceId?: string;
60
+ /** Unread message count in the target chat since agent's last interaction. */
61
+ unreadCount?: number;
62
+ /** Channel cursor: the last message ID the agent read. */
63
+ unreadSince?: string;
64
+ /** For thread replies: total replies and unread replies in the thread. */
65
+ threadReplyCount?: number;
66
+ threadUnreadCount?: number;
67
+ /** Thread cursor: the last reply ID the agent read in this thread. */
68
+ threadUnreadSince?: string;
53
69
  };