@parall/parel-channel 1.42.1 → 1.44.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-capability.d.ts +29 -0
- package/dist/channel-capability.d.ts.map +1 -0
- package/dist/channel-capability.js +81 -0
- package/dist/channel-prompt.d.ts +16 -9
- package/dist/channel-prompt.d.ts.map +1 -1
- package/dist/channel-prompt.js +25 -19
- package/dist/delivery.js +1 -1
- package/dist/fork.d.ts +8 -4
- package/dist/fork.d.ts.map +1 -1
- package/dist/inbound-channel.d.ts.map +1 -1
- package/dist/inbound-channel.js +19 -14
- package/dist/inbound.d.ts.map +1 -1
- package/dist/inbound.js +65 -43
- package/dist/session.d.ts +43 -19
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +75 -36
- package/package.json +2 -2
- package/src/channel-capability.ts +121 -0
- package/src/channel-prompt.ts +33 -20
- package/src/delivery.ts +1 -1
- package/src/fork.ts +6 -7
- package/src/inbound-channel.ts +21 -15
- package/src/inbound.ts +75 -52
- package/src/session.ts +81 -34
- package/dist/parel-sdk-compat.d.ts +0 -63
- package/dist/parel-sdk-compat.d.ts.map +0 -1
- package/dist/parel-sdk-compat.js +0 -16
- package/src/parel-sdk-compat.ts +0 -77
package/dist/inbound.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { channelCapabilityEnv } from './channel-capability.js';
|
|
1
2
|
import { buildChatPrompt } from './channel-prompt.js';
|
|
2
3
|
import { parallApiUrl, parallOrgId, requireAgk, sandboxEnabled } from './connect.js';
|
|
3
4
|
import { effectsForChannelDispatch, PROVIDER_LOOKUP_TIMEOUT_MS } from './inbound-channel.js';
|
|
4
5
|
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
|
-
import {
|
|
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';
|
|
6
|
+
import { ackByIdEffect, claimDispatchSource, completeFencedEnvelopes, completeSourceEffect, conversationKey, createErrorStep, fetchMainSessionFresh, createInputStep, createTraceStep, createTypedInputStep, EMIT_TTL_MS, ensureChildSession, ensureSession, invalidateChildSession, invalidateSession, 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
|
|
@@ -274,7 +274,7 @@ async function effectsForDispatch(data, ctx) {
|
|
|
274
274
|
return [];
|
|
275
275
|
if (!chatId) {
|
|
276
276
|
// A message dispatch with no chat can never be routed or replied to.
|
|
277
|
-
return [
|
|
277
|
+
return [completeSourceEffect(ctx, sourceType, sourceId)];
|
|
278
278
|
}
|
|
279
279
|
if (await shouldSkipLiveRow(ctx, sourceType, sourceId))
|
|
280
280
|
return [];
|
|
@@ -289,7 +289,7 @@ async function effectsForDispatch(data, ctx) {
|
|
|
289
289
|
if (!m.ok) {
|
|
290
290
|
if (m.status === 404 || m.status === 403) {
|
|
291
291
|
// Deleted message / lost chat access — permanently unusable, drop it.
|
|
292
|
-
return [
|
|
292
|
+
return [completeSourceEffect(ctx, sourceType, sourceId)];
|
|
293
293
|
}
|
|
294
294
|
// Transient failure: keep pending, the catch-up sweep retries.
|
|
295
295
|
return [];
|
|
@@ -312,7 +312,7 @@ async function effectsForDispatch(data, ctx) {
|
|
|
312
312
|
// Truly empty messages never grow content — ack-and-drop rather than let
|
|
313
313
|
// them pile up in the pending queue forever. (Attachment-only messages
|
|
314
314
|
// used to take this path; they now ride the attachment lines instead.)
|
|
315
|
-
return [
|
|
315
|
+
return [completeSourceEffect(ctx, sourceType, sourceId)];
|
|
316
316
|
}
|
|
317
317
|
// Frame the message with its chat of origin: all chats share the agent's
|
|
318
318
|
// one main session, so the model must see WHERE each message comes from or
|
|
@@ -356,12 +356,18 @@ async function effectsForDispatch(data, ctx) {
|
|
|
356
356
|
// so the server resolves this WorkItem in the reply's own transaction.
|
|
357
357
|
// (Fork turns get it too — their rows are legacy-acked so the resolve is a
|
|
358
358
|
// no-op today, and it wires up for free when fork ledger parity lands.)
|
|
359
|
+
// Channel-capability env (lark-cli auth) rides EVERY turn's invocation
|
|
360
|
+
// context while granted — Feishu workspace ops (docs/calendar/…) are asked
|
|
361
|
+
// for from ordinary Parall chats, not only from channel turns. Null when
|
|
362
|
+
// ungranted or the mint degrades; never blocks the emit.
|
|
363
|
+
const capabilityEnv = await channelCapabilityEnv(ctx);
|
|
359
364
|
const invocationContext = {
|
|
360
365
|
PRLL_CHAT_ID: chatId,
|
|
361
366
|
PRLL_TRIGGER_MESSAGE_ID: sourceId,
|
|
362
367
|
PRLL_DISPATCH_SOURCE_TYPE: sourceType,
|
|
363
368
|
PRLL_DISPATCH_SOURCE_ID: sourceId,
|
|
364
369
|
...(threadRootId ? { PRLL_THREAD_ROOT_ID: threadRootId } : {}),
|
|
370
|
+
...(capabilityEnv ?? {}),
|
|
365
371
|
};
|
|
366
372
|
const envelope = {
|
|
367
373
|
id: envelopeId,
|
|
@@ -394,7 +400,7 @@ async function effectsForDispatch(data, ctx) {
|
|
|
394
400
|
// Progress reporting lands on the CHILD's ase_ — its turn does the work,
|
|
395
401
|
// and the replyRoute carries childRef so deliver() links the reply to the
|
|
396
402
|
// child session (and idles IT, not main).
|
|
397
|
-
const receivedSettled =
|
|
403
|
+
const receivedSettled = claimDispatchSource(ctx, sourceType, sourceId);
|
|
398
404
|
const childAse = await ensureChildSession(ctx, decision.childRef);
|
|
399
405
|
if (childAse) {
|
|
400
406
|
const results = await Promise.all([
|
|
@@ -445,18 +451,18 @@ async function effectsForDispatch(data, ctx) {
|
|
|
445
451
|
source_id: sourceId,
|
|
446
452
|
chat_id: chatId,
|
|
447
453
|
});
|
|
448
|
-
return
|
|
449
|
-
{ type: 'setTimer', key: retryKey, at: ctx.now() + FORK_RETRY_DELAY_MS },
|
|
450
|
-
]);
|
|
454
|
+
return [{ type: 'setTimer', key: retryKey, at: ctx.now() + FORK_RETRY_DELAY_MS }];
|
|
451
455
|
}
|
|
452
|
-
return
|
|
456
|
+
return [
|
|
453
457
|
{ type: 'emitEvent', event: childEnvelope, deliverTo: { childRef: decision.childRef } },
|
|
454
|
-
]
|
|
458
|
+
];
|
|
455
459
|
}
|
|
456
|
-
return
|
|
460
|
+
return [
|
|
457
461
|
{ type: 'emitEvent', event: childEnvelope, deliverTo: { childRef: decision.childRef } },
|
|
458
|
-
|
|
459
|
-
|
|
462
|
+
// Fork-handled rows keep the legacy ack machinery (ledger parity is a
|
|
463
|
+
// declared follow-up); by id — the precise form.
|
|
464
|
+
dispatchId ? ackByIdEffect(ctx, dispatchId) : completeSourceEffect(ctx, sourceType, sourceId),
|
|
465
|
+
];
|
|
460
466
|
}
|
|
461
467
|
if (decision.mode === 'spawn') {
|
|
462
468
|
// Main is mid-turn on another conversation — fork. The opening message
|
|
@@ -502,7 +508,7 @@ async function effectsForDispatch(data, ctx) {
|
|
|
502
508
|
}
|
|
503
509
|
}
|
|
504
510
|
if (!spawnBlackHoled) {
|
|
505
|
-
const receivedSettled =
|
|
511
|
+
const receivedSettled = claimDispatchSource(ctx, sourceType, sourceId);
|
|
506
512
|
const childAse = await ensureChildSession(ctx, decision.childRef);
|
|
507
513
|
if (childAse) {
|
|
508
514
|
const results = await Promise.all([
|
|
@@ -519,7 +525,14 @@ async function effectsForDispatch(data, ctx) {
|
|
|
519
525
|
}
|
|
520
526
|
await recordSpawn(ctx, subject, decision.childRef, { dispatchId, sourceId });
|
|
521
527
|
await receivedSettled;
|
|
522
|
-
|
|
528
|
+
// Known gap: SpawnChildSessionEffect (#140) carries no invocation
|
|
529
|
+
// context, so the fork's OPENING turn runs without the capability env
|
|
530
|
+
// (lark-cli workspace ops would need a follow-up turn — those arrive
|
|
531
|
+
// as deliverTo envelopes, which inherit the full context). Channel
|
|
532
|
+
// replies are unaffected: channel_message dispatches always ride main.
|
|
533
|
+
// Fix belongs parel-side (context on spawnChildSession) — tracked as a
|
|
534
|
+
// parel ask.
|
|
535
|
+
return [
|
|
523
536
|
{
|
|
524
537
|
type: 'spawnChildSession',
|
|
525
538
|
childRef: decision.childRef,
|
|
@@ -527,7 +540,7 @@ async function effectsForDispatch(data, ctx) {
|
|
|
527
540
|
subject,
|
|
528
541
|
},
|
|
529
542
|
...timerEffects,
|
|
530
|
-
]
|
|
543
|
+
];
|
|
531
544
|
}
|
|
532
545
|
// fall through: black-holed spawn rides main below.
|
|
533
546
|
}
|
|
@@ -541,7 +554,7 @@ async function effectsForDispatch(data, ctx) {
|
|
|
541
554
|
// pending→received transition. It is deliberately NOT gated on the
|
|
542
555
|
// session resolving: a transient session failure must not also cost the
|
|
543
556
|
// indicator.
|
|
544
|
-
const receivedSettled =
|
|
557
|
+
const receivedSettled = claimDispatchSource(ctx, sourceType, sourceId);
|
|
545
558
|
let sessionId = await ensureSession(ctx);
|
|
546
559
|
if (sessionId) {
|
|
547
560
|
// The guard marker goes down BEFORE any session write: a stale
|
|
@@ -604,7 +617,7 @@ async function effectsForDispatch(data, ctx) {
|
|
|
604
617
|
* them on the orchestrator preserves task/schedule context continuity.
|
|
605
618
|
*/
|
|
606
619
|
async function effectsForTypedDispatch(data, ctx) {
|
|
607
|
-
// No
|
|
620
|
+
// No claimDispatchSource here ON PURPOSE: typed dispatches have no
|
|
608
621
|
// message-level activity indicator (DispatchResolvedData: "typed
|
|
609
622
|
// dispatches have no indicator"), so there is no pending ring to light —
|
|
610
623
|
// the typed input step below is the session panel's timeline entry.
|
|
@@ -614,12 +627,21 @@ async function effectsForTypedDispatch(data, ctx) {
|
|
|
614
627
|
// Typed WorkItems ack by id, never by source: sibling rows (a task
|
|
615
628
|
// mutation's task_assign + task_update) share (source_type, source_id),
|
|
616
629
|
// and the source ack would terminally resolve work that was never emitted.
|
|
617
|
-
const ack = () =>
|
|
630
|
+
const ack = () => {
|
|
631
|
+
if (!dispatchId) {
|
|
632
|
+
// No WorkItem id (malformed payload): the by-source form would
|
|
633
|
+
// terminally resolve SIBLING typed rows sharing this source — leave
|
|
634
|
+
// the row pending for the sweep instead.
|
|
635
|
+
console.error(`[parel-channel] typed dispatch without id (${sourceType}:${sourceId}) left pending`);
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
return [ackByIdEffect(ctx, dispatchId)];
|
|
639
|
+
};
|
|
618
640
|
const plan = await planTypedDispatch(data, ctx);
|
|
619
641
|
if (plan.outcome === 'pending')
|
|
620
642
|
return [];
|
|
621
643
|
if (plan.outcome === 'stale')
|
|
622
|
-
return
|
|
644
|
+
return ack();
|
|
623
645
|
const envelopeId = dispatchId || `${sourceType}:${sourceId}`;
|
|
624
646
|
let sessionId = await ensureSession(ctx);
|
|
625
647
|
if (sessionId) {
|
|
@@ -647,6 +669,15 @@ async function effectsForTypedDispatch(data, ctx) {
|
|
|
647
669
|
}
|
|
648
670
|
}
|
|
649
671
|
}
|
|
672
|
+
// Typed turns carry the capability env too: a task or schedule is exactly
|
|
673
|
+
// where "create the Feishu doc / check the calendar" work is assigned, so
|
|
674
|
+
// the every-turn capability contract covers them (null when ungranted or
|
|
675
|
+
// the mint degrades; never blocks the emit).
|
|
676
|
+
const capabilityEnv = await channelCapabilityEnv(ctx);
|
|
677
|
+
const typedContext = {
|
|
678
|
+
...(plan.chatId ? { PRLL_CHAT_ID: plan.chatId, PRLL_TRIGGER_MESSAGE_ID: sourceId } : {}),
|
|
679
|
+
...(capabilityEnv ?? {}),
|
|
680
|
+
};
|
|
650
681
|
return [
|
|
651
682
|
{
|
|
652
683
|
type: 'emitEvent',
|
|
@@ -673,17 +704,10 @@ async function effectsForTypedDispatch(data, ctx) {
|
|
|
673
704
|
? { chatId: plan.chatId, messageId: sourceId, envelopeId }
|
|
674
705
|
: { envelopeId },
|
|
675
706
|
},
|
|
676
|
-
...(
|
|
677
|
-
? {
|
|
678
|
-
context: {
|
|
679
|
-
PRLL_CHAT_ID: plan.chatId,
|
|
680
|
-
PRLL_TRIGGER_MESSAGE_ID: sourceId,
|
|
681
|
-
},
|
|
682
|
-
}
|
|
683
|
-
: {}),
|
|
707
|
+
...(Object.keys(typedContext).length > 0 ? { context: typedContext } : {}),
|
|
684
708
|
},
|
|
685
709
|
},
|
|
686
|
-
ack(),
|
|
710
|
+
...ack(),
|
|
687
711
|
];
|
|
688
712
|
}
|
|
689
713
|
/**
|
|
@@ -765,22 +789,21 @@ export function handleParallAgentEvent(event, ctx) {
|
|
|
765
789
|
return serialized(() => handleParallAgentEventInner(event, ctx));
|
|
766
790
|
}
|
|
767
791
|
async function handleParallAgentEventInner(event, ctx) {
|
|
768
|
-
// spawnChildSession's asynchronous error channel — not turn-scoped
|
|
769
|
-
// regardless of observe scopes
|
|
770
|
-
//
|
|
771
|
-
|
|
772
|
-
if (spawnFailed) {
|
|
792
|
+
// spawnChildSession's asynchronous error channel — not turn-scoped and
|
|
793
|
+
// pushed regardless of observe scopes. Handle it before anything
|
|
794
|
+
// subject-gated.
|
|
795
|
+
if (event.type === 'child_spawn_failed') {
|
|
773
796
|
const effects = [];
|
|
774
797
|
try {
|
|
775
|
-
const outcome = await handleChildSpawnFailed(ctx,
|
|
798
|
+
const outcome = await handleChildSpawnFailed(ctx, event);
|
|
776
799
|
if (outcome.cleared) {
|
|
777
800
|
// The spawn's ase_ was already created active with an input step; no
|
|
778
801
|
// child turn will ever emit a terminal event for it — settle it here
|
|
779
802
|
// or the session (and its processing indicator) stays active forever.
|
|
780
|
-
const orphanAse = await ensureChildSession(ctx,
|
|
803
|
+
const orphanAse = await ensureChildSession(ctx, event.childRef);
|
|
781
804
|
if (orphanAse)
|
|
782
805
|
await patchSession(ctx, orphanAse, { status: 'idle' });
|
|
783
|
-
await invalidateChildSession(ctx,
|
|
806
|
+
await invalidateChildSession(ctx, event.childRef);
|
|
784
807
|
}
|
|
785
808
|
// Re-drive the stranded messages WITHOUT waiting for a reconnect sweep
|
|
786
809
|
// (a healthy long-lived socket never re-runs onOpen): park their
|
|
@@ -806,15 +829,14 @@ async function handleParallAgentEventInner(event, ctx) {
|
|
|
806
829
|
}
|
|
807
830
|
return effects;
|
|
808
831
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
return handleChildAgentEvent(withChild, ctx);
|
|
832
|
+
if (event.childRef) {
|
|
833
|
+
return handleChildAgentEvent(event, ctx);
|
|
812
834
|
}
|
|
813
835
|
const chatId = event.subject;
|
|
814
836
|
try {
|
|
815
837
|
// Best-effort busy classifier for fork-on-busy: only MAIN turns feed it
|
|
816
838
|
// (child events took the branch above).
|
|
817
|
-
await noteMainTurnEvent(ctx,
|
|
839
|
+
await noteMainTurnEvent(ctx, event).catch(() => { });
|
|
818
840
|
if (!chatId)
|
|
819
841
|
return [];
|
|
820
842
|
if (event.type === 'turn_completed' || event.type === 'turn_failed') {
|
|
@@ -831,7 +853,7 @@ async function handleParallAgentEventInner(event, ctx) {
|
|
|
831
853
|
// the absorbing turn's envelopeIds and settle here naturally. The
|
|
832
854
|
// server decides reply broad-cover vs no_action per batch; turn_failed
|
|
833
855
|
// sweeps the same way with the error step below as the visible record.
|
|
834
|
-
await completeFencedEnvelopes(ctx, event.envelopeIds);
|
|
856
|
+
await completeFencedEnvelopes(ctx, event.envelopeIds, event.type === 'turn_failed' ? 'error' : 'ok');
|
|
835
857
|
// Staleness guard: only idle if this event's turn covers the AGENT'S
|
|
836
858
|
// latest emitted envelope (the marker is agent-global — all chats share
|
|
837
859
|
// the one main session, so a done signal for chat A's turn must not
|
package/dist/session.d.ts
CHANGED
|
@@ -216,12 +216,14 @@ export declare function createTraceStep(ctx: ConnectorContext, sessionId: string
|
|
|
216
216
|
outputPreview?: string;
|
|
217
217
|
}): Promise<ReportResult>;
|
|
218
218
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
219
|
+
* Terminally drop ONE source's live WorkItem via the unified complete's
|
|
220
|
+
* source form (no covering reply Effect exists for a dropped row, so the
|
|
221
|
+
* server sweeps it to no_action — the drop semantics; idempotent). Typed as
|
|
222
|
+
* the fetch effect so it can ride BOTH hook channels (ConnectorEffect[] and
|
|
223
|
+
* AgentEventEffect[]). Replaces the legacy by-source ack for drop cases —
|
|
224
|
+
* that endpoint retires at S3 (dispatch-convergence-design.md §3).
|
|
223
225
|
*/
|
|
224
|
-
export declare function
|
|
226
|
+
export declare function completeSourceEffect(ctx: ConnectorContext, sourceType: string, sourceId: string): Extract<ConnectorEffect, {
|
|
225
227
|
type: 'fetch';
|
|
226
228
|
}>;
|
|
227
229
|
/**
|
|
@@ -236,12 +238,16 @@ export declare function ackByIdEffect(ctx: ConnectorContext, dispatchId: string)
|
|
|
236
238
|
type: 'fetch';
|
|
237
239
|
}>;
|
|
238
240
|
/**
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
241
|
+
* Claim the source's WorkItem before the turn (server folds the row into its
|
|
242
|
+
* message lane, flips it pending→received and broadcasts dispatch.received —
|
|
243
|
+
* the pending ring rides the claim side effect now; the retired received
|
|
244
|
+
* endpoint was a bare receipt). Best-effort like the receipt was: a refused
|
|
245
|
+
* or failed claim never blocks the emit — the row stays pending and the
|
|
246
|
+
* turn's complete resolves it just the same. Must COMPLETE before any
|
|
247
|
+
* settling effect is returned so a same-batch complete can't race the
|
|
248
|
+
* pending→received transition.
|
|
243
249
|
*/
|
|
244
|
-
export declare function
|
|
250
|
+
export declare function claimDispatchSource(ctx: ConnectorContext, sourceType: string, sourceId: string): Promise<void>;
|
|
245
251
|
/**
|
|
246
252
|
* How long an emitted source may stay unresolved before the reconnect sweep
|
|
247
253
|
* re-emits it (crash / lost turn_completed self-heal — the successor of the
|
|
@@ -263,6 +269,14 @@ export type ActiveEmit = {
|
|
|
263
269
|
export declare function readActiveEmit(ctx: ConnectorContext, sourceType: string, sourceId: string): Promise<ActiveEmit | null>;
|
|
264
270
|
export declare function writeActiveEmit(ctx: ConnectorContext, sourceType: string, sourceId: string, envelopeId: string): Promise<void>;
|
|
265
271
|
export declare function clearActiveEmit(ctx: ConnectorContext, sourceType: string, sourceId: string): Promise<void>;
|
|
272
|
+
/**
|
|
273
|
+
* Burn the fence after an ERROR close: keep the attempt id (so the re-driven
|
|
274
|
+
* emit's nextEnvelopeId picks a fresh `#r{n+1}` — a bare repeat of the base
|
|
275
|
+
* id could be deduped by parel and strand the retry) but zero the timestamp
|
|
276
|
+
* (so the redelivery-skip window is already over and the server's re-drive
|
|
277
|
+
* hint re-emits immediately instead of hiding behind a 30-min TTL).
|
|
278
|
+
*/
|
|
279
|
+
export declare function burnActiveEmit(ctx: ConnectorContext, sourceType: string, sourceId: string): Promise<void>;
|
|
266
280
|
/**
|
|
267
281
|
* Deterministic envelope id for a source: `{sourceType}:{sourceId}` on the
|
|
268
282
|
* first attempt, `…#r{n}` on re-emits. Attempt-suffixed ids are unique per
|
|
@@ -275,6 +289,8 @@ export declare function sourceFromEnvelopeId(envelopeId: string): {
|
|
|
275
289
|
sourceType: string;
|
|
276
290
|
sourceId: string;
|
|
277
291
|
} | null;
|
|
292
|
+
/** Turn outcome for the ledger close — mirrors the server's turn_outcome. */
|
|
293
|
+
export type TurnOutcome = 'ok' | 'error';
|
|
278
294
|
/**
|
|
279
295
|
* Fence-checked turn-end ledger close for a set of envelope ids — the shared
|
|
280
296
|
* core of BOTH turn-end hooks: onAgentEvent (turn_completed/turn_failed, the
|
|
@@ -282,21 +298,29 @@ export declare function sourceFromEnvelopeId(envelopeId: string): {
|
|
|
282
298
|
* never arrive and this is the ONLY terminal ledger hook — without it a
|
|
283
299
|
* received row would re-emit on every TTL sweep forever). Only sources whose
|
|
284
300
|
* `active:{source}` store row matches the envelope id EXACTLY are completed
|
|
285
|
-
* (attempt fence)
|
|
286
|
-
*
|
|
301
|
+
* (attempt fence). Both hooks running is harmless: whichever completes first
|
|
302
|
+
* consumes the fence, the other skips.
|
|
303
|
+
*
|
|
304
|
+
* Fence disposal follows the outcome: an "ok" close clears the fences (the
|
|
305
|
+
* sources are terminal); an "error" close BURNS them instead — the server
|
|
306
|
+
* released the uncovered members back to pending and its re-drive hint
|
|
307
|
+
* re-emits them, so the burned fence must neither hide the retry behind the
|
|
308
|
+
* redelivery-skip TTL nor let it reuse a dedupe-prone envelope id.
|
|
287
309
|
*/
|
|
288
|
-
export declare function completeFencedEnvelopes(ctx: ConnectorContext, envelopeIds: string[]): Promise<void>;
|
|
310
|
+
export declare function completeFencedEnvelopes(ctx: ConnectorContext, envelopeIds: string[], outcome?: TurnOutcome): Promise<void>;
|
|
289
311
|
/**
|
|
290
|
-
* Turn-end ledger close: resolves the turn's folded sources on the server
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
312
|
+
* Turn-end ledger close: resolves the turn's folded sources on the server via
|
|
313
|
+
* the unified complete's source form (POST /dispatch/complete, idempotent) —
|
|
314
|
+
* broad-cover to the turn's reply Effect when one exists; otherwise a
|
|
315
|
+
* no_action sweep on "ok", or a release back to pending on the redrive budget
|
|
316
|
+
* on "error" (a failed turn must not silently consume work it never handled).
|
|
317
|
+
* One immediate retry on failure; a still-failed call leaves the rows live
|
|
318
|
+
* and the catch-up TTL re-emit self-heals.
|
|
295
319
|
*/
|
|
296
320
|
export declare function completeSources(ctx: ConnectorContext, sources: {
|
|
297
321
|
source_type: string;
|
|
298
322
|
source_id: string;
|
|
299
|
-
}[]): Promise<boolean>;
|
|
323
|
+
}[], outcome?: TurnOutcome): Promise<boolean>;
|
|
300
324
|
/**
|
|
301
325
|
* Turn-end plain text as a suppressed audit step — the runtime-standard
|
|
302
326
|
* Layer 0 posture (agent-dm-loop-prevention.md): recorded on the session,
|
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
|
@@ -523,24 +523,26 @@ export async function createTraceStep(ctx, sessionId, subject, event) {
|
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
525
|
/**
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
526
|
+
* Terminally drop ONE source's live WorkItem via the unified complete's
|
|
527
|
+
* source form (no covering reply Effect exists for a dropped row, so the
|
|
528
|
+
* server sweeps it to no_action — the drop semantics; idempotent). Typed as
|
|
529
|
+
* the fetch effect so it can ride BOTH hook channels (ConnectorEffect[] and
|
|
530
|
+
* AgentEventEffect[]). Replaces the legacy by-source ack for drop cases —
|
|
531
|
+
* that endpoint retires at S3 (dispatch-convergence-design.md §3).
|
|
530
532
|
*/
|
|
531
|
-
export function
|
|
533
|
+
export function completeSourceEffect(ctx, sourceType, sourceId) {
|
|
532
534
|
return {
|
|
533
535
|
type: 'fetch',
|
|
534
536
|
request: {
|
|
535
|
-
url: `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/dispatch/
|
|
537
|
+
url: `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/dispatch/complete`,
|
|
536
538
|
method: 'POST',
|
|
537
539
|
headers: {
|
|
538
540
|
Authorization: `Bearer ${requireAgk(ctx)}`,
|
|
539
541
|
'Content-Type': 'application/json',
|
|
540
542
|
},
|
|
541
|
-
body: JSON.stringify({ source_type: sourceType, source_id: sourceId }),
|
|
543
|
+
body: JSON.stringify({ sources: [{ source_type: sourceType, source_id: sourceId }] }),
|
|
542
544
|
},
|
|
543
|
-
idempotencyKey: `
|
|
545
|
+
idempotencyKey: `complete:src:${sourceType}:${sourceId}`,
|
|
544
546
|
};
|
|
545
547
|
}
|
|
546
548
|
/**
|
|
@@ -567,18 +569,22 @@ export function ackByIdEffect(ctx, dispatchId) {
|
|
|
567
569
|
};
|
|
568
570
|
}
|
|
569
571
|
/**
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
572
|
+
* Claim the source's WorkItem before the turn (server folds the row into its
|
|
573
|
+
* message lane, flips it pending→received and broadcasts dispatch.received —
|
|
574
|
+
* the pending ring rides the claim side effect now; the retired received
|
|
575
|
+
* endpoint was a bare receipt). Best-effort like the receipt was: a refused
|
|
576
|
+
* or failed claim never blocks the emit — the row stays pending and the
|
|
577
|
+
* turn's complete resolves it just the same. Must COMPLETE before any
|
|
578
|
+
* settling effect is returned so a same-batch complete can't race the
|
|
579
|
+
* pending→received transition.
|
|
574
580
|
*/
|
|
575
|
-
export async function
|
|
581
|
+
export async function claimDispatchSource(ctx, sourceType, sourceId) {
|
|
576
582
|
// Deliberately NOT gated on reportingEnabled: this is ledger surface, not
|
|
577
|
-
// session reporting — the URL needs no agent id, and
|
|
578
|
-
//
|
|
579
|
-
//
|
|
583
|
+
// session reporting — the URL needs no agent id, and the claim transition
|
|
584
|
+
// is what keeps the indicator lifecycle working even for agent-id-less old
|
|
585
|
+
// configs.
|
|
580
586
|
try {
|
|
581
|
-
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/dispatch/
|
|
587
|
+
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/dispatch/claim`;
|
|
582
588
|
const res = await fetch(url, {
|
|
583
589
|
method: 'POST',
|
|
584
590
|
headers: jsonHeaders(ctx),
|
|
@@ -586,10 +592,10 @@ export async function markDispatchReceived(ctx, sourceType, sourceId) {
|
|
|
586
592
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
587
593
|
});
|
|
588
594
|
if (!res.ok)
|
|
589
|
-
console.error(`[parel-channel]
|
|
595
|
+
console.error(`[parel-channel] claim source failed (${res.status})`);
|
|
590
596
|
}
|
|
591
597
|
catch (err) {
|
|
592
|
-
console.error('[parel-channel]
|
|
598
|
+
console.error('[parel-channel] claim source failed', err);
|
|
593
599
|
}
|
|
594
600
|
}
|
|
595
601
|
// ── By-source ledger surface ────────────────────────────
|
|
@@ -632,6 +638,21 @@ export async function writeActiveEmit(ctx, sourceType, sourceId, envelopeId) {
|
|
|
632
638
|
export async function clearActiveEmit(ctx, sourceType, sourceId) {
|
|
633
639
|
await ctx.store?.delete(activeEmitKey(sourceType, sourceId)).catch(() => { });
|
|
634
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* Burn the fence after an ERROR close: keep the attempt id (so the re-driven
|
|
643
|
+
* emit's nextEnvelopeId picks a fresh `#r{n+1}` — a bare repeat of the base
|
|
644
|
+
* id could be deduped by parel and strand the retry) but zero the timestamp
|
|
645
|
+
* (so the redelivery-skip window is already over and the server's re-drive
|
|
646
|
+
* hint re-emits immediately instead of hiding behind a 30-min TTL).
|
|
647
|
+
*/
|
|
648
|
+
export async function burnActiveEmit(ctx, sourceType, sourceId) {
|
|
649
|
+
const prior = await readActiveEmit(ctx, sourceType, sourceId);
|
|
650
|
+
if (!prior)
|
|
651
|
+
return;
|
|
652
|
+
await ctx.store
|
|
653
|
+
?.set(activeEmitKey(sourceType, sourceId), { id: prior.id, at: 0 })
|
|
654
|
+
.catch(() => { });
|
|
655
|
+
}
|
|
635
656
|
/**
|
|
636
657
|
* Deterministic envelope id for a source: `{sourceType}:{sourceId}` on the
|
|
637
658
|
* first attempt, `…#r{n}` on re-emits. Attempt-suffixed ids are unique per
|
|
@@ -661,47 +682,65 @@ export function sourceFromEnvelopeId(envelopeId) {
|
|
|
661
682
|
* never arrive and this is the ONLY terminal ledger hook — without it a
|
|
662
683
|
* received row would re-emit on every TTL sweep forever). Only sources whose
|
|
663
684
|
* `active:{source}` store row matches the envelope id EXACTLY are completed
|
|
664
|
-
* (attempt fence)
|
|
665
|
-
*
|
|
685
|
+
* (attempt fence). Both hooks running is harmless: whichever completes first
|
|
686
|
+
* consumes the fence, the other skips.
|
|
687
|
+
*
|
|
688
|
+
* Fence disposal follows the outcome: an "ok" close clears the fences (the
|
|
689
|
+
* sources are terminal); an "error" close BURNS them instead — the server
|
|
690
|
+
* released the uncovered members back to pending and its re-drive hint
|
|
691
|
+
* re-emits them, so the burned fence must neither hide the retry behind the
|
|
692
|
+
* redelivery-skip TTL nor let it reuse a dedupe-prone envelope id.
|
|
666
693
|
*/
|
|
667
|
-
export async function completeFencedEnvelopes(ctx, envelopeIds) {
|
|
694
|
+
export async function completeFencedEnvelopes(ctx, envelopeIds, outcome = 'ok') {
|
|
668
695
|
const fenced = [];
|
|
669
696
|
for (const envId of envelopeIds) {
|
|
670
697
|
const src = sourceFromEnvelopeId(envId);
|
|
671
698
|
if (!src)
|
|
672
699
|
continue; // pre-alignment envelope id (dsp_*) — old rows were acked on emit
|
|
673
700
|
const active = await readActiveEmit(ctx, src.sourceType, src.sourceId);
|
|
674
|
-
|
|
701
|
+
// A BURNED fence (at === 0) still carries the attempt id but the attempt
|
|
702
|
+
// is already error-closed — its members are back in pending awaiting the
|
|
703
|
+
// re-drive. The redundant deliver()-side close for the same envelope
|
|
704
|
+
// must skip here, or its "ok" would no_action-sweep the very rows the
|
|
705
|
+
// error close just released (the retry hole S3a closes).
|
|
706
|
+
if (active?.id === envId && active.at > 0) {
|
|
675
707
|
fenced.push({ source_type: src.sourceType, source_id: src.sourceId });
|
|
676
708
|
}
|
|
677
709
|
}
|
|
678
|
-
if (fenced.length > 0 && (await completeSources(ctx, fenced))) {
|
|
710
|
+
if (fenced.length > 0 && (await completeSources(ctx, fenced, outcome))) {
|
|
679
711
|
for (const src of fenced) {
|
|
680
|
-
|
|
712
|
+
if (outcome === 'error') {
|
|
713
|
+
await burnActiveEmit(ctx, src.source_type, src.source_id);
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
await clearActiveEmit(ctx, src.source_type, src.source_id);
|
|
717
|
+
}
|
|
681
718
|
}
|
|
682
719
|
}
|
|
683
720
|
}
|
|
684
721
|
/**
|
|
685
|
-
* Turn-end ledger close: resolves the turn's folded sources on the server
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
722
|
+
* Turn-end ledger close: resolves the turn's folded sources on the server via
|
|
723
|
+
* the unified complete's source form (POST /dispatch/complete, idempotent) —
|
|
724
|
+
* broad-cover to the turn's reply Effect when one exists; otherwise a
|
|
725
|
+
* no_action sweep on "ok", or a release back to pending on the redrive budget
|
|
726
|
+
* on "error" (a failed turn must not silently consume work it never handled).
|
|
727
|
+
* One immediate retry on failure; a still-failed call leaves the rows live
|
|
728
|
+
* and the catch-up TTL re-emit self-heals.
|
|
690
729
|
*/
|
|
691
|
-
export async function completeSources(ctx, sources) {
|
|
692
|
-
// Ledger surface — not gated on reportingEnabled (see
|
|
730
|
+
export async function completeSources(ctx, sources, outcome = 'ok') {
|
|
731
|
+
// Ledger surface — not gated on reportingEnabled (see claimDispatchSource).
|
|
693
732
|
if (sources.length === 0)
|
|
694
733
|
return true;
|
|
695
|
-
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/dispatch/complete
|
|
734
|
+
const url = `${parallApiUrl(ctx)}/api/v1/orgs/${parallOrgId(ctx)}/dispatch/complete`;
|
|
696
735
|
const attempt = async () => {
|
|
697
736
|
const res = await fetch(url, {
|
|
698
737
|
method: 'POST',
|
|
699
738
|
headers: jsonHeaders(ctx),
|
|
700
|
-
body: JSON.stringify({ sources }),
|
|
739
|
+
body: JSON.stringify({ sources, turn_outcome: outcome }),
|
|
701
740
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
702
741
|
});
|
|
703
742
|
if (!res.ok)
|
|
704
|
-
console.error(`[parel-channel] complete
|
|
743
|
+
console.error(`[parel-channel] complete (sources) failed (${res.status})`);
|
|
705
744
|
return res.ok;
|
|
706
745
|
};
|
|
707
746
|
try {
|
|
@@ -715,7 +754,7 @@ export async function completeSources(ctx, sources) {
|
|
|
715
754
|
return await attempt();
|
|
716
755
|
}
|
|
717
756
|
catch (err) {
|
|
718
|
-
console.error('[parel-channel] complete
|
|
757
|
+
console.error('[parel-channel] complete (sources) failed', err);
|
|
719
758
|
return false;
|
|
720
759
|
}
|
|
721
760
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/parel-channel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.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": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"parel.plugin.json"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@parel/plugin-sdk": "^0.
|
|
26
|
+
"@parel/plugin-sdk": "^0.12.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.0.0",
|