@parall/parel-channel 1.39.0 → 1.40.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/dist/inbound.d.ts.map +1 -1
- package/dist/inbound.js +14 -2
- package/dist/session.d.ts +31 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +72 -0
- package/package.json +1 -1
- package/src/inbound.ts +14 -1
- package/src/session.ts +78 -0
package/dist/inbound.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACf,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACf,MAAM,mBAAmB,CAAC;AAiD3B,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAiB5B;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAuDrF;AAkdD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyD7B"}
|
package/dist/inbound.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildChannelPrompt, buildChatPrompt } from './channel-prompt.js';
|
|
2
2
|
import { parallApiUrl, parallOrgId, requireAgk } from './connect.js';
|
|
3
|
-
import { createChannelInputStep, createInputStep, createTraceStep, ensureSession, invalidateSession, markDispatchReceived, patchSession, REQUEST_TIMEOUT_MS, TURN_ENVELOPE_KEY, } from './session.js';
|
|
3
|
+
import { createChannelInputStep, createErrorStep, createInputStep, createTraceStep, ensureSession, invalidateSession, markDispatchReceived, patchSession, REQUEST_TIMEOUT_MS, TURN_ENVELOPE_KEY, } from './session.js';
|
|
4
4
|
export async function handleParallFrame(frame, ctx) {
|
|
5
5
|
let event;
|
|
6
6
|
try {
|
|
@@ -525,10 +525,22 @@ export async function handleParallAgentEvent(event, ctx) {
|
|
|
525
525
|
}
|
|
526
526
|
const sessionId = await ensureSession(ctx);
|
|
527
527
|
if (sessionId) {
|
|
528
|
+
// Error step BEFORE the idle patch so the panel orders failure →
|
|
529
|
+
// done; the step is what makes a failed turn visible at all (the
|
|
530
|
+
// reply never arrives — see createErrorStep).
|
|
531
|
+
let stale = false;
|
|
532
|
+
if (event.type === 'turn_failed') {
|
|
533
|
+
const stepResult = await createErrorStep(ctx, sessionId, {
|
|
534
|
+
subject: chatId,
|
|
535
|
+
turnId: event.turnId,
|
|
536
|
+
error: event.error,
|
|
537
|
+
});
|
|
538
|
+
stale = stepResult === 'stale';
|
|
539
|
+
}
|
|
528
540
|
const result = await patchSession(ctx, sessionId, { status: 'idle' });
|
|
529
541
|
// A closed session is already terminal (frontend shows done) — just
|
|
530
542
|
// drop the cache so the next dispatch resolves a fresh one.
|
|
531
|
-
if (result === 'stale')
|
|
543
|
+
if (stale || result === 'stale')
|
|
532
544
|
await invalidateSession(ctx);
|
|
533
545
|
}
|
|
534
546
|
return [];
|
package/dist/session.d.ts
CHANGED
|
@@ -87,6 +87,37 @@ export declare function createChannelInputStep(ctx: ConnectorContext, sessionId:
|
|
|
87
87
|
text: string;
|
|
88
88
|
sentAt?: string;
|
|
89
89
|
}): Promise<ReportResult>;
|
|
90
|
+
/**
|
|
91
|
+
* Credential redaction for runtime error strings that become user-visible
|
|
92
|
+
* step content. Two layers: every secret VALUE the connector actually holds
|
|
93
|
+
* (`ctx.secrets`) is replaced exactly — the strongest guarantee for the
|
|
94
|
+
* secrets in this connector's scope — then credential-shaped patterns are
|
|
95
|
+
* masked (parall key prefixes, common vendor prefixes, AWS access key ids,
|
|
96
|
+
* bearer tokens, and any long unbroken token as a fallback: credentials are
|
|
97
|
+
* long random strings, while prose and paths break on spaces and slashes
|
|
98
|
+
* first). Pattern matching can't catch every conceivable secret (e.g. a
|
|
99
|
+
* short password echoed by a foreign exception), which is the accepted
|
|
100
|
+
* trade-off for keeping the error text diagnosable — the audience is the
|
|
101
|
+
* org-internal session panel.
|
|
102
|
+
*/
|
|
103
|
+
export declare function redactSecrets(s: string, knownValues?: string[]): string;
|
|
104
|
+
/**
|
|
105
|
+
* turn_failed → an error step in the session panel, following the
|
|
106
|
+
* cross-runtime error-step contract (text step + content.status:'error',
|
|
107
|
+
* same shape agent-core's dispatch-failure step uses — pinned by
|
|
108
|
+
* ts/protocol-vectors/agent-steps.json). Without this the turn's failure is
|
|
109
|
+
* invisible on the parall side: the 2026-07-05 parel regression was fully
|
|
110
|
+
* silent because this connector dropped event.error.
|
|
111
|
+
*
|
|
112
|
+
* The error string comes from parel's runtime (exception messages) — length
|
|
113
|
+
* is unbounded and it can echo credential-shaped values, so it is redacted
|
|
114
|
+
* (see redactSecrets) and truncated before leaving the connector.
|
|
115
|
+
*/
|
|
116
|
+
export declare function createErrorStep(ctx: ConnectorContext, sessionId: string, args: {
|
|
117
|
+
subject: string;
|
|
118
|
+
turnId: string;
|
|
119
|
+
error: string;
|
|
120
|
+
}): Promise<ReportResult>;
|
|
90
121
|
/**
|
|
91
122
|
* E2 step-trace events → parall AgentSteps, shaped exactly like agent-core's
|
|
92
123
|
* createRuntimeStep so the session panel renders them identically to CC's.
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/session.js
CHANGED
|
@@ -194,6 +194,78 @@ export async function createChannelInputStep(ctx, sessionId, args) {
|
|
|
194
194
|
return 'failed';
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
|
+
const ERROR_TEXT_MAX = 500;
|
|
198
|
+
/**
|
|
199
|
+
* Credential redaction for runtime error strings that become user-visible
|
|
200
|
+
* step content. Two layers: every secret VALUE the connector actually holds
|
|
201
|
+
* (`ctx.secrets`) is replaced exactly — the strongest guarantee for the
|
|
202
|
+
* secrets in this connector's scope — then credential-shaped patterns are
|
|
203
|
+
* masked (parall key prefixes, common vendor prefixes, AWS access key ids,
|
|
204
|
+
* bearer tokens, and any long unbroken token as a fallback: credentials are
|
|
205
|
+
* long random strings, while prose and paths break on spaces and slashes
|
|
206
|
+
* first). Pattern matching can't catch every conceivable secret (e.g. a
|
|
207
|
+
* short password echoed by a foreign exception), which is the accepted
|
|
208
|
+
* trade-off for keeping the error text diagnosable — the audience is the
|
|
209
|
+
* org-internal session panel.
|
|
210
|
+
*/
|
|
211
|
+
export function redactSecrets(s, knownValues = []) {
|
|
212
|
+
let out = s;
|
|
213
|
+
for (const v of knownValues) {
|
|
214
|
+
// Skip tiny values: replacing e.g. a 3-char string would shred prose.
|
|
215
|
+
if (typeof v === 'string' && v.length >= 6)
|
|
216
|
+
out = out.split(v).join('***');
|
|
217
|
+
}
|
|
218
|
+
return out
|
|
219
|
+
.replace(/\b(agk|mck|cpk)_[A-Za-z0-9_-]+/g, '$1_***')
|
|
220
|
+
.replace(/\b(sk|pk|rk)-[A-Za-z0-9_-]{8,}/g, '$1-***')
|
|
221
|
+
.replace(/\bAKIA[0-9A-Z]{16}\b/g, 'AKIA***')
|
|
222
|
+
.replace(/\b(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi, '$1***')
|
|
223
|
+
.replace(/[A-Za-z0-9_-]{32,}/g, '***');
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* turn_failed → an error step in the session panel, following the
|
|
227
|
+
* cross-runtime error-step contract (text step + content.status:'error',
|
|
228
|
+
* same shape agent-core's dispatch-failure step uses — pinned by
|
|
229
|
+
* ts/protocol-vectors/agent-steps.json). Without this the turn's failure is
|
|
230
|
+
* invisible on the parall side: the 2026-07-05 parel regression was fully
|
|
231
|
+
* silent because this connector dropped event.error.
|
|
232
|
+
*
|
|
233
|
+
* The error string comes from parel's runtime (exception messages) — length
|
|
234
|
+
* is unbounded and it can echo credential-shaped values, so it is redacted
|
|
235
|
+
* (see redactSecrets) and truncated before leaving the connector.
|
|
236
|
+
*/
|
|
237
|
+
export async function createErrorStep(ctx, sessionId, args) {
|
|
238
|
+
const knownSecrets = Object.values((ctx.secrets ?? {})).filter((v) => typeof v === 'string');
|
|
239
|
+
const text = `Turn failed: ${redactSecrets(args.error, knownSecrets)}`.slice(0, ERROR_TEXT_MAX);
|
|
240
|
+
try {
|
|
241
|
+
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/agents/${parallAgentId(ctx)}/sessions/${sessionId}/steps`;
|
|
242
|
+
// Same subject-prefix line createTraceStep draws: chv_ turns must not
|
|
243
|
+
// target 'chat' or the broadcast lands on a channel no client watches.
|
|
244
|
+
const targetType = args.subject.startsWith('chv_') ? 'channel_conversation' : 'chat';
|
|
245
|
+
const res = await fetch(url, {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: jsonHeaders(ctx),
|
|
248
|
+
body: JSON.stringify({
|
|
249
|
+
step_type: 'text',
|
|
250
|
+
target_type: targetType,
|
|
251
|
+
target_id: args.subject,
|
|
252
|
+
// At-most-once today (agent-event pushes are not replayed), but the
|
|
253
|
+
// turnId key makes a future retry path free of duplicate error rows.
|
|
254
|
+
idempotency_key: `error:${args.turnId}`,
|
|
255
|
+
content: { text, suppressed: false, status: 'error' },
|
|
256
|
+
}),
|
|
257
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
258
|
+
});
|
|
259
|
+
if (res.ok)
|
|
260
|
+
return 'ok';
|
|
261
|
+
console.error(`[parel-channel] error step failed (${res.status})`);
|
|
262
|
+
return resultForStatus(res.status);
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
console.error('[parel-channel] error step failed', err);
|
|
266
|
+
return 'failed';
|
|
267
|
+
}
|
|
268
|
+
}
|
|
197
269
|
// Subject-scoped: one connector serves many per-subject sessions, and
|
|
198
270
|
// provider call ids are only unique within a turn — never let two subjects'
|
|
199
271
|
// concurrent calls overwrite each other's parked name.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/parel-channel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.40.0",
|
|
4
4
|
"description": "Parall channel plugin for the parel runtime — lets a parel agent treat Parall as a managed_ws channel (receive dispatches, reply via the Parall API)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/src/inbound.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { buildChannelPrompt, buildChatPrompt } from './channel-prompt.js';
|
|
|
9
9
|
import { parallApiUrl, parallOrgId, requireAgk } from './connect.js';
|
|
10
10
|
import {
|
|
11
11
|
createChannelInputStep,
|
|
12
|
+
createErrorStep,
|
|
12
13
|
createInputStep,
|
|
13
14
|
createTraceStep,
|
|
14
15
|
ensureSession,
|
|
@@ -638,10 +639,22 @@ export async function handleParallAgentEvent(
|
|
|
638
639
|
}
|
|
639
640
|
const sessionId = await ensureSession(ctx);
|
|
640
641
|
if (sessionId) {
|
|
642
|
+
// Error step BEFORE the idle patch so the panel orders failure →
|
|
643
|
+
// done; the step is what makes a failed turn visible at all (the
|
|
644
|
+
// reply never arrives — see createErrorStep).
|
|
645
|
+
let stale = false;
|
|
646
|
+
if (event.type === 'turn_failed') {
|
|
647
|
+
const stepResult = await createErrorStep(ctx, sessionId, {
|
|
648
|
+
subject: chatId,
|
|
649
|
+
turnId: event.turnId,
|
|
650
|
+
error: event.error,
|
|
651
|
+
});
|
|
652
|
+
stale = stepResult === 'stale';
|
|
653
|
+
}
|
|
641
654
|
const result = await patchSession(ctx, sessionId, { status: 'idle' });
|
|
642
655
|
// A closed session is already terminal (frontend shows done) — just
|
|
643
656
|
// drop the cache so the next dispatch resolves a fresh one.
|
|
644
|
-
if (result === 'stale') await invalidateSession(ctx);
|
|
657
|
+
if (stale || result === 'stale') await invalidateSession(ctx);
|
|
645
658
|
}
|
|
646
659
|
return [];
|
|
647
660
|
}
|
package/src/session.ts
CHANGED
|
@@ -230,6 +230,84 @@ export async function createChannelInputStep(
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
const ERROR_TEXT_MAX = 500;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Credential redaction for runtime error strings that become user-visible
|
|
237
|
+
* step content. Two layers: every secret VALUE the connector actually holds
|
|
238
|
+
* (`ctx.secrets`) is replaced exactly — the strongest guarantee for the
|
|
239
|
+
* secrets in this connector's scope — then credential-shaped patterns are
|
|
240
|
+
* masked (parall key prefixes, common vendor prefixes, AWS access key ids,
|
|
241
|
+
* bearer tokens, and any long unbroken token as a fallback: credentials are
|
|
242
|
+
* long random strings, while prose and paths break on spaces and slashes
|
|
243
|
+
* first). Pattern matching can't catch every conceivable secret (e.g. a
|
|
244
|
+
* short password echoed by a foreign exception), which is the accepted
|
|
245
|
+
* trade-off for keeping the error text diagnosable — the audience is the
|
|
246
|
+
* org-internal session panel.
|
|
247
|
+
*/
|
|
248
|
+
export function redactSecrets(s: string, knownValues: string[] = []): string {
|
|
249
|
+
let out = s;
|
|
250
|
+
for (const v of knownValues) {
|
|
251
|
+
// Skip tiny values: replacing e.g. a 3-char string would shred prose.
|
|
252
|
+
if (typeof v === 'string' && v.length >= 6) out = out.split(v).join('***');
|
|
253
|
+
}
|
|
254
|
+
return out
|
|
255
|
+
.replace(/\b(agk|mck|cpk)_[A-Za-z0-9_-]+/g, '$1_***')
|
|
256
|
+
.replace(/\b(sk|pk|rk)-[A-Za-z0-9_-]{8,}/g, '$1-***')
|
|
257
|
+
.replace(/\bAKIA[0-9A-Z]{16}\b/g, 'AKIA***')
|
|
258
|
+
.replace(/\b(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi, '$1***')
|
|
259
|
+
.replace(/[A-Za-z0-9_-]{32,}/g, '***');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* turn_failed → an error step in the session panel, following the
|
|
264
|
+
* cross-runtime error-step contract (text step + content.status:'error',
|
|
265
|
+
* same shape agent-core's dispatch-failure step uses — pinned by
|
|
266
|
+
* ts/protocol-vectors/agent-steps.json). Without this the turn's failure is
|
|
267
|
+
* invisible on the parall side: the 2026-07-05 parel regression was fully
|
|
268
|
+
* silent because this connector dropped event.error.
|
|
269
|
+
*
|
|
270
|
+
* The error string comes from parel's runtime (exception messages) — length
|
|
271
|
+
* is unbounded and it can echo credential-shaped values, so it is redacted
|
|
272
|
+
* (see redactSecrets) and truncated before leaving the connector.
|
|
273
|
+
*/
|
|
274
|
+
export async function createErrorStep(
|
|
275
|
+
ctx: ConnectorContext,
|
|
276
|
+
sessionId: string,
|
|
277
|
+
args: { subject: string; turnId: string; error: string },
|
|
278
|
+
): Promise<ReportResult> {
|
|
279
|
+
const knownSecrets = Object.values((ctx.secrets ?? {}) as Record<string, unknown>).filter(
|
|
280
|
+
(v): v is string => typeof v === 'string',
|
|
281
|
+
);
|
|
282
|
+
const text = `Turn failed: ${redactSecrets(args.error, knownSecrets)}`.slice(0, ERROR_TEXT_MAX);
|
|
283
|
+
try {
|
|
284
|
+
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/agents/${parallAgentId(ctx)}/sessions/${sessionId}/steps`;
|
|
285
|
+
// Same subject-prefix line createTraceStep draws: chv_ turns must not
|
|
286
|
+
// target 'chat' or the broadcast lands on a channel no client watches.
|
|
287
|
+
const targetType = args.subject.startsWith('chv_') ? 'channel_conversation' : 'chat';
|
|
288
|
+
const res = await fetch(url, {
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: jsonHeaders(ctx),
|
|
291
|
+
body: JSON.stringify({
|
|
292
|
+
step_type: 'text',
|
|
293
|
+
target_type: targetType,
|
|
294
|
+
target_id: args.subject,
|
|
295
|
+
// At-most-once today (agent-event pushes are not replayed), but the
|
|
296
|
+
// turnId key makes a future retry path free of duplicate error rows.
|
|
297
|
+
idempotency_key: `error:${args.turnId}`,
|
|
298
|
+
content: { text, suppressed: false, status: 'error' },
|
|
299
|
+
}),
|
|
300
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
301
|
+
});
|
|
302
|
+
if (res.ok) return 'ok';
|
|
303
|
+
console.error(`[parel-channel] error step failed (${res.status})`);
|
|
304
|
+
return resultForStatus(res.status);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
console.error('[parel-channel] error step failed', err);
|
|
307
|
+
return 'failed';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
233
311
|
// Subject-scoped: one connector serves many per-subject sessions, and
|
|
234
312
|
// provider call ids are only unique within a turn — never let two subjects'
|
|
235
313
|
// concurrent calls overwrite each other's parked name.
|