@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.
- package/dist/bridge-workspace.d.ts +1 -1
- package/dist/bridge-workspace.d.ts.map +1 -1
- package/dist/bridge-workspace.js +13 -13
- package/dist/dispatch-adapter.d.ts +15 -8
- package/dist/dispatch-adapter.d.ts.map +1 -1
- package/dist/event-format.d.ts +1 -1
- package/dist/event-format.d.ts.map +1 -1
- package/dist/event-format.js +68 -25
- package/dist/gateway-base.d.ts +14 -13
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +650 -313
- package/dist/index.d.ts +15 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -12
- package/dist/internal/attachment-input.d.ts +3 -3
- package/dist/internal/attachment-input.d.ts.map +1 -1
- package/dist/internal/attachment-input.js +61 -58
- package/dist/logger.d.ts +1 -1
- package/dist/platform-config.d.ts +28 -2
- package/dist/platform-config.d.ts.map +1 -1
- package/dist/platform-config.js +42 -11
- package/dist/prompt-fragments.d.ts +2 -2
- package/dist/prompt-fragments.d.ts.map +1 -1
- package/dist/prompt-fragments.js +37 -14
- package/dist/provider-config.d.ts +9 -0
- package/dist/provider-config.d.ts.map +1 -1
- package/dist/provider-config.js +13 -2
- package/dist/routing.d.ts +5 -5
- package/dist/routing.js +6 -6
- package/dist/session-state.d.ts +16 -0
- package/dist/session-state.d.ts.map +1 -1
- package/dist/session-state.js +45 -0
- package/dist/skills/index.d.ts +5 -4
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +28 -21
- package/dist/skills/parall-clips.d.ts +2 -0
- package/dist/skills/parall-clips.d.ts.map +1 -0
- package/dist/skills/parall-clips.js +44 -0
- package/dist/skills/parall-platform.d.ts +1 -1
- package/dist/skills/parall-platform.d.ts.map +1 -1
- package/dist/skills/parall-platform.js +6 -2
- package/dist/skills/parall-tasks.d.ts +1 -1
- package/dist/skills/parall-tasks.d.ts.map +1 -1
- package/dist/skills/parall-tasks.js +1 -1
- package/dist/skills/parall-wiki.d.ts +1 -1
- package/dist/skills/parall-wiki.d.ts.map +1 -1
- package/dist/skills/parall-wiki.js +1 -1
- package/dist/telemetry.d.ts +27 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +205 -0
- package/dist/types.d.ts +18 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -2
- package/src/bridge-workspace.ts +13 -13
- package/src/dispatch-adapter.ts +31 -8
- package/src/event-format.ts +80 -30
- package/src/gateway-base.ts +988 -445
- package/src/index.ts +23 -13
- package/src/internal/attachment-input.ts +127 -100
- package/src/logger.ts +1 -1
- package/src/platform-config.ts +61 -16
- package/src/prompt-fragments.ts +37 -14
- package/src/provider-config.ts +14 -2
- package/src/routing.ts +11 -11
- package/src/session-state.ts +62 -0
- package/src/skills/index.ts +34 -23
- package/src/skills/parall-clips.ts +44 -0
- package/src/skills/parall-platform.ts +6 -2
- package/src/skills/parall-tasks.ts +1 -1
- package/src/skills/parall-wiki.ts +1 -1
- package/src/telemetry.ts +252 -0
- package/src/types.ts +18 -2
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { trace, metrics, SpanStatusCode, } from '@opentelemetry/api';
|
|
3
|
+
import { SeverityNumber } from '@opentelemetry/api-logs';
|
|
4
|
+
let initialized = false;
|
|
5
|
+
let shutdownFn = null;
|
|
6
|
+
let tracer = null;
|
|
7
|
+
let dispatchCounter = null;
|
|
8
|
+
let dispatchDuration = null;
|
|
9
|
+
let missingReplyCounter = null;
|
|
10
|
+
let otelLogger = null;
|
|
11
|
+
function resolveTargetType(targetId) {
|
|
12
|
+
if (targetId.startsWith('cht_'))
|
|
13
|
+
return 'chat';
|
|
14
|
+
if (targetId.startsWith('tsk_'))
|
|
15
|
+
return 'task';
|
|
16
|
+
if (targetId.startsWith('sch_'))
|
|
17
|
+
return 'schedule';
|
|
18
|
+
return 'unknown';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Initialize agent telemetry. All agents export OTLP to the Parall
|
|
22
|
+
* telemetry-service (`PRLL_API_URL/otel`), authenticated with `PRLL_API_KEY`.
|
|
23
|
+
* The service canonicalizes identity from the token and proxies to SigNoz.
|
|
24
|
+
*
|
|
25
|
+
* Resource attributes include machine/agent/org identity from env.
|
|
26
|
+
* Returns a no-op handle when `PRLL_API_URL` is absent (local dev).
|
|
27
|
+
*/
|
|
28
|
+
export async function initAgentTelemetry(serviceName, runtimeType) {
|
|
29
|
+
const noopHandle = { shutdown: async () => { } };
|
|
30
|
+
const apiUrl = process.env.PRLL_API_URL;
|
|
31
|
+
const apiKey = process.env.PRLL_API_KEY;
|
|
32
|
+
if (!apiUrl || !apiKey) {
|
|
33
|
+
return noopHandle;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const otelEndpoint = apiUrl.replace(/\/$/, '') + '/otel';
|
|
37
|
+
const { OTLPTraceExporter } = await import('@opentelemetry/exporter-trace-otlp-proto');
|
|
38
|
+
const { OTLPMetricExporter } = await import('@opentelemetry/exporter-metrics-otlp-proto');
|
|
39
|
+
const { OTLPLogExporter } = await import('@opentelemetry/exporter-logs-otlp-proto');
|
|
40
|
+
const { NodeTracerProvider, BatchSpanProcessor } = await import('@opentelemetry/sdk-trace-node');
|
|
41
|
+
const { MeterProvider, PeriodicExportingMetricReader } = await import('@opentelemetry/sdk-metrics');
|
|
42
|
+
const { LoggerProvider, BatchLogRecordProcessor } = await import('@opentelemetry/sdk-logs');
|
|
43
|
+
const { Resource } = await import('@opentelemetry/resources');
|
|
44
|
+
const resource = new Resource({
|
|
45
|
+
'service.name': serviceName,
|
|
46
|
+
'service.version': process.env.npm_package_version || 'unknown',
|
|
47
|
+
'deployment.environment.name': process.env.PRLL_SERVER_ENV || process.env.NODE_ENV || 'development',
|
|
48
|
+
'parall.runtime_type': runtimeType,
|
|
49
|
+
'parall.agent_id': process.env.PRLL_AGENT_ID || '',
|
|
50
|
+
'parall.machine_id': process.env.PRLL_MACHINE_ID || '',
|
|
51
|
+
'parall.org_id': process.env.PRLL_ORG_ID || '',
|
|
52
|
+
'parall.daemon_mode': process.env.PRLL_DAEMON_MODE === '1',
|
|
53
|
+
});
|
|
54
|
+
const authHeaders = { Authorization: `Bearer ${apiKey}` };
|
|
55
|
+
const traceExporter = new OTLPTraceExporter({
|
|
56
|
+
url: `${otelEndpoint}/v1/traces`,
|
|
57
|
+
headers: authHeaders,
|
|
58
|
+
});
|
|
59
|
+
const tracerProvider = new NodeTracerProvider({ resource });
|
|
60
|
+
tracerProvider.addSpanProcessor(new BatchSpanProcessor(traceExporter));
|
|
61
|
+
tracerProvider.register();
|
|
62
|
+
const metricExporter = new OTLPMetricExporter({
|
|
63
|
+
url: `${otelEndpoint}/v1/metrics`,
|
|
64
|
+
headers: authHeaders,
|
|
65
|
+
});
|
|
66
|
+
const metricReader = new PeriodicExportingMetricReader({
|
|
67
|
+
exporter: metricExporter,
|
|
68
|
+
exportIntervalMillis: 15_000,
|
|
69
|
+
});
|
|
70
|
+
const meterProvider = new MeterProvider({ resource, readers: [metricReader] });
|
|
71
|
+
metrics.setGlobalMeterProvider(meterProvider);
|
|
72
|
+
const logExporter = new OTLPLogExporter({
|
|
73
|
+
url: `${otelEndpoint}/v1/logs`,
|
|
74
|
+
headers: authHeaders,
|
|
75
|
+
});
|
|
76
|
+
const loggerProvider = new LoggerProvider({ resource });
|
|
77
|
+
loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
|
|
78
|
+
const meter = metrics.getMeter('parall.agent');
|
|
79
|
+
tracer = trace.getTracer('parall.agent');
|
|
80
|
+
otelLogger = loggerProvider.getLogger('parall.agent');
|
|
81
|
+
dispatchCounter = meter.createCounter('parall.dispatch.count', {
|
|
82
|
+
description: 'Number of dispatch cycles completed',
|
|
83
|
+
});
|
|
84
|
+
dispatchDuration = meter.createHistogram('parall.dispatch.duration', {
|
|
85
|
+
description: 'Dispatch cycle duration in milliseconds',
|
|
86
|
+
unit: 'ms',
|
|
87
|
+
});
|
|
88
|
+
missingReplyCounter = meter.createCounter('parall.dispatch.missing_reply', {
|
|
89
|
+
description: 'Dispatches where agent produced text but sent no reply message',
|
|
90
|
+
});
|
|
91
|
+
initialized = true;
|
|
92
|
+
shutdownFn = async () => {
|
|
93
|
+
await tracerProvider.forceFlush();
|
|
94
|
+
await meterProvider.forceFlush();
|
|
95
|
+
await loggerProvider.forceFlush();
|
|
96
|
+
await tracerProvider.shutdown();
|
|
97
|
+
await meterProvider.shutdown();
|
|
98
|
+
await loggerProvider.shutdown();
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
shutdown: async () => {
|
|
102
|
+
if (shutdownFn)
|
|
103
|
+
await shutdownFn();
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return noopHandle;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function startDispatchSpan(event, runtimeType, sessionKey) {
|
|
112
|
+
if (!initialized || !tracer)
|
|
113
|
+
return null;
|
|
114
|
+
return tracer.startSpan('parall.dispatch', {
|
|
115
|
+
attributes: {
|
|
116
|
+
'dispatch.target_type': resolveTargetType(event.targetId),
|
|
117
|
+
'dispatch.event_type': event.type,
|
|
118
|
+
'dispatch.runtime_type': runtimeType,
|
|
119
|
+
'dispatch.session_key': sessionKey,
|
|
120
|
+
'dispatch.message_id': event.messageId,
|
|
121
|
+
'dispatch.target_id': event.targetId,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
export function endDispatchSpan(span, metricsSnapshot, error) {
|
|
126
|
+
if (!span)
|
|
127
|
+
return;
|
|
128
|
+
if (metricsSnapshot) {
|
|
129
|
+
span.setAttributes({
|
|
130
|
+
'dispatch.deliver_text_chunks': metricsSnapshot.deliver_text_chunks,
|
|
131
|
+
'dispatch.deliver_text_chars': metricsSnapshot.deliver_text_chars,
|
|
132
|
+
'dispatch.message_send_attempts': metricsSnapshot.message_send_attempts,
|
|
133
|
+
'dispatch.message_send_successes': metricsSnapshot.message_send_successes,
|
|
134
|
+
'dispatch.no_reply_called': metricsSnapshot.no_reply_called,
|
|
135
|
+
'dispatch.tool_call_count': metricsSnapshot.tool_call_count,
|
|
136
|
+
'dispatch.duration_ms': Date.now() - metricsSnapshot.started_at,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (error) {
|
|
140
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
|
141
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
142
|
+
}
|
|
143
|
+
span.end();
|
|
144
|
+
}
|
|
145
|
+
export function recordDispatchMetric(event, runtimeType, durationMs) {
|
|
146
|
+
if (!initialized)
|
|
147
|
+
return;
|
|
148
|
+
const attrs = {
|
|
149
|
+
target_type: resolveTargetType(event.targetId),
|
|
150
|
+
event_type: event.type,
|
|
151
|
+
runtime_type: runtimeType,
|
|
152
|
+
};
|
|
153
|
+
dispatchCounter?.add(1, attrs);
|
|
154
|
+
dispatchDuration?.record(durationMs, attrs);
|
|
155
|
+
}
|
|
156
|
+
export function recordMissingReply(runtimeType) {
|
|
157
|
+
if (!initialized)
|
|
158
|
+
return;
|
|
159
|
+
missingReplyCounter?.add(1, { runtime_type: runtimeType });
|
|
160
|
+
}
|
|
161
|
+
const sessionKeyStorage = new AsyncLocalStorage();
|
|
162
|
+
export function runWithSessionKey(sessionKey, fn) {
|
|
163
|
+
return sessionKeyStorage.run(sessionKey, fn);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Create a GatewayLogger that forwards all levels to OTLP logs.
|
|
167
|
+
* Two layers: "agent" (runtime) and "daemon".
|
|
168
|
+
*/
|
|
169
|
+
export function createOtelLogger(layer, prefix) {
|
|
170
|
+
const ts = () => new Date().toISOString();
|
|
171
|
+
const emit = (severity, msg) => {
|
|
172
|
+
if (!otelLogger)
|
|
173
|
+
return;
|
|
174
|
+
const severityNumber = severity === 'ERROR'
|
|
175
|
+
? SeverityNumber.ERROR
|
|
176
|
+
: severity === 'WARN'
|
|
177
|
+
? SeverityNumber.WARN
|
|
178
|
+
: SeverityNumber.INFO;
|
|
179
|
+
const attrs = { 'log.layer': layer, 'log.prefix': prefix };
|
|
180
|
+
const sk = sessionKeyStorage.getStore();
|
|
181
|
+
if (sk)
|
|
182
|
+
attrs['session.key'] = sk;
|
|
183
|
+
otelLogger.emit({
|
|
184
|
+
severityNumber,
|
|
185
|
+
severityText: severity,
|
|
186
|
+
body: msg,
|
|
187
|
+
attributes: attrs,
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
info: (msg) => {
|
|
192
|
+
console.log(`${ts()} [${prefix}] ${msg}`);
|
|
193
|
+
emit('INFO', msg);
|
|
194
|
+
},
|
|
195
|
+
warn: (msg) => {
|
|
196
|
+
console.warn(`${ts()} [${prefix}] ${msg}`);
|
|
197
|
+
emit('WARN', msg);
|
|
198
|
+
},
|
|
199
|
+
error: (msg) => {
|
|
200
|
+
console.error(`${ts()} [${prefix}] ${msg}`);
|
|
201
|
+
emit('ERROR', msg);
|
|
202
|
+
},
|
|
203
|
+
child: (sub) => createOtelLogger(layer, `${prefix}:${sub}`),
|
|
204
|
+
};
|
|
205
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -26,10 +26,17 @@ export type DispatchState = {
|
|
|
26
26
|
};
|
|
27
27
|
/** Normalized inbound event from Parall. */
|
|
28
28
|
export type ParallEvent = {
|
|
29
|
-
type:
|
|
29
|
+
type: 'message' | 'task' | 'task_comment' | 'wiki_comment' | 'schedule' | 'approval';
|
|
30
30
|
targetId: string;
|
|
31
31
|
targetName?: string;
|
|
32
32
|
targetType?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Full `prll://` target URI to reply on, used for wiki_comment events where
|
|
35
|
+
* the reply goes back to the same wiki page / changeset via
|
|
36
|
+
* `parall comments add --target <uri>`. Carried separately from `targetId`
|
|
37
|
+
* (a bare entity id) because the reply needs the full URI incl. path/anchor.
|
|
38
|
+
*/
|
|
39
|
+
replyTargetUri?: string;
|
|
33
40
|
deliveryReason?: string;
|
|
34
41
|
senderId: string;
|
|
35
42
|
senderName: string;
|
|
@@ -50,7 +57,16 @@ export type ParallEvent = {
|
|
|
50
57
|
/** Original event timestamp (e.g., message.created_at). When present,
|
|
51
58
|
* input steps use this instead of server insertion time for ordering. */
|
|
52
59
|
sentAt?: string;
|
|
53
|
-
ackSourceType?:
|
|
60
|
+
ackSourceType?: 'message' | 'task_activity' | 'comment' | 'schedule_run';
|
|
54
61
|
ackSourceId?: string;
|
|
62
|
+
/** Unread message count in the target chat since agent's last interaction. */
|
|
63
|
+
unreadCount?: number;
|
|
64
|
+
/** Channel cursor: the last message ID the agent read. */
|
|
65
|
+
unreadSince?: string;
|
|
66
|
+
/** For thread replies: total replies and unread replies in the thread. */
|
|
67
|
+
threadReplyCount?: number;
|
|
68
|
+
threadUnreadCount?: number;
|
|
69
|
+
/** Thread cursor: the last reply ID the agent read in this thread. */
|
|
70
|
+
threadUnreadSince?: string;
|
|
55
71
|
};
|
|
56
72
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,mFAAmF;IACnF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kBAAkB,EAAE,UAAU,EAAE,CAAC;IACjC,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,oFAAoF;IACpF,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,GAAG,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,mFAAmF;IACnF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kBAAkB,EAAE,UAAU,EAAE,CAAC;IACjC,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,oFAAoF;IACpF,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,cAAc,GAAG,UAAU,GAAG,UAAU,CAAC;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;8EAC0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,SAAS,GAAG,cAAc,CAAC;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/agent-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.1",
|
|
4
4
|
"description": "Shared agent runtime orchestration helpers for Parall",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,16 @@
|
|
|
26
26
|
"src"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@
|
|
29
|
+
"@opentelemetry/api": "^1.9.0",
|
|
30
|
+
"@opentelemetry/api-logs": "^0.57.0",
|
|
31
|
+
"@opentelemetry/exporter-logs-otlp-proto": "^0.57.0",
|
|
32
|
+
"@opentelemetry/exporter-metrics-otlp-proto": "^0.57.0",
|
|
33
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.57.0",
|
|
34
|
+
"@opentelemetry/resources": "^1.30.0",
|
|
35
|
+
"@opentelemetry/sdk-logs": "^0.57.0",
|
|
36
|
+
"@opentelemetry/sdk-metrics": "^1.30.0",
|
|
37
|
+
"@opentelemetry/sdk-trace-node": "^1.30.0",
|
|
38
|
+
"@parall/sdk": "1.32.1"
|
|
30
39
|
},
|
|
31
40
|
"devDependencies": {
|
|
32
41
|
"@types/node": "^22.0.0",
|
package/src/bridge-workspace.ts
CHANGED
|
@@ -50,7 +50,7 @@ See \`docs/engineering-design/agent-dm-loop-prevention.md\` § Layer 0 for why p
|
|
|
50
50
|
|
|
51
51
|
When you try an action (e.g., archive a chat) and receive a PERMISSION_DENIED error, you can request someone with permission to do it:
|
|
52
52
|
|
|
53
|
-
1. The error includes a \`PERMISSION_DENIED\` code
|
|
53
|
+
1. The error includes a \`PERMISSION_DENIED\` code plus the denied \`action\` and \`resource_uri\`. If the action is approvable (decided by the server — no fixed allowlist), a \`Request approval:\` line with an approval command is printed — fill in its \`--chat\`, \`--title\`, \`--reason\` placeholders and run it. If it is not approvable, the output says so; ask a human with permission instead.
|
|
54
54
|
2. Request approval: \`parall approvals request --action chat.archive --resource prll://cht_123 --chat prll://cht_456 --title "Archive #old-project" --reason "Channel inactive"\`
|
|
55
55
|
3. A card will appear in the specified chat for someone with permission to approve
|
|
56
56
|
4. Check the result: \`parall approvals get prll://<id>\` or wait: \`parall approvals wait prll://<id> --timeout 300\`
|
|
@@ -84,9 +84,9 @@ Messages may arrive with a \`[Thread: prll://msg_xxx]\` line in the event block,
|
|
|
84
84
|
|
|
85
85
|
/** Extracts the `command` string from a shell/bash tool call's input payload. */
|
|
86
86
|
export function extractShellCommand(input: unknown): string | undefined {
|
|
87
|
-
if (!input || typeof input !==
|
|
87
|
+
if (!input || typeof input !== 'object') return undefined;
|
|
88
88
|
const command = (input as { command?: unknown }).command;
|
|
89
|
-
return typeof command ===
|
|
89
|
+
return typeof command === 'string' && command.trim() ? command.trim() : undefined;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/**
|
|
@@ -101,24 +101,24 @@ export function extractShellCommand(input: unknown): string | undefined {
|
|
|
101
101
|
* `no-reply` subcommand).
|
|
102
102
|
*/
|
|
103
103
|
export function parseParallCliInvocation(command: string): string[] | null {
|
|
104
|
-
const tokens = command.replace(/\s+/g,
|
|
104
|
+
const tokens = command.replace(/\s+/g, ' ').trim().split(' ');
|
|
105
105
|
let i = 0;
|
|
106
|
-
if (tokens[i] ===
|
|
106
|
+
if (tokens[i] === 'parall') {
|
|
107
107
|
i++;
|
|
108
|
-
} else if (tokens[i] ===
|
|
108
|
+
} else if (tokens[i] === 'npx') {
|
|
109
109
|
i++;
|
|
110
|
-
while (i < tokens.length && tokens[i].startsWith(
|
|
110
|
+
while (i < tokens.length && tokens[i].startsWith('-')) i++;
|
|
111
111
|
if (i >= tokens.length || !/^@parall\/cli(?:@.+)?$/.test(tokens[i])) return null;
|
|
112
112
|
i++;
|
|
113
|
-
} else if (tokens[i] ===
|
|
113
|
+
} else if (tokens[i] === 'pnpm') {
|
|
114
114
|
i++;
|
|
115
|
-
if (i < tokens.length && (tokens[i] ===
|
|
116
|
-
if (i >= tokens.length || tokens[i] !==
|
|
115
|
+
if (i < tokens.length && (tokens[i] === 'exec' || tokens[i] === 'dlx')) i++;
|
|
116
|
+
if (i >= tokens.length || tokens[i] !== 'parall') return null;
|
|
117
117
|
i++;
|
|
118
118
|
} else {
|
|
119
119
|
return null;
|
|
120
120
|
}
|
|
121
|
-
return tokens.slice(i).filter((t) => !t.startsWith(
|
|
121
|
+
return tokens.slice(i).filter((t) => !t.startsWith('-'));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
@@ -132,7 +132,7 @@ export function isParallSendCommand(command: string | undefined): boolean {
|
|
|
132
132
|
if (!command) return false;
|
|
133
133
|
const sub = parseParallCliInvocation(command);
|
|
134
134
|
if (!sub || sub.length === 0) return false;
|
|
135
|
-
return sub[0] ===
|
|
135
|
+
return sub[0] === 'dm' || (sub[0] === 'messages' && sub[1] === 'send');
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/**
|
|
@@ -144,5 +144,5 @@ export function isParallSendCommand(command: string | undefined): boolean {
|
|
|
144
144
|
export function isParallNoReplyCommand(command: string | undefined): boolean {
|
|
145
145
|
if (!command) return false;
|
|
146
146
|
const sub = parseParallCliInvocation(command);
|
|
147
|
-
return sub?.[0] ===
|
|
147
|
+
return sub?.[0] === 'no-reply';
|
|
148
148
|
}
|
package/src/dispatch-adapter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ParallClient } from
|
|
2
|
-
import type { ParallEvent } from
|
|
1
|
+
import type { ParallClient } from '@parall/sdk';
|
|
2
|
+
import type { ParallEvent } from './types.js';
|
|
3
3
|
|
|
4
4
|
export type GatewayLogger = {
|
|
5
5
|
info: (msg: string) => void;
|
|
@@ -29,16 +29,31 @@ export type DispatchContext = {
|
|
|
29
29
|
|
|
30
30
|
export type RuntimeEvent =
|
|
31
31
|
| {
|
|
32
|
-
type:
|
|
32
|
+
type: 'runtime_session';
|
|
33
33
|
runtimeSessionId: string;
|
|
34
34
|
runtimeLaneKey?: string;
|
|
35
35
|
runtimeRef?: Record<string, unknown>;
|
|
36
36
|
}
|
|
37
|
-
| { type:
|
|
38
|
-
| {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
| { type: 'thinking'; text: string; groupKey?: string }
|
|
38
|
+
| {
|
|
39
|
+
type: 'tool_call';
|
|
40
|
+
callId: string;
|
|
41
|
+
toolName: string;
|
|
42
|
+
input: unknown;
|
|
43
|
+
startedAt?: string;
|
|
44
|
+
groupKey?: string;
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
type: 'tool_result';
|
|
48
|
+
callId: string;
|
|
49
|
+
toolName: string;
|
|
50
|
+
output: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
durationMs?: number;
|
|
53
|
+
groupKey?: string;
|
|
54
|
+
}
|
|
55
|
+
| { type: 'text'; text: string; project?: boolean; groupKey?: string }
|
|
56
|
+
| { type: 'error'; message: string };
|
|
42
57
|
|
|
43
58
|
export type DispatchOpts = {
|
|
44
59
|
event: ParallEvent;
|
|
@@ -98,4 +113,12 @@ export interface DispatchAdapter {
|
|
|
98
113
|
|
|
99
114
|
/** Return the path to the session's history file on disk, if the runtime persists it. */
|
|
100
115
|
getSessionHistoryPath?(sessionKey: string): string | undefined;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Abort the in-flight dispatch for the given session. Called by the gateway
|
|
119
|
+
* when the dispatch deadline is exceeded. Implementations should unblock the
|
|
120
|
+
* `dispatch()` generator (e.g. close a turn sink, end stdin, fail a stream)
|
|
121
|
+
* so the `for await` loop in `runDispatch` exits naturally. Must be idempotent.
|
|
122
|
+
*/
|
|
123
|
+
abortDispatch?(sessionKey: string): void;
|
|
101
124
|
}
|
package/src/event-format.ts
CHANGED
|
@@ -1,31 +1,58 @@
|
|
|
1
|
-
import type { ForkResult, ParallEvent } from
|
|
1
|
+
import type { ForkResult, ParallEvent } from './types.js';
|
|
2
2
|
|
|
3
3
|
function sanitizeMeta(value: string): string {
|
|
4
|
-
return value
|
|
4
|
+
return value
|
|
5
|
+
.replace(/[\r\n]+/g, ' ')
|
|
6
|
+
.replace(/[[\]|]/g, ' ')
|
|
7
|
+
.trim();
|
|
5
8
|
}
|
|
6
9
|
|
|
7
10
|
export function buildEventBody(event: ParallEvent): string {
|
|
8
11
|
const lines: string[] = [];
|
|
9
|
-
if (event.type ===
|
|
12
|
+
if (event.type === 'message') {
|
|
10
13
|
lines.push(`[Event: message.new]`);
|
|
11
14
|
const chatLabel = event.targetName
|
|
12
15
|
? `"${event.targetName}" (prll://${event.targetId})`
|
|
13
16
|
: `prll://${event.targetId}`;
|
|
14
|
-
lines.push(`[Chat: ${chatLabel} | type: ${event.targetType ??
|
|
17
|
+
lines.push(`[Chat: ${chatLabel} | type: ${event.targetType ?? 'unknown'}]`);
|
|
15
18
|
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
16
19
|
lines.push(`[Message ID: prll://${event.messageId}]`);
|
|
17
|
-
if (event.threadRootId)
|
|
20
|
+
if (event.threadRootId) {
|
|
21
|
+
const threadMeta = [
|
|
22
|
+
`prll://${event.threadRootId}`,
|
|
23
|
+
event.threadReplyCount != null ? `${event.threadReplyCount} replies` : null,
|
|
24
|
+
event.threadUnreadCount != null && event.threadUnreadCount > 0
|
|
25
|
+
? `${event.threadUnreadCount} unread`
|
|
26
|
+
: null,
|
|
27
|
+
event.threadUnreadCount != null && event.threadUnreadCount > 0 && event.threadUnreadSince
|
|
28
|
+
? `since: prll://${event.threadUnreadSince}`
|
|
29
|
+
: null,
|
|
30
|
+
]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join(' | ');
|
|
33
|
+
lines.push(`[Thread: ${threadMeta}]`);
|
|
34
|
+
}
|
|
35
|
+
if (event.unreadCount != null && event.unreadCount > 1) {
|
|
36
|
+
const countStr = event.unreadCount >= 1000 ? '999+' : String(event.unreadCount);
|
|
37
|
+
const sinceStr = event.unreadSince ? ` | since: prll://${event.unreadSince}` : '';
|
|
38
|
+
let line = `[Unread: ${countStr} messages${sinceStr}]`;
|
|
39
|
+
if (event.unreadCount > 50) line += ` — fetch recent context with --limit, not all`;
|
|
40
|
+
lines.push(line);
|
|
41
|
+
}
|
|
18
42
|
if (event.noReply) lines.push(`[Hint: no_reply]`);
|
|
19
43
|
if (event.attachments?.length) {
|
|
20
44
|
for (const att of event.attachments) {
|
|
21
|
-
const sizeStr =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
const sizeStr =
|
|
46
|
+
att.fileSize >= 1048576
|
|
47
|
+
? `${(att.fileSize / 1048576).toFixed(1)}MB`
|
|
48
|
+
: `${Math.round(att.fileSize / 1024)}KB`;
|
|
49
|
+
lines.push(
|
|
50
|
+
`[Attachment: prll://${att.id} | ${sanitizeMeta(att.mimeType)} | ${sizeStr} | ${sanitizeMeta(att.fileName)}]`,
|
|
51
|
+
);
|
|
25
52
|
}
|
|
26
53
|
}
|
|
27
|
-
lines.push(
|
|
28
|
-
} else if (event.type ===
|
|
54
|
+
lines.push('', event.body);
|
|
55
|
+
} else if (event.type === 'task_comment') {
|
|
29
56
|
lines.push(`[Event: task.comment.created]`);
|
|
30
57
|
const taskLabel = event.targetName
|
|
31
58
|
? `${event.targetName} (prll://${event.targetId})`
|
|
@@ -34,20 +61,34 @@ export function buildEventBody(event: ParallEvent): string {
|
|
|
34
61
|
if (event.deliveryReason) lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
|
|
35
62
|
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
36
63
|
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
37
|
-
lines.push(
|
|
38
|
-
} else if (event.type ===
|
|
64
|
+
lines.push('', event.body);
|
|
65
|
+
} else if (event.type === 'wiki_comment') {
|
|
66
|
+
lines.push(`[Event: wiki.comment.created]`);
|
|
67
|
+
const target = event.replyTargetUri ?? `prll://${event.targetId}`;
|
|
68
|
+
if (event.targetType === 'changeset') {
|
|
69
|
+
lines.push(`[Wiki Changeset: ${target}]`);
|
|
70
|
+
} else {
|
|
71
|
+
lines.push(
|
|
72
|
+
`[Wiki: ${event.targetName ? `${sanitizeMeta(event.targetName)} (${target})` : target}]`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (event.deliveryReason) lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
|
|
76
|
+
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
77
|
+
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
78
|
+
lines.push('', event.body);
|
|
79
|
+
} else if (event.type === 'approval') {
|
|
39
80
|
lines.push(`[Event: approval.decided]`);
|
|
40
81
|
lines.push(`[Approval: prll://${event.messageId}]`);
|
|
41
82
|
lines.push(`[Chat: prll://${event.targetId}]`);
|
|
42
83
|
lines.push(`[Decided by: ${event.senderName} (prll://${event.senderId})]`);
|
|
43
|
-
lines.push(
|
|
44
|
-
} else if (event.type ===
|
|
84
|
+
lines.push('', event.body);
|
|
85
|
+
} else if (event.type === 'schedule') {
|
|
45
86
|
lines.push(`[Event: schedule.fired]`);
|
|
46
87
|
lines.push(`[Schedule: prll://${event.targetId}]`);
|
|
47
88
|
lines.push(`[Run: prll://${event.messageId}]`);
|
|
48
89
|
if (event.scheduledFireAt) lines.push(`[Scheduled at: ${sanitizeMeta(event.scheduledFireAt)}]`);
|
|
49
90
|
if (event.attachedUri) lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
50
|
-
lines.push(
|
|
91
|
+
lines.push('', event.body);
|
|
51
92
|
} else {
|
|
52
93
|
lines.push(`[Event: task.assigned]`);
|
|
53
94
|
const taskLabel = event.targetName
|
|
@@ -55,31 +96,36 @@ export function buildEventBody(event: ParallEvent): string {
|
|
|
55
96
|
: `prll://${event.targetId}`;
|
|
56
97
|
lines.push(`[Task: ${taskLabel}]`);
|
|
57
98
|
lines.push(`[Assigned by: ${event.senderName} (prll://${event.senderId})]`);
|
|
58
|
-
lines.push(
|
|
99
|
+
lines.push('', event.body);
|
|
59
100
|
}
|
|
60
|
-
return lines.join(
|
|
101
|
+
return lines.join('\n') + buildSendMessageHint(event);
|
|
61
102
|
}
|
|
62
103
|
|
|
63
104
|
export function buildEventBodyForForkResult(event: ParallEvent): string {
|
|
64
|
-
return buildEventBody(event).replace(/\n<system-reminder>[\s\S]*<\/system-reminder>$/,
|
|
105
|
+
return buildEventBody(event).replace(/\n<system-reminder>[\s\S]*<\/system-reminder>$/, '');
|
|
65
106
|
}
|
|
66
107
|
|
|
67
108
|
function buildSendMessageHint(event: ParallEvent): string {
|
|
68
|
-
if (event.noReply) return
|
|
109
|
+
if (event.noReply) return '';
|
|
110
|
+
|
|
111
|
+
if (event.type === 'wiki_comment' && event.replyTargetUri) {
|
|
112
|
+
const where = event.targetType === 'changeset' ? 'this changeset comment' : 'this wiki page';
|
|
113
|
+
return `\n<system-reminder>To reply on ${where}, run: \`parall comments add --target "${event.replyTargetUri}" --body "..."\` (read the thread first with \`parall comments list --target "${event.replyTargetUri}"\`). To message someone instead, use \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
114
|
+
}
|
|
69
115
|
|
|
70
|
-
if (event.targetId.startsWith(
|
|
116
|
+
if (event.targetId.startsWith('cht_')) {
|
|
71
117
|
return `\n<system-reminder>To reply, run: \`parall messages send prll://${event.targetId} --text "..."\` — your plain text output is not delivered to the chat.</system-reminder>`;
|
|
72
118
|
}
|
|
73
119
|
|
|
74
|
-
if (event.targetId.startsWith(
|
|
120
|
+
if (event.targetId.startsWith('tsk_')) {
|
|
75
121
|
return `\n<system-reminder>To respond, use the CLI: \`parall tasks update\` / \`parall tasks comments add\`. To message someone, use \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
76
122
|
}
|
|
77
123
|
|
|
78
|
-
if (event.targetId.startsWith(
|
|
124
|
+
if (event.targetId.startsWith('sch_')) {
|
|
79
125
|
return `\n<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
80
126
|
}
|
|
81
127
|
|
|
82
|
-
return
|
|
128
|
+
return '';
|
|
83
129
|
}
|
|
84
130
|
|
|
85
131
|
export function buildForkScopePrefix(event: ParallEvent): string {
|
|
@@ -90,17 +136,21 @@ export function buildForkScopePrefix(event: ParallEvent): string {
|
|
|
90
136
|
}
|
|
91
137
|
|
|
92
138
|
export function buildForkResultPrefix(results: ForkResult[]): string {
|
|
93
|
-
if (!results.length) return
|
|
139
|
+
if (!results.length) return '';
|
|
94
140
|
const blocks = results.map((result) => {
|
|
95
141
|
const lines: string[] = [];
|
|
96
142
|
for (const body of result.eventBodies) {
|
|
97
143
|
lines.push(body);
|
|
98
144
|
}
|
|
99
|
-
lines.push(
|
|
100
|
-
|
|
101
|
-
|
|
145
|
+
lines.push(
|
|
146
|
+
`[This event was handled by a parallel fork session. Do NOT re-handle, re-reply, or duplicate work for it.]`,
|
|
147
|
+
);
|
|
148
|
+
lines.push(
|
|
149
|
+
`[Fork summary: ${result.agentSummary ? sanitizeMeta(result.agentSummary) : 'No fork summary available — the fork completed without producing a text summary. Check the target chat/task for any actions the fork already took before acting.'}]`,
|
|
150
|
+
);
|
|
151
|
+
if (result.actions.length) lines.push(`[Fork actions: ${result.actions.join('; ')}]`);
|
|
102
152
|
if (result.historyPath) lines.push(`[Fork history: ${result.historyPath}]`);
|
|
103
|
-
return lines.join(
|
|
153
|
+
return lines.join('\n');
|
|
104
154
|
});
|
|
105
|
-
return blocks.join(
|
|
155
|
+
return blocks.join('\n\n') + '\n\n---\n\n';
|
|
106
156
|
}
|