@parall/parel-channel 1.41.0 → 1.42.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/channel-prompt.d.ts +4 -3
- package/dist/channel-prompt.d.ts.map +1 -1
- package/dist/channel-prompt.js +4 -3
- package/dist/delivery.d.ts +23 -16
- package/dist/delivery.d.ts.map +1 -1
- package/dist/delivery.js +85 -116
- package/dist/fork.d.ts +8 -5
- package/dist/fork.d.ts.map +1 -1
- package/dist/fork.js +9 -6
- package/dist/inbound-channel.d.ts +1 -1
- package/dist/inbound-channel.d.ts.map +1 -1
- package/dist/inbound-channel.js +13 -7
- package/dist/inbound.d.ts.map +1 -1
- package/dist/inbound.js +87 -20
- package/dist/session.d.ts +67 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +166 -2
- package/dist/typed-dispatch.d.ts +14 -8
- package/dist/typed-dispatch.d.ts.map +1 -1
- package/dist/typed-dispatch.js +5 -3
- package/package.json +1 -1
- package/src/channel-prompt.ts +4 -3
- package/src/delivery.ts +84 -114
- package/src/fork.ts +9 -6
- package/src/inbound-channel.ts +14 -6
- package/src/inbound.ts +109 -23
- package/src/session.ts +201 -1
- package/src/typed-dispatch.ts +19 -11
package/dist/inbound.js
CHANGED
|
@@ -3,7 +3,7 @@ import { parallApiUrl, parallOrgId, requireAgk, sandboxEnabled } from './connect
|
|
|
3
3
|
import { effectsForChannelDispatch, PROVIDER_LOOKUP_TIMEOUT_MS } from './inbound-channel.js';
|
|
4
4
|
import { buildForkScopePrefix, bumpForkRetryAttempts, childConfirmed, childRefKnown, clearForkState, clearForkSubject, FORK_RETRY_DELAY_MS, FORK_RETRY_PREFIX, handleChildSpawnFailed, MAX_SPAWN_VERIFY_ATTEMPTS, noteMainTurnEvent, planForkDecision, recordSpawn, settleChildEvent, trackFollowUpAck, } from './fork.js';
|
|
5
5
|
import { asChildSpawnFailed, asConnectorEffects, } from './parel-sdk-compat.js';
|
|
6
|
-
import { ackByIdEffect, ackEffect, conversationKey, createErrorStep, fetchMainSessionFresh, createInputStep, createTraceStep, createTypedInputStep, ensureChildSession, ensureSession, invalidateChildSession, invalidateSession, markDispatchReceived, patchSession, reportingEnabled, SESSION_CACHE_KEY, TURN_ENVELOPE_KEY, } from './session.js';
|
|
6
|
+
import { ackByIdEffect, ackEffect, completeFencedEnvelopes, conversationKey, createErrorStep, fetchMainSessionFresh, createInputStep, createTraceStep, createTypedInputStep, EMIT_TTL_MS, ensureChildSession, ensureSession, invalidateChildSession, invalidateSession, markDispatchReceived, nextEnvelopeId, patchSession, readActiveEmit, reportingEnabled, SESSION_CACHE_KEY, TURN_ENVELOPE_KEY, writeActiveEmit, } from './session.js';
|
|
7
7
|
import { isTypedEventType, planTypedDispatch } from './typed-dispatch.js';
|
|
8
8
|
/**
|
|
9
9
|
* Connection-local hook serialization. The parel ConnectorHost dispatches
|
|
@@ -212,6 +212,25 @@ async function catchUpParallInner(ctx) {
|
|
|
212
212
|
}
|
|
213
213
|
return effects;
|
|
214
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Turn-in-flight guard for redelivery (catch-up sweep AND repeated
|
|
217
|
+
* dispatch.new pushes). Main-path rows stay live (not acked) for the whole
|
|
218
|
+
* turn now, and GET /dispatch lists pending AND received rows — so redelivery
|
|
219
|
+
* re-sees every in-flight source. Skip any row whose attempt fence is younger
|
|
220
|
+
* than EMIT_TTL_MS, REGARDLESS of row status: a transiently-failed
|
|
221
|
+
* /dispatch/received leaves the row `pending` while the emit succeeded (fence
|
|
222
|
+
* written), and re-emitting it inside the TTL would duplicate the in-flight
|
|
223
|
+
* turn — complete-sources resolves pending rows just as well, so the fence
|
|
224
|
+
* alone is the truth. Past the TTL (crashed emit, lost turn events) fall
|
|
225
|
+
* through and re-emit with a fresh attempt-suffixed envelope id. Brand-new
|
|
226
|
+
* sources have no fence and never skip; fork-handled rows never write a
|
|
227
|
+
* fence (their ack machinery owns redelivery there).
|
|
228
|
+
* Design: parel-reply-contract-alignment.md §5.3.
|
|
229
|
+
*/
|
|
230
|
+
async function shouldSkipLiveRow(ctx, sourceType, sourceId) {
|
|
231
|
+
const active = await readActiveEmit(ctx, sourceType, sourceId);
|
|
232
|
+
return active !== null && ctx.now() - active.at < EMIT_TTL_MS;
|
|
233
|
+
}
|
|
215
234
|
async function effectsForDispatch(data, ctx) {
|
|
216
235
|
const sourceType = String(data.source_type ?? '');
|
|
217
236
|
const sourceId = data.source_id ? String(data.source_id) : undefined;
|
|
@@ -220,7 +239,9 @@ async function effectsForDispatch(data, ctx) {
|
|
|
220
239
|
if (sourceType === 'channel_message' && sourceId) {
|
|
221
240
|
// External IM message (chat_id is NULL by design — the conversation
|
|
222
241
|
// lives on the provider, not in a Parall chat).
|
|
223
|
-
|
|
242
|
+
if (await shouldSkipLiveRow(ctx, sourceType, sourceId))
|
|
243
|
+
return [];
|
|
244
|
+
return effectsForChannelDispatch(sourceId, ctx);
|
|
224
245
|
}
|
|
225
246
|
const eventType = String(data.event_type ?? '');
|
|
226
247
|
if (sourceId && isTypedEventType(eventType)) {
|
|
@@ -233,10 +254,13 @@ async function effectsForDispatch(data, ctx) {
|
|
|
233
254
|
return [];
|
|
234
255
|
}
|
|
235
256
|
// Conversational-only deployments (no sandbox → no `parall` CLI) cannot
|
|
236
|
-
// act on
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
|
|
257
|
+
// act on typed work AT ALL under the CLI-only reply contract — nothing
|
|
258
|
+
// auto-delivers anymore, approval_decided included — so emitting AND
|
|
259
|
+
// acking would silently consume real work. Keep it pending (same
|
|
260
|
+
// posture as unknown types). Defensive-only: the server's mandatory
|
|
261
|
+
// sandbox gate (422 PAREL_SANDBOX_REQUIRED) means no such managed
|
|
262
|
+
// agent can be provisioned.
|
|
263
|
+
if (!sandboxEnabled(ctx)) {
|
|
240
264
|
return [];
|
|
241
265
|
}
|
|
242
266
|
return effectsForTypedDispatch(data, ctx);
|
|
@@ -252,6 +276,8 @@ async function effectsForDispatch(data, ctx) {
|
|
|
252
276
|
// A message dispatch with no chat can never be routed or replied to.
|
|
253
277
|
return [ackEffect(ctx, sourceType, sourceId)];
|
|
254
278
|
}
|
|
279
|
+
if (await shouldSkipLiveRow(ctx, sourceType, sourceId))
|
|
280
|
+
return [];
|
|
255
281
|
const apiUrl = parallApiUrl(ctx);
|
|
256
282
|
const agk = requireAgk(ctx);
|
|
257
283
|
const orgId = parallOrgId(ctx);
|
|
@@ -306,7 +332,11 @@ async function effectsForDispatch(data, ctx) {
|
|
|
306
332
|
text,
|
|
307
333
|
attachments,
|
|
308
334
|
});
|
|
309
|
-
|
|
335
|
+
// Deterministic source-derived envelope id (+ attempt suffix on re-emits):
|
|
336
|
+
// complete-sources strips the suffix back to the source, and every re-emit
|
|
337
|
+
// is globally unique so parel's dedupe (either way) can't strand a retry.
|
|
338
|
+
const priorEmit = await readActiveEmit(ctx, sourceType, sourceId);
|
|
339
|
+
const envelopeId = nextEnvelopeId(sourceType, sourceId, priorEmit);
|
|
310
340
|
// Input-step summary must never be empty (attachment-only messages).
|
|
311
341
|
const stepText = text || `[${attachments.length} attachment(s)]`;
|
|
312
342
|
// Thread-aware conversation identity: F2 grouping / F3 injection / fork
|
|
@@ -321,9 +351,16 @@ async function effectsForDispatch(data, ctx) {
|
|
|
321
351
|
// Per-turn invocation context: parel snapshots this at turn start and
|
|
322
352
|
// consume-gated plugins receive it — sandbox-e2b flattens it into per-exec
|
|
323
353
|
// env, so `parall` CLI calls inside the sandbox land on the right chat.
|
|
354
|
+
// The PRLL_DISPATCH_SOURCE_* pair is the CLI's by-source ledger binding: a
|
|
355
|
+
// send back into the exact trigger (chat, thread) carries dispatch_source
|
|
356
|
+
// so the server resolves this WorkItem in the reply's own transaction.
|
|
357
|
+
// (Fork turns get it too — their rows are legacy-acked so the resolve is a
|
|
358
|
+
// no-op today, and it wires up for free when fork ledger parity lands.)
|
|
324
359
|
const invocationContext = {
|
|
325
360
|
PRLL_CHAT_ID: chatId,
|
|
326
361
|
PRLL_TRIGGER_MESSAGE_ID: sourceId,
|
|
362
|
+
PRLL_DISPATCH_SOURCE_TYPE: sourceType,
|
|
363
|
+
PRLL_DISPATCH_SOURCE_ID: sourceId,
|
|
327
364
|
...(threadRootId ? { PRLL_THREAD_ROOT_ID: threadRootId } : {}),
|
|
328
365
|
};
|
|
329
366
|
const envelope = {
|
|
@@ -494,15 +531,16 @@ async function effectsForDispatch(data, ctx) {
|
|
|
494
531
|
}
|
|
495
532
|
// fall through: black-holed spawn rides main below.
|
|
496
533
|
}
|
|
497
|
-
// Main path. Progress reporting (best-effort
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
//
|
|
534
|
+
// Main path. Progress reporting (best-effort): received flips the row out
|
|
535
|
+
// of 'pending' and lights the indicator (dispatch.received → active); the
|
|
536
|
+
// row then STAYS 'received' for the whole turn — resolution comes from the
|
|
537
|
+
// CLI reply's dispatch_source or the turn event's complete-sources, never
|
|
538
|
+
// from an ack here (the old ack-on-emit is what killed the indicator
|
|
539
|
+
// early; parel-reply-contract-alignment.md §1.1). received still settles
|
|
540
|
+
// before this function returns so a same-batch reply can never race the
|
|
541
|
+
// pending→received transition. It is deliberately NOT gated on the
|
|
542
|
+
// session resolving: a transient session failure must not also cost the
|
|
543
|
+
// indicator.
|
|
506
544
|
const receivedSettled = markDispatchReceived(ctx, sourceType, sourceId);
|
|
507
545
|
let sessionId = await ensureSession(ctx);
|
|
508
546
|
if (sessionId) {
|
|
@@ -540,7 +578,20 @@ async function effectsForDispatch(data, ctx) {
|
|
|
540
578
|
}
|
|
541
579
|
}
|
|
542
580
|
await receivedSettled;
|
|
543
|
-
|
|
581
|
+
// Arm the attempt fence BEFORE the emit effect is returned: turn events
|
|
582
|
+
// complete a source only while `active:{source}` matches their envelope id
|
|
583
|
+
// exactly, and the redelivery-skip window starts at this timestamp. Only
|
|
584
|
+
// the main path writes it — fork paths keep their own ack machinery.
|
|
585
|
+
// Deliberate ordering tradeoff: a crash between this write and the host
|
|
586
|
+
// executing the emit leaves an armed fence for an un-emitted source, so
|
|
587
|
+
// the reconnect sweep hides it until the TTL re-emit (≤30min delay). The
|
|
588
|
+
// reverse order (fence after emit) would instead double-emit on a crash —
|
|
589
|
+
// a duplicate agent turn. Delay heals; duplicates don't.
|
|
590
|
+
await writeActiveEmit(ctx, sourceType, sourceId, envelopeId);
|
|
591
|
+
// No ack effect: the row stays 'received' until the reply (dispatch
|
|
592
|
+
// _source) or the turn event (complete-sources) resolves it — the
|
|
593
|
+
// indicator's whole lifecycle rides on that.
|
|
594
|
+
return [{ type: 'emitEvent', event: envelope }];
|
|
544
595
|
}
|
|
545
596
|
/**
|
|
546
597
|
* Typed (non-message) dispatch → main-session envelope. The heavy lifting
|
|
@@ -613,9 +664,11 @@ async function effectsForTypedDispatch(data, ctx) {
|
|
|
613
664
|
replyRoute: {
|
|
614
665
|
kind: 'provider_http',
|
|
615
666
|
connectionId: ctx.connectionId,
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
//
|
|
667
|
+
// Replies are CLI-only for every typed event (nothing
|
|
668
|
+
// auto-delivers). chatId — only approval_decided has one — merely
|
|
669
|
+
// routes deliver()'s suppressed turn-end audit step to the chat;
|
|
670
|
+
// chat-less routes carry just the envelopeId for the redundant
|
|
671
|
+
// ledger close + idle.
|
|
619
672
|
data: plan.chatId
|
|
620
673
|
? { chatId: plan.chatId, messageId: sourceId, envelopeId }
|
|
621
674
|
: { envelopeId },
|
|
@@ -765,6 +818,20 @@ async function handleParallAgentEventInner(event, ctx) {
|
|
|
765
818
|
if (!chatId)
|
|
766
819
|
return [];
|
|
767
820
|
if (event.type === 'turn_completed' || event.type === 'turn_failed') {
|
|
821
|
+
// Ledger close FIRST, fenced per source — deliberately NOT behind the
|
|
822
|
+
// agent-global marker guard below: that guard protects the shared
|
|
823
|
+
// session's idle (last-emit-wins), while the ledger rows are
|
|
824
|
+
// per-source facts that must close whenever THEIR envelope is the one
|
|
825
|
+
// this turn consumed (a concurrent conversation-B emit must not leave
|
|
826
|
+
// conversation-A's resolution hanging). The attempt fence inside
|
|
827
|
+
// completeFencedEnvelopes drops late events from a superseded attempt
|
|
828
|
+
// (the TTL re-emit took the source over) AND fork-handled envelopes
|
|
829
|
+
// (no fence written), so it can never close the in-flight retry's
|
|
830
|
+
// work nor double-close fork acks. F3-absorbed envelopes appear in
|
|
831
|
+
// the absorbing turn's envelopeIds and settle here naturally. The
|
|
832
|
+
// server decides reply broad-cover vs no_action per batch; turn_failed
|
|
833
|
+
// sweeps the same way with the error step below as the visible record.
|
|
834
|
+
await completeFencedEnvelopes(ctx, event.envelopeIds);
|
|
768
835
|
// Staleness guard: only idle if this event's turn covers the AGENT'S
|
|
769
836
|
// latest emitted envelope (the marker is agent-global — all chats share
|
|
770
837
|
// the one main session, so a done signal for chat A's turn must not
|
package/dist/session.d.ts
CHANGED
|
@@ -242,4 +242,71 @@ export declare function ackByIdEffect(ctx: ConnectorContext, dispatchId: string)
|
|
|
242
242
|
* means the ring never appears.
|
|
243
243
|
*/
|
|
244
244
|
export declare function markDispatchReceived(ctx: ConnectorContext, sourceType: string, sourceId: string): Promise<void>;
|
|
245
|
+
/**
|
|
246
|
+
* How long an emitted source may stay unresolved before the reconnect sweep
|
|
247
|
+
* re-emits it (crash / lost turn_completed self-heal — the successor of the
|
|
248
|
+
* retired turnwatch timer). Must comfortably exceed any legitimate turn.
|
|
249
|
+
*/
|
|
250
|
+
export declare const EMIT_TTL_MS: number;
|
|
251
|
+
/** The current emit attempt for a source: its envelope id + emit time. */
|
|
252
|
+
export type ActiveEmit = {
|
|
253
|
+
id: string;
|
|
254
|
+
at: number;
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Per-source attempt fence. Written before emit, cleared by the matching
|
|
258
|
+
* turn event's complete: a turn event only closes a source whose stored id
|
|
259
|
+
* EXACTLY matches one of its envelopeIds — a late turn_completed from a
|
|
260
|
+
* superseded attempt (the source was re-emitted after the TTL) must not
|
|
261
|
+
* close the in-flight retry's work (codex P2 attempt fence).
|
|
262
|
+
*/
|
|
263
|
+
export declare function readActiveEmit(ctx: ConnectorContext, sourceType: string, sourceId: string): Promise<ActiveEmit | null>;
|
|
264
|
+
export declare function writeActiveEmit(ctx: ConnectorContext, sourceType: string, sourceId: string, envelopeId: string): Promise<void>;
|
|
265
|
+
export declare function clearActiveEmit(ctx: ConnectorContext, sourceType: string, sourceId: string): Promise<void>;
|
|
266
|
+
/**
|
|
267
|
+
* Deterministic envelope id for a source: `{sourceType}:{sourceId}` on the
|
|
268
|
+
* first attempt, `…#r{n}` on re-emits. Attempt-suffixed ids are unique per
|
|
269
|
+
* attempt, so a re-emit is a NEW envelope to parel regardless of any dedupe
|
|
270
|
+
* behavior — the retry always produces a fresh turn.
|
|
271
|
+
*/
|
|
272
|
+
export declare function nextEnvelopeId(sourceType: string, sourceId: string, prior: ActiveEmit | null): string;
|
|
273
|
+
/** envelope id → source pointer (strips the attempt suffix). */
|
|
274
|
+
export declare function sourceFromEnvelopeId(envelopeId: string): {
|
|
275
|
+
sourceType: string;
|
|
276
|
+
sourceId: string;
|
|
277
|
+
} | null;
|
|
278
|
+
/**
|
|
279
|
+
* Fence-checked turn-end ledger close for a set of envelope ids — the shared
|
|
280
|
+
* core of BOTH turn-end hooks: onAgentEvent (turn_completed/turn_failed, the
|
|
281
|
+
* primary) and deliver (the no-observe npm-fallback path, where turn events
|
|
282
|
+
* never arrive and this is the ONLY terminal ledger hook — without it a
|
|
283
|
+
* received row would re-emit on every TTL sweep forever). Only sources whose
|
|
284
|
+
* `active:{source}` store row matches the envelope id EXACTLY are completed
|
|
285
|
+
* (attempt fence); the fence rows clear on success. Both hooks running is
|
|
286
|
+
* harmless: whichever completes first consumes the fence, the other skips.
|
|
287
|
+
*/
|
|
288
|
+
export declare function completeFencedEnvelopes(ctx: ConnectorContext, envelopeIds: string[]): Promise<void>;
|
|
289
|
+
/**
|
|
290
|
+
* Turn-end ledger close: resolves the turn's folded sources on the server —
|
|
291
|
+
* broad-cover to the turn's reply Effect when one exists, no_action sweep
|
|
292
|
+
* otherwise (the server decides; POST /dispatch/complete-sources, idempotent).
|
|
293
|
+
* One immediate retry on failure; a still-failed call leaves the rows
|
|
294
|
+
* received and the catch-up TTL re-emit self-heals.
|
|
295
|
+
*/
|
|
296
|
+
export declare function completeSources(ctx: ConnectorContext, sources: {
|
|
297
|
+
source_type: string;
|
|
298
|
+
source_id: string;
|
|
299
|
+
}[]): Promise<boolean>;
|
|
300
|
+
/**
|
|
301
|
+
* Turn-end plain text as a suppressed audit step — the runtime-standard
|
|
302
|
+
* Layer 0 posture (agent-dm-loop-prevention.md): recorded on the session,
|
|
303
|
+
* never delivered to the chat. No `projection` field, so the server's
|
|
304
|
+
* projection gate keeps it panel-only; `suppressed: true` is the content
|
|
305
|
+
* marker the standard bridges write for undelivered text.
|
|
306
|
+
*/
|
|
307
|
+
export declare function createSuppressedTextStep(ctx: ConnectorContext, sessionId: string, args: {
|
|
308
|
+
subject: string;
|
|
309
|
+
deliveryId: string;
|
|
310
|
+
text: string;
|
|
311
|
+
}): Promise<ReportResult>;
|
|
245
312
|
//# sourceMappingURL=session.d.ts.map
|
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":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAG3E;;;;;;;;;;;;;;;;GAgBG;AAEH,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAE/D;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,sBAAqC,CAAC;AACpE,eAAO,MAAM,iBAAiB,iBAAgC,CAAC;AAG/D;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOzD;AAED;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC;AAMrD,2EAA2E;AAC3E,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AASD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,gBAAgB,EACrB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyBxB;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmBzF;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkCxB;AAED,yEAAyE;AACzE,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,qEAAqE;AACrE,wBAAsB,YAAY,CAChC,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/D,OAAO,CAAC,YAAY,CAAC,CAgBvB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3E,OAAO,CAAC,YAAY,CAAC,CA8BvB;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IACJ,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,YAAY,CAAC,CAsCvB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IACJ,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB;;0EAEsE;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,YAAY,CAAC,CA4BvB;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,GAAE,MAAM,EAAO,GAAG,MAAM,CAY3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACvD,OAAO,CAAC,YAAY,CAAC,CAiCvB;AAOD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,EACD;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GACpF;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACJ,OAAO,CAAC,YAAY,CAAC,CAmEvB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAc7C;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAc7C;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAiBf;AASD;;;;GAIG;AACH,eAAO,MAAM,WAAW,QAAiB,CAAC;AAE1C,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAMpD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAW5B;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,UAAU,GAAG,IAAI,GACvB,MAAM,CAMR;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,GACjB;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKjD;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,gBAAgB,EACrB,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,GACpD,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,gBAAgB,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,YAAY,CAAC,CAyBvB"}
|
package/dist/session.js
CHANGED
|
@@ -573,8 +573,10 @@ export function ackByIdEffect(ctx, dispatchId) {
|
|
|
573
573
|
* means the ring never appears.
|
|
574
574
|
*/
|
|
575
575
|
export async function markDispatchReceived(ctx, sourceType, sourceId) {
|
|
576
|
-
|
|
577
|
-
|
|
576
|
+
// Deliberately NOT gated on reportingEnabled: this is ledger surface, not
|
|
577
|
+
// session reporting — the URL needs no agent id, and with ack-on-emit gone
|
|
578
|
+
// the received transition is what keeps the reconnect sweep's TTL window
|
|
579
|
+
// (and the indicator lifecycle) working even for agent-id-less old configs.
|
|
578
580
|
try {
|
|
579
581
|
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/dispatch/received`;
|
|
580
582
|
const res = await fetch(url, {
|
|
@@ -590,3 +592,165 @@ export async function markDispatchReceived(ctx, sourceType, sourceId) {
|
|
|
590
592
|
console.error('[parel-channel] mark received failed', err);
|
|
591
593
|
}
|
|
592
594
|
}
|
|
595
|
+
// ── By-source ledger surface ────────────────────────────
|
|
596
|
+
//
|
|
597
|
+
// The connector consumes dispatches without claiming lanes (parel's own
|
|
598
|
+
// queue serializes turns), so its ledger writes address WorkItems by source
|
|
599
|
+
// pointer, and its liveness bookkeeping is a per-source attempt fence in the
|
|
600
|
+
// durable store. Design: parel-reply-contract-alignment.md §5.3.
|
|
601
|
+
/**
|
|
602
|
+
* How long an emitted source may stay unresolved before the reconnect sweep
|
|
603
|
+
* re-emits it (crash / lost turn_completed self-heal — the successor of the
|
|
604
|
+
* retired turnwatch timer). Must comfortably exceed any legitimate turn.
|
|
605
|
+
*/
|
|
606
|
+
export const EMIT_TTL_MS = 30 * 60 * 1000;
|
|
607
|
+
function activeEmitKey(sourceType, sourceId) {
|
|
608
|
+
return `active:${sourceType}:${sourceId}`;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Per-source attempt fence. Written before emit, cleared by the matching
|
|
612
|
+
* turn event's complete: a turn event only closes a source whose stored id
|
|
613
|
+
* EXACTLY matches one of its envelopeIds — a late turn_completed from a
|
|
614
|
+
* superseded attempt (the source was re-emitted after the TTL) must not
|
|
615
|
+
* close the in-flight retry's work (codex P2 attempt fence).
|
|
616
|
+
*/
|
|
617
|
+
export async function readActiveEmit(ctx, sourceType, sourceId) {
|
|
618
|
+
const raw = await ctx.store?.get(activeEmitKey(sourceType, sourceId)).catch(() => null);
|
|
619
|
+
if (raw &&
|
|
620
|
+
typeof raw === 'object' &&
|
|
621
|
+
typeof raw.id === 'string' &&
|
|
622
|
+
typeof raw.at === 'number') {
|
|
623
|
+
return raw;
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
export async function writeActiveEmit(ctx, sourceType, sourceId, envelopeId) {
|
|
628
|
+
await ctx.store
|
|
629
|
+
?.set(activeEmitKey(sourceType, sourceId), { id: envelopeId, at: ctx.now() })
|
|
630
|
+
.catch(() => { });
|
|
631
|
+
}
|
|
632
|
+
export async function clearActiveEmit(ctx, sourceType, sourceId) {
|
|
633
|
+
await ctx.store?.delete(activeEmitKey(sourceType, sourceId)).catch(() => { });
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Deterministic envelope id for a source: `{sourceType}:{sourceId}` on the
|
|
637
|
+
* first attempt, `…#r{n}` on re-emits. Attempt-suffixed ids are unique per
|
|
638
|
+
* attempt, so a re-emit is a NEW envelope to parel regardless of any dedupe
|
|
639
|
+
* behavior — the retry always produces a fresh turn.
|
|
640
|
+
*/
|
|
641
|
+
export function nextEnvelopeId(sourceType, sourceId, prior) {
|
|
642
|
+
const base = `${sourceType}:${sourceId}`;
|
|
643
|
+
if (!prior)
|
|
644
|
+
return base;
|
|
645
|
+
const m = prior.id.match(/#r(\d+)$/);
|
|
646
|
+
const attempt = m ? Number(m[1]) + 1 : 1;
|
|
647
|
+
return `${base}#r${attempt}`;
|
|
648
|
+
}
|
|
649
|
+
/** envelope id → source pointer (strips the attempt suffix). */
|
|
650
|
+
export function sourceFromEnvelopeId(envelopeId) {
|
|
651
|
+
const bare = envelopeId.replace(/#r\d+$/, '');
|
|
652
|
+
const i = bare.indexOf(':');
|
|
653
|
+
if (i <= 0 || i === bare.length - 1)
|
|
654
|
+
return null;
|
|
655
|
+
return { sourceType: bare.slice(0, i), sourceId: bare.slice(i + 1) };
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Fence-checked turn-end ledger close for a set of envelope ids — the shared
|
|
659
|
+
* core of BOTH turn-end hooks: onAgentEvent (turn_completed/turn_failed, the
|
|
660
|
+
* primary) and deliver (the no-observe npm-fallback path, where turn events
|
|
661
|
+
* never arrive and this is the ONLY terminal ledger hook — without it a
|
|
662
|
+
* received row would re-emit on every TTL sweep forever). Only sources whose
|
|
663
|
+
* `active:{source}` store row matches the envelope id EXACTLY are completed
|
|
664
|
+
* (attempt fence); the fence rows clear on success. Both hooks running is
|
|
665
|
+
* harmless: whichever completes first consumes the fence, the other skips.
|
|
666
|
+
*/
|
|
667
|
+
export async function completeFencedEnvelopes(ctx, envelopeIds) {
|
|
668
|
+
const fenced = [];
|
|
669
|
+
for (const envId of envelopeIds) {
|
|
670
|
+
const src = sourceFromEnvelopeId(envId);
|
|
671
|
+
if (!src)
|
|
672
|
+
continue; // pre-alignment envelope id (dsp_*) — old rows were acked on emit
|
|
673
|
+
const active = await readActiveEmit(ctx, src.sourceType, src.sourceId);
|
|
674
|
+
if (active?.id === envId) {
|
|
675
|
+
fenced.push({ source_type: src.sourceType, source_id: src.sourceId });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (fenced.length > 0 && (await completeSources(ctx, fenced))) {
|
|
679
|
+
for (const src of fenced) {
|
|
680
|
+
await clearActiveEmit(ctx, src.source_type, src.source_id);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Turn-end ledger close: resolves the turn's folded sources on the server —
|
|
686
|
+
* broad-cover to the turn's reply Effect when one exists, no_action sweep
|
|
687
|
+
* otherwise (the server decides; POST /dispatch/complete-sources, idempotent).
|
|
688
|
+
* One immediate retry on failure; a still-failed call leaves the rows
|
|
689
|
+
* received and the catch-up TTL re-emit self-heals.
|
|
690
|
+
*/
|
|
691
|
+
export async function completeSources(ctx, sources) {
|
|
692
|
+
// Ledger surface — not gated on reportingEnabled (see markDispatchReceived).
|
|
693
|
+
if (sources.length === 0)
|
|
694
|
+
return true;
|
|
695
|
+
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/dispatch/complete-sources`;
|
|
696
|
+
const attempt = async () => {
|
|
697
|
+
const res = await fetch(url, {
|
|
698
|
+
method: 'POST',
|
|
699
|
+
headers: jsonHeaders(ctx),
|
|
700
|
+
body: JSON.stringify({ sources }),
|
|
701
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
702
|
+
});
|
|
703
|
+
if (!res.ok)
|
|
704
|
+
console.error(`[parel-channel] complete-sources failed (${res.status})`);
|
|
705
|
+
return res.ok;
|
|
706
|
+
};
|
|
707
|
+
try {
|
|
708
|
+
if (await attempt())
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
// fall through to the single immediate retry
|
|
713
|
+
}
|
|
714
|
+
try {
|
|
715
|
+
return await attempt();
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
console.error('[parel-channel] complete-sources failed', err);
|
|
719
|
+
return false;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Turn-end plain text as a suppressed audit step — the runtime-standard
|
|
724
|
+
* Layer 0 posture (agent-dm-loop-prevention.md): recorded on the session,
|
|
725
|
+
* never delivered to the chat. No `projection` field, so the server's
|
|
726
|
+
* projection gate keeps it panel-only; `suppressed: true` is the content
|
|
727
|
+
* marker the standard bridges write for undelivered text.
|
|
728
|
+
*/
|
|
729
|
+
export async function createSuppressedTextStep(ctx, sessionId, args) {
|
|
730
|
+
try {
|
|
731
|
+
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/agents/${parallAgentId(ctx)}/sessions/${sessionId}/steps`;
|
|
732
|
+
// Same subject-prefix line the other step writers draw: chv_ turns must
|
|
733
|
+
// not target 'chat' or the broadcast lands on a channel no client watches.
|
|
734
|
+
const targetType = args.subject.startsWith('chv_') ? 'channel_conversation' : 'chat';
|
|
735
|
+
const res = await fetch(url, {
|
|
736
|
+
method: 'POST',
|
|
737
|
+
headers: jsonHeaders(ctx),
|
|
738
|
+
body: JSON.stringify({
|
|
739
|
+
step_type: 'text',
|
|
740
|
+
target_type: targetType,
|
|
741
|
+
target_id: args.subject,
|
|
742
|
+
idempotency_key: `text:${args.deliveryId}`,
|
|
743
|
+
content: { text: args.text, suppressed: true },
|
|
744
|
+
}),
|
|
745
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
746
|
+
});
|
|
747
|
+
if (res.ok)
|
|
748
|
+
return 'ok';
|
|
749
|
+
console.error(`[parel-channel] suppressed text step failed (${res.status})`);
|
|
750
|
+
return resultForStatus(res.status);
|
|
751
|
+
}
|
|
752
|
+
catch (err) {
|
|
753
|
+
console.error('[parel-channel] suppressed text step failed', err);
|
|
754
|
+
return 'failed';
|
|
755
|
+
}
|
|
756
|
+
}
|
package/dist/typed-dispatch.d.ts
CHANGED
|
@@ -19,13 +19,15 @@ import type { ConnectorContext } from '@parel/plugin-sdk';
|
|
|
19
19
|
* attached_to_uri legitimately revokes access to a delivered run snapshot;
|
|
20
20
|
* docs/primitives/schedule.md § Snapshot 不变性) — warn + ack + drop.
|
|
21
21
|
*
|
|
22
|
-
* Reply semantics
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* the
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* Reply semantics are uniform with the rest of the connector: NOTHING
|
|
23
|
+
* auto-delivers (the CLI-only reply contract,
|
|
24
|
+
* parel-reply-contract-alignment.md). A typed turn's turn-end text is never
|
|
25
|
+
* posted anywhere; the agent acts through its sandbox CLI — identical to
|
|
26
|
+
* the standard runtimes, where typed events are answered with CLI actions.
|
|
27
|
+
* approval_decided differs only in ROUTING metadata: it lives in a chat, so
|
|
28
|
+
* its plan carries the chatId — that anchors the suppressed turn-end audit
|
|
29
|
+
* step and tells the agent where to `parall messages send`; the reply
|
|
30
|
+
* itself is still an explicit CLI send.
|
|
29
31
|
*/
|
|
30
32
|
export interface TypedDispatchData {
|
|
31
33
|
id?: unknown;
|
|
@@ -55,7 +57,11 @@ export interface TypedEventPlan {
|
|
|
55
57
|
/** Input-step content bits. */
|
|
56
58
|
triggerType: string;
|
|
57
59
|
summary: string;
|
|
58
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Set only for approval_decided: anchors the suppressed turn-end audit
|
|
62
|
+
* step and tells the agent where to `parall messages send` — replies are
|
|
63
|
+
* CLI-only, nothing auto-delivers.
|
|
64
|
+
*/
|
|
59
65
|
chatId?: string;
|
|
60
66
|
}
|
|
61
67
|
export type TypedDispatchResult = TypedEventPlan
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-dispatch.d.ts","sourceRoot":"","sources":["../src/typed-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAI1D
|
|
1
|
+
{"version":3,"file":"typed-dispatch.d.ts","sourceRoot":"","sources":["../src/typed-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAI1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,kIAQpB,CAAC;AAEX,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,mBAAmB,GAC3B,cAAc;AAChB,2CAA2C;GACzC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE;AACtB,gEAAgE;GAC9D;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,CAAC;AAY3B;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,iBAAiB,EACvB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,mBAAmB,CAAC,CAwC9B"}
|
package/dist/typed-dispatch.js
CHANGED
|
@@ -140,8 +140,9 @@ async function planTask(ctx, taskId, eventType, actor) {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
|
-
* schedule.fire / external_trigger turns have NO
|
|
144
|
-
*
|
|
143
|
+
* schedule.fire / external_trigger turns have NO conversation attached at
|
|
144
|
+
* all — plain text output is never delivered anywhere (the CLI-only reply
|
|
145
|
+
* contract), and unlike chat turns there is no obvious chat to send into,
|
|
145
146
|
* so without this line a normal completion silently disappears.
|
|
146
147
|
*/
|
|
147
148
|
function cliOnlyOutputHint(kind) {
|
|
@@ -334,7 +335,8 @@ async function planApprovalDecided(ctx, approvalId, chatId, actor) {
|
|
|
334
335
|
return {
|
|
335
336
|
outcome: 'emit',
|
|
336
337
|
// The decision belongs to the chat conversation it happened in — group
|
|
337
|
-
// and correlate there
|
|
338
|
+
// and correlate there; a follow-up reply is the agent's explicit CLI
|
|
339
|
+
// send into that chat (turn-end text never auto-delivers).
|
|
338
340
|
subject: resolvedChat ?? approval.id,
|
|
339
341
|
body: lines.join('\n'),
|
|
340
342
|
stepTarget: resolvedChat ? { type: 'chat', id: resolvedChat } : null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/parel-channel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.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/channel-prompt.ts
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* chat shares the agent's ONE parel session, so consecutive turns can come
|
|
6
6
|
* from different conversations — each message is framed with its originating
|
|
7
7
|
* chat so the model registers the context switch instead of answering chat B
|
|
8
|
-
* with chat A's thread of thought. No reply hint: the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* with chat A's thread of thought. No per-message reply hint: the system
|
|
9
|
+
* prompt teaches the CLI-only reply contract once (SSOT — plain text is
|
|
10
|
+
* never delivered; reply via `parall messages send`), and per-turn
|
|
11
|
+
* invocation context presets PRLL_CHAT_ID so the bare send hits this chat.
|
|
11
12
|
*
|
|
12
13
|
* External IM channel messages (buildChannelPrompt, channel_message
|
|
13
14
|
* dispatches). Mirrors agent-core's event-format.ts channel branch, adapted
|