@parall/parel-channel 1.52.0 → 1.52.2

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.
@@ -43,6 +43,8 @@ export interface ChatPromptArgs {
43
43
  text: string;
44
44
  /** Message attachments — rendered as reference lines (agent fetches via CLI). */
45
45
  attachments?: PromptAttachment[];
46
+ /** message.hints.no_reply — rendered as the same [Hint: no_reply] line agent-core injects. */
47
+ noReply?: boolean;
46
48
  }
47
49
  /**
48
50
  * Frame a Parall chat message with its conversation of origin. Kept to a
@@ -1 +1 @@
1
- {"version":3,"file":"channel-prompt.d.ts","sourceRoot":"","sources":["../src/channel-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AASH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAClC;AAmBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAU5D;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAkBlE"}
1
+ {"version":3,"file":"channel-prompt.d.ts","sourceRoot":"","sources":["../src/channel-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AASH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAmBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAW5D;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAkBlE"}
@@ -63,6 +63,8 @@ export function buildChatPrompt(args) {
63
63
  lines.push(`[From: ${sanitizeMeta(args.senderId)}]`);
64
64
  if (args.threadRootId)
65
65
  lines.push(`[Thread: ${sanitizeMeta(args.threadRootId)}]`);
66
+ if (args.noReply)
67
+ lines.push(`[Hint: no_reply]`);
66
68
  lines.push(...attachmentLines(args.attachments));
67
69
  lines.push('', args.text);
68
70
  return lines.join('\n');
@@ -1 +1 @@
1
- {"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAa5F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAgF5B"}
1
+ {"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAc5F;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAsF5B"}
package/dist/delivery.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { childRefKnown } from './fork.js';
2
- import { completeFencedEnvelopes, createSuppressedTextStep, ensureChildSession, ensureSession, invalidateChildSession, invalidateSession, patchSession, TURN_ENVELOPE_KEY, } from './session.js';
2
+ import { completeFencedEnvelopes, createSuppressedTextStep, ensureChildSession, ensureSession, invalidateChildSession, invalidateSession, patchSession, TURN_ENVELOPE_KEY, turnEventsObserved, } from './session.js';
3
3
  /**
4
4
  * Turn-end delivery — which, under the platform reply contract, delivers
5
5
  * NOTHING to the chat.
@@ -67,16 +67,22 @@ export async function buildParallDelivery(delivery, ctx) {
67
67
  // (the npm ^1 fallback path): there turn events never arrive and, with
68
68
  // ack-on-emit gone, this hook is the only terminal ledger path — without it
69
69
  // a delivered turn's row would stay live and re-emit on every TTL sweep.
70
- // Under the normal artifact path both hooks run; the fence makes the
71
- // overlap free (whichever completes first consumes it, the other skips).
72
70
  // Fork-handled envelopes never wrote a fence, so this is a no-op for them
73
71
  // (their ack machinery owns the ledger there).
74
- // Known bound of this fallback: the replyRoute snapshot carries ONE
75
- // envelope id, so a folded turn's OTHER sources heal only via the TTL
72
+ // On an OBSERVING binding this close must stand down (the turn-observed
73
+ // latch): the per-envelope fence makes the overlap idempotent, but not
74
+ // batch-safe — deliver runs before turn_completed, so closing the
75
+ // trigger's envelope here consumes its fence and splits the folded turn's
76
+ // [trigger, sibling] batch; the sibling's complete then finds no covering
77
+ // reply and sweeps to no_action (R-06). Cost of standing down: if the
78
+ // observing binding's turn_completed is lost, the fence heals via the TTL
79
+ // re-emit — the same recovery every lost close already relies on.
80
+ // Known bound of the no-observe fallback: the replyRoute snapshot carries
81
+ // ONE envelope id, so a folded turn's OTHER sources heal only via the TTL
76
82
  // re-emit here. Reaching that state needs a binding with injectInFlight
77
83
  // ON and observe OFF — the platform template always sets both together,
78
84
  // so it exists only on a hand-crafted binding.
79
- if (route.envelopeId) {
85
+ if (route.envelopeId && !(await turnEventsObserved(ctx))) {
80
86
  await completeFencedEnvelopes(ctx, [route.envelopeId], 'ok');
81
87
  }
82
88
  const subject = route.chatId ?? route.conversationId;
package/dist/fork.d.ts CHANGED
@@ -146,13 +146,17 @@ export declare function handleChildSpawnFailed(ctx: ConnectorContext, event: Chi
146
146
  * this line keeps the child from "finishing" main's visible work).
147
147
  *
148
148
  * Replies are CLI-only on EVERY fork turn (the platform reply contract —
149
- * nothing auto-delivers, parel-reply-contract-alignment.md). The opening
150
- * turn additionally has no envelope (it starts from the spawn input), so it
151
- * gets no per-turn invocation context either the prefix therefore spells
152
- * out the full CLI reply command with the explicit chat target; follow-ups
153
- * arrive as deliverTo envelopes whose invocation context presets
154
- * PRLL_CHAT_ID, so the system prompt's bare `parall messages send` teaching
155
- * covers them.
149
+ * nothing auto-delivers, parel-reply-contract-alignment.md). The opening turn
150
+ * has no envelope of its own (it starts from the spawn input), so its per-turn
151
+ * context rides the effect's `invocationContext` instead; follow-ups arrive as
152
+ * deliverTo envelopes carrying theirs the usual way.
153
+ *
154
+ * The prefix still spells out the full CLI reply command with the explicit chat
155
+ * target rather than relying on the bare `parall messages send` the system
156
+ * prompt teaches. That is deliberate: effects are duck-typed by the host, so a
157
+ * parel deployment predating `invocationContext` silently drops it, and the
158
+ * opening turn would then have no PRLL_CHAT_ID to default to. The explicit
159
+ * target costs nothing and keeps the reply path independent of that skew.
156
160
  */
157
161
  export declare function buildForkScopePrefix(subject: string, threadRootId?: string): string;
158
162
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"fork.d.ts","sourceRoot":"","sources":["../src/fork.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fork.d.ts","sourceRoot":"","sources":["../src/fork.ts"],"names":[],"mappings":"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"}
package/dist/fork.js CHANGED
@@ -314,13 +314,17 @@ export async function handleChildSpawnFailed(ctx, event) {
314
314
  * this line keeps the child from "finishing" main's visible work).
315
315
  *
316
316
  * Replies are CLI-only on EVERY fork turn (the platform reply contract —
317
- * nothing auto-delivers, parel-reply-contract-alignment.md). The opening
318
- * turn additionally has no envelope (it starts from the spawn input), so it
319
- * gets no per-turn invocation context either the prefix therefore spells
320
- * out the full CLI reply command with the explicit chat target; follow-ups
321
- * arrive as deliverTo envelopes whose invocation context presets
322
- * PRLL_CHAT_ID, so the system prompt's bare `parall messages send` teaching
323
- * covers them.
317
+ * nothing auto-delivers, parel-reply-contract-alignment.md). The opening turn
318
+ * has no envelope of its own (it starts from the spawn input), so its per-turn
319
+ * context rides the effect's `invocationContext` instead; follow-ups arrive as
320
+ * deliverTo envelopes carrying theirs the usual way.
321
+ *
322
+ * The prefix still spells out the full CLI reply command with the explicit chat
323
+ * target rather than relying on the bare `parall messages send` the system
324
+ * prompt teaches. That is deliberate: effects are duck-typed by the host, so a
325
+ * parel deployment predating `invocationContext` silently drops it, and the
326
+ * opening turn would then have no PRLL_CHAT_ID to default to. The explicit
327
+ * target costs nothing and keeps the reply path independent of that skew.
324
328
  */
325
329
  export function buildForkScopePrefix(subject, threadRootId) {
326
330
  // Quoted-heredoc input form (mirrors agent-core's send hint): plain
@@ -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;AAyH3B,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAWrF;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5B;AAoDD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EACtB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5B;AAsCD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAElF;AAwnBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE7B"}
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;AAsI3B,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAWrF;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5B;AAoDD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EACtB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,eAAe,EAAE,CAAC,CAE5B;AAsCD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAElF;AAyoBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,gBAAgB,GACpB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAE7B"}
package/dist/inbound.js CHANGED
@@ -4,7 +4,7 @@ import { armCatchUpEffect, beginCatchUp, CATCHUP_TIMER_KEY, deferToCatchUp, runC
4
4
  import { parallApiUrl, parallOrgId, requireAgk, sandboxEnabled } from './connect.js';
5
5
  import { effectsForChannelDispatch, PROVIDER_LOOKUP_TIMEOUT_MS } from './inbound-channel.js';
6
6
  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';
7
- import { ackByIdEffect, claimDispatchSource, completeFencedEnvelopes, completeSourceEffect, conversationKey, createErrorStep, createInputStep, createTraceStep, createTypedInputStep, EMIT_TTL_MS, ensureChildSession, ensureSession, invalidateChildSession, invalidateSession, nextEnvelopeId, patchSession, readActiveEmit, reportingEnabled, REQUEST_TIMEOUT_MS, TURN_ENVELOPE_KEY, writeActiveEmit, } from './session.js';
7
+ import { ackByIdEffect, claimDispatchSource, completeFencedEnvelopes, completeSourceEffect, conversationKey, createErrorStep, createInputStep, createTraceStep, createTypedInputStep, EMIT_TTL_MS, ensureChildSession, ensureSession, invalidateChildSession, invalidateSession, nextEnvelopeId, noteTurnEventsObserved, patchSession, readActiveEmit, reportingEnabled, REQUEST_TIMEOUT_MS, TURN_ENVELOPE_KEY, writeActiveEmit, } from './session.js';
8
8
  import { isTypedEventType, planTypedDispatch } from './typed-dispatch.js';
9
9
  /**
10
10
  * Connection-keyed hook serialization — a v1-host compatibility shim. The v1
@@ -255,6 +255,10 @@ async function effectsForDispatch(data, ctx) {
255
255
  // prefetch could never persist its cache row.
256
256
  const chatName = await cachedChatName(ctx, chatId);
257
257
  const senderId = msg?.sender_id ? String(msg.sender_id) : undefined;
258
+ // Structured no-reply hint (R-10): render the same [Hint: no_reply] line
259
+ // agent-core injects for the standard runtimes, instead of silently
260
+ // dropping the hint and betting on the body text.
261
+ const noReply = msg?.hints?.no_reply === true;
258
262
  const framedText = buildChatPrompt({
259
263
  chatId,
260
264
  chatName,
@@ -262,6 +266,7 @@ async function effectsForDispatch(data, ctx) {
262
266
  threadRootId,
263
267
  text,
264
268
  attachments,
269
+ noReply,
265
270
  });
266
271
  // Deterministic source-derived envelope id (+ attempt suffix on re-emits):
267
272
  // complete-sources strips the suffix back to the source, and every re-emit
@@ -298,6 +303,10 @@ async function effectsForDispatch(data, ctx) {
298
303
  PRLL_DISPATCH_SOURCE_TYPE: sourceType,
299
304
  PRLL_DISPATCH_SOURCE_ID: sourceId,
300
305
  ...(threadRootId ? { PRLL_THREAD_ROOT_ID: threadRootId } : {}),
306
+ // Same env contract the standard runtimes' context file carries — the
307
+ // CLI already parses PRLL_NO_REPLY; today it is informational (prompt
308
+ // hint above does the suppression), but the context must not lie.
309
+ ...(noReply ? { PRLL_NO_REPLY: '1' } : {}),
301
310
  ...(capabilityEnv ?? {}),
302
311
  };
303
312
  const envelope = {
@@ -464,26 +473,34 @@ async function effectsForDispatch(data, ctx) {
464
473
  ]);
465
474
  if (results.includes('stale'))
466
475
  await invalidateChildSession(ctx, decision.childRef);
476
+ // Same stamp — and same never-stamp-a-stale-ase_ rule — as the
477
+ // deliverTo branch above. Now that the opening turn carries context,
478
+ // its CLI reply links to the child session directly instead of
479
+ // resolving through dispatch provenance alone.
480
+ else
481
+ invocationContext.PRLL_SESSION_ID = childAse;
467
482
  }
468
483
  await recordSpawn(ctx, subject, decision.childRef, { dispatchId, sourceId });
469
484
  await receivedSettled;
470
- // Known gap: SpawnChildSessionEffect (plugin-sdk 0.12) carries no
471
- // invocation context, so the fork's OPENING turn runs without the
472
- // capability env (lark-cli workspace ops would need a follow-up turn)
473
- // AND without PRLL_SESSION_ID (its reply links via dispatch provenance
474
- // instead)deliverTo follow-ups inherit the full context, including
475
- // the session stamp. Channel replies are unaffected: channel_message
476
- // dispatches always ride main. Fix belongs parel-side (context on
477
- // spawnChildSession) tracked as a parel ask.
478
- return [
479
- {
480
- type: 'spawnChildSession',
481
- childRef: decision.childRef,
482
- input: buildForkScopePrefix(chatId, threadRootId) + framedText,
483
- subject,
484
- },
485
- ...timerEffects,
486
- ];
485
+ // The opening turn gets the SAME per-turn context as any deliverTo
486
+ // envelope (parel `spawnChildSession.invocationContext`): PRLL_CHAT_ID,
487
+ // the dispatch-source pair and the session stamp reach the sandbox, and
488
+ // the capability env (lark-cli) is live from turn one. It used to be the
489
+ // single turn of a child's life that ran blind it starts from the spawn
490
+ // input, not an envelope, and the effect had nowhere to carry context.
491
+ //
492
+ // A parel deployment that predates the field ignores it (effects are
493
+ // duck-typed, not schema-validated), which is why buildForkScopePrefix
494
+ // still spells out the explicit chat target: the reply path must not
495
+ // depend on the host having shipped this yet.
496
+ const spawn = {
497
+ type: 'spawnChildSession',
498
+ childRef: decision.childRef,
499
+ input: buildForkScopePrefix(chatId, threadRootId) + framedText,
500
+ subject,
501
+ invocationContext,
502
+ };
503
+ return [spawn, ...timerEffects];
487
504
  }
488
505
  // fall through: black-holed spawn rides main below.
489
506
  }
@@ -751,6 +768,18 @@ export function handleParallAgentEvent(event, ctx) {
751
768
  return serialized(ctx, () => handleParallAgentEventInner(event, ctx));
752
769
  }
753
770
  async function handleParallAgentEventInner(event, ctx) {
771
+ // Turn-observed latch (delivery.ts's close gate): ONLY the turn lifecycle
772
+ // proves this binding will get a turn_completed, so the deliver-side
773
+ // redundant close can stand down and let turn_completed settle each folded
774
+ // batch whole. Step events (observe: [steps] without turn) and
775
+ // child_spawn_failed (pushed regardless of observe scopes) reach bindings
776
+ // that never see a turn end — arming on those would suppress their ONLY
777
+ // terminal ledger path, and keep refreshing the suppression.
778
+ if (event.type === 'turn_started' ||
779
+ event.type === 'turn_completed' ||
780
+ event.type === 'turn_failed') {
781
+ await noteTurnEventsObserved(ctx);
782
+ }
754
783
  // spawnChildSession's asynchronous error channel — not turn-scoped and
755
784
  // pushed regardless of observe scopes. Handle it before anything
756
785
  // subject-gated.
package/dist/session.d.ts CHANGED
@@ -43,6 +43,23 @@ export declare function reportingEnabled(ctx: ConnectorContext): boolean;
43
43
  */
44
44
  export declare const TURN_ENVELOPE_KEY = "turnEnvelope:main";
45
45
  export declare const SESSION_CACHE_KEY = "session:main";
46
+ /**
47
+ * Turn-observed latch: proof this binding observes the TURN lifecycle, armed
48
+ * by turn_started / turn_completed / turn_failed only. deliver()'s redundant
49
+ * turn-end close exists only for bindings WITHOUT `observe: [turn]` — on an
50
+ * observing binding that close races turn_completed and can consume the
51
+ * trigger's fence FIRST, splitting a folded turn's [trigger, sibling] batch
52
+ * so the sibling's complete finds no covering reply and sweeps to no_action
53
+ * (R-06). Step events and child_spawn_failed must NOT arm it: they reach
54
+ * bindings that never receive a turn end, whose deliver-side close is their
55
+ * only terminal ledger path. The TTL keeps the fallback able to return should
56
+ * a binding's turn observation ever be turned off: a day with zero turn
57
+ * events ages the latch out and the deliver-side close resumes.
58
+ */
59
+ export declare const TURN_EVENTS_OBSERVED_KEY = "turnEventsObserved:main";
60
+ export declare const TURN_OBSERVED_TTL_MS: number;
61
+ export declare function noteTurnEventsObserved(ctx: ConnectorContext): Promise<void>;
62
+ export declare function turnEventsObserved(ctx: ConnectorContext): Promise<boolean>;
46
63
  /**
47
64
  * Conversation key for envelope subjects and fork routing. Thread-aware:
48
65
  * parel's turn grouping (F2) and mid-turn injection (F3) key on the envelope
@@ -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
@@ -46,6 +46,49 @@ export function reportingEnabled(ctx) {
46
46
  export const TURN_ENVELOPE_KEY = `turnEnvelope:${MAIN_SESSION_KEY}`;
47
47
  export const SESSION_CACHE_KEY = `session:${MAIN_SESSION_KEY}`;
48
48
  const childSessionCacheKey = (childRef) => `sessionChild:${childRef}`;
49
+ /**
50
+ * Turn-observed latch: proof this binding observes the TURN lifecycle, armed
51
+ * by turn_started / turn_completed / turn_failed only. deliver()'s redundant
52
+ * turn-end close exists only for bindings WITHOUT `observe: [turn]` — on an
53
+ * observing binding that close races turn_completed and can consume the
54
+ * trigger's fence FIRST, splitting a folded turn's [trigger, sibling] batch
55
+ * so the sibling's complete finds no covering reply and sweeps to no_action
56
+ * (R-06). Step events and child_spawn_failed must NOT arm it: they reach
57
+ * bindings that never receive a turn end, whose deliver-side close is their
58
+ * only terminal ledger path. The TTL keeps the fallback able to return should
59
+ * a binding's turn observation ever be turned off: a day with zero turn
60
+ * events ages the latch out and the deliver-side close resumes.
61
+ */
62
+ export const TURN_EVENTS_OBSERVED_KEY = `turnEventsObserved:${MAIN_SESSION_KEY}`;
63
+ export const TURN_OBSERVED_TTL_MS = 24 * 60 * 60 * 1000;
64
+ export async function noteTurnEventsObserved(ctx) {
65
+ // A failed write is not propagated: throwing would fail the whole hook and
66
+ // buy a platform replay of an event whose other effects (steps, session
67
+ // patches) already landed, to re-arm a latch the NEXT turn event re-arms
68
+ // anyway — every turn opens with turn_started. The read below is where a
69
+ // store failure actually has to be handled safely.
70
+ await ctx.store?.set(TURN_EVENTS_OBSERVED_KEY, ctx.now()).catch(() => { });
71
+ }
72
+ export async function turnEventsObserved(ctx) {
73
+ // No store at all: nothing was ever armed, so the fallback close is this
74
+ // binding's only terminal path — it must run.
75
+ if (!ctx.store)
76
+ return false;
77
+ let at;
78
+ try {
79
+ at = await ctx.store.get(TURN_EVENTS_OBSERVED_KEY);
80
+ }
81
+ catch {
82
+ // A transient read failure must NOT read as "this binding does not observe
83
+ // turns". That answer runs the deliver-side close, the destructive branch:
84
+ // it consumes the trigger's fence and can split a folded batch, which is
85
+ // R-06 itself. Failing closed costs at most a missed fallback close, which
86
+ // the existing 30min TTL re-emit heals; failing open costs a sibling swept
87
+ // to no_action, which nothing recovers.
88
+ return true;
89
+ }
90
+ return typeof at === 'number' && ctx.now() - at < TURN_OBSERVED_TTL_MS;
91
+ }
49
92
  /**
50
93
  * Conversation key for envelope subjects and fork routing. Thread-aware:
51
94
  * parel's turn grouping (F2) and mid-turn injection (F3) key on the envelope
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/parel-channel",
3
- "version": "1.52.0",
3
+ "version": "1.52.2",
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": {
@@ -52,6 +52,8 @@ export interface ChatPromptArgs {
52
52
  text: string;
53
53
  /** Message attachments — rendered as reference lines (agent fetches via CLI). */
54
54
  attachments?: PromptAttachment[];
55
+ /** message.hints.no_reply — rendered as the same [Hint: no_reply] line agent-core injects. */
56
+ noReply?: boolean;
55
57
  }
56
58
 
57
59
  /**
@@ -84,6 +86,7 @@ export function buildChatPrompt(args: ChatPromptArgs): string {
84
86
  const lines = [`[Chat: ${chatLabel}]`];
85
87
  if (args.senderId) lines.push(`[From: ${sanitizeMeta(args.senderId)}]`);
86
88
  if (args.threadRootId) lines.push(`[Thread: ${sanitizeMeta(args.threadRootId)}]`);
89
+ if (args.noReply) lines.push(`[Hint: no_reply]`);
87
90
  lines.push(...attachmentLines(args.attachments));
88
91
  lines.push('', args.text);
89
92
  return lines.join('\n');
package/src/delivery.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  invalidateSession,
10
10
  patchSession,
11
11
  TURN_ENVELOPE_KEY,
12
+ turnEventsObserved,
12
13
  } from './session.js';
13
14
 
14
15
  /**
@@ -89,16 +90,22 @@ export async function buildParallDelivery(
89
90
  // (the npm ^1 fallback path): there turn events never arrive and, with
90
91
  // ack-on-emit gone, this hook is the only terminal ledger path — without it
91
92
  // a delivered turn's row would stay live and re-emit on every TTL sweep.
92
- // Under the normal artifact path both hooks run; the fence makes the
93
- // overlap free (whichever completes first consumes it, the other skips).
94
93
  // Fork-handled envelopes never wrote a fence, so this is a no-op for them
95
94
  // (their ack machinery owns the ledger there).
96
- // Known bound of this fallback: the replyRoute snapshot carries ONE
97
- // envelope id, so a folded turn's OTHER sources heal only via the TTL
95
+ // On an OBSERVING binding this close must stand down (the turn-observed
96
+ // latch): the per-envelope fence makes the overlap idempotent, but not
97
+ // batch-safe — deliver runs before turn_completed, so closing the
98
+ // trigger's envelope here consumes its fence and splits the folded turn's
99
+ // [trigger, sibling] batch; the sibling's complete then finds no covering
100
+ // reply and sweeps to no_action (R-06). Cost of standing down: if the
101
+ // observing binding's turn_completed is lost, the fence heals via the TTL
102
+ // re-emit — the same recovery every lost close already relies on.
103
+ // Known bound of the no-observe fallback: the replyRoute snapshot carries
104
+ // ONE envelope id, so a folded turn's OTHER sources heal only via the TTL
98
105
  // re-emit here. Reaching that state needs a binding with injectInFlight
99
106
  // ON and observe OFF — the platform template always sets both together,
100
107
  // so it exists only on a hand-crafted binding.
101
- if (route.envelopeId) {
108
+ if (route.envelopeId && !(await turnEventsObserved(ctx))) {
102
109
  await completeFencedEnvelopes(ctx, [route.envelopeId], 'ok');
103
110
  }
104
111
 
package/src/fork.ts CHANGED
@@ -426,13 +426,17 @@ export async function handleChildSpawnFailed(
426
426
  * this line keeps the child from "finishing" main's visible work).
427
427
  *
428
428
  * Replies are CLI-only on EVERY fork turn (the platform reply contract —
429
- * nothing auto-delivers, parel-reply-contract-alignment.md). The opening
430
- * turn additionally has no envelope (it starts from the spawn input), so it
431
- * gets no per-turn invocation context either the prefix therefore spells
432
- * out the full CLI reply command with the explicit chat target; follow-ups
433
- * arrive as deliverTo envelopes whose invocation context presets
434
- * PRLL_CHAT_ID, so the system prompt's bare `parall messages send` teaching
435
- * covers them.
429
+ * nothing auto-delivers, parel-reply-contract-alignment.md). The opening turn
430
+ * has no envelope of its own (it starts from the spawn input), so its per-turn
431
+ * context rides the effect's `invocationContext` instead; follow-ups arrive as
432
+ * deliverTo envelopes carrying theirs the usual way.
433
+ *
434
+ * The prefix still spells out the full CLI reply command with the explicit chat
435
+ * target rather than relying on the bare `parall messages send` the system
436
+ * prompt teaches. That is deliberate: effects are duck-typed by the host, so a
437
+ * parel deployment predating `invocationContext` silently drops it, and the
438
+ * opening turn would then have no PRLL_CHAT_ID to default to. The explicit
439
+ * target costs nothing and keeps the reply path independent of that skew.
436
440
  */
437
441
  export function buildForkScopePrefix(subject: string, threadRootId?: string): string {
438
442
  // Quoted-heredoc input form (mirrors agent-core's send hint): plain
package/src/inbound.ts CHANGED
@@ -49,6 +49,7 @@ import {
49
49
  invalidateChildSession,
50
50
  invalidateSession,
51
51
  nextEnvelopeId,
52
+ noteTurnEventsObserved,
52
53
  patchSession,
53
54
  readActiveEmit,
54
55
  reportingEnabled,
@@ -96,6 +97,18 @@ import { isTypedEventType, planTypedDispatch } from './typed-dispatch.js';
96
97
  * pending rather than being acked-and-dropped.
97
98
  */
98
99
 
100
+ /**
101
+ * The spawn effect plus parel's `invocationContext`, which carries per-turn
102
+ * context into the child's opening turn. The field is native in
103
+ * `@parel/plugin-sdk` 0.14.0, but this package still resolves `^0.12.0` and a
104
+ * 0.x caret never crosses minors — so the alias stays until that dependency is
105
+ * bumped, then it can go. Effects are duck-typed by the host, so an older parel
106
+ * deployment ignores the field rather than rejecting the spawn.
107
+ */
108
+ type SpawnChildEffect = Extract<ConnectorEffect, { type: 'spawnChildSession' }> & {
109
+ invocationContext?: Record<string, unknown>;
110
+ };
111
+
99
112
  interface DispatchData {
100
113
  id?: unknown;
101
114
  event_type?: unknown;
@@ -355,6 +368,7 @@ async function effectsForDispatch(
355
368
  content?: { text?: string };
356
369
  thread_root_id?: string;
357
370
  sender_id?: string;
371
+ hints?: { no_reply?: boolean };
358
372
  attachments?: Array<{
359
373
  id?: string;
360
374
  file_name?: string;
@@ -392,6 +406,10 @@ async function effectsForDispatch(
392
406
  // prefetch could never persist its cache row.
393
407
  const chatName = await cachedChatName(ctx, chatId);
394
408
  const senderId = msg?.sender_id ? String(msg.sender_id) : undefined;
409
+ // Structured no-reply hint (R-10): render the same [Hint: no_reply] line
410
+ // agent-core injects for the standard runtimes, instead of silently
411
+ // dropping the hint and betting on the body text.
412
+ const noReply = msg?.hints?.no_reply === true;
395
413
  const framedText = buildChatPrompt({
396
414
  chatId,
397
415
  chatName,
@@ -399,6 +417,7 @@ async function effectsForDispatch(
399
417
  threadRootId,
400
418
  text,
401
419
  attachments,
420
+ noReply,
402
421
  });
403
422
  // Deterministic source-derived envelope id (+ attempt suffix on re-emits):
404
423
  // complete-sources strips the suffix back to the source, and every re-emit
@@ -437,6 +456,10 @@ async function effectsForDispatch(
437
456
  PRLL_DISPATCH_SOURCE_TYPE: sourceType,
438
457
  PRLL_DISPATCH_SOURCE_ID: sourceId,
439
458
  ...(threadRootId ? { PRLL_THREAD_ROOT_ID: threadRootId } : {}),
459
+ // Same env contract the standard runtimes' context file carries — the
460
+ // CLI already parses PRLL_NO_REPLY; today it is informational (prompt
461
+ // hint above does the suppression), but the context must not lie.
462
+ ...(noReply ? { PRLL_NO_REPLY: '1' } : {}),
440
463
  ...(capabilityEnv ?? {}),
441
464
  };
442
465
 
@@ -603,26 +626,33 @@ async function effectsForDispatch(
603
626
  }),
604
627
  ]);
605
628
  if (results.includes('stale')) await invalidateChildSession(ctx, decision.childRef);
629
+ // Same stamp — and same never-stamp-a-stale-ase_ rule — as the
630
+ // deliverTo branch above. Now that the opening turn carries context,
631
+ // its CLI reply links to the child session directly instead of
632
+ // resolving through dispatch provenance alone.
633
+ else invocationContext.PRLL_SESSION_ID = childAse;
606
634
  }
607
635
  await recordSpawn(ctx, subject, decision.childRef, { dispatchId, sourceId });
608
636
  await receivedSettled;
609
- // Known gap: SpawnChildSessionEffect (plugin-sdk 0.12) carries no
610
- // invocation context, so the fork's OPENING turn runs without the
611
- // capability env (lark-cli workspace ops would need a follow-up turn)
612
- // AND without PRLL_SESSION_ID (its reply links via dispatch provenance
613
- // instead)deliverTo follow-ups inherit the full context, including
614
- // the session stamp. Channel replies are unaffected: channel_message
615
- // dispatches always ride main. Fix belongs parel-side (context on
616
- // spawnChildSession) tracked as a parel ask.
617
- return [
618
- {
619
- type: 'spawnChildSession',
620
- childRef: decision.childRef,
621
- input: buildForkScopePrefix(chatId, threadRootId) + framedText,
622
- subject,
623
- },
624
- ...timerEffects,
625
- ];
637
+ // The opening turn gets the SAME per-turn context as any deliverTo
638
+ // envelope (parel `spawnChildSession.invocationContext`): PRLL_CHAT_ID,
639
+ // the dispatch-source pair and the session stamp reach the sandbox, and
640
+ // the capability env (lark-cli) is live from turn one. It used to be the
641
+ // single turn of a child's life that ran blind it starts from the spawn
642
+ // input, not an envelope, and the effect had nowhere to carry context.
643
+ //
644
+ // A parel deployment that predates the field ignores it (effects are
645
+ // duck-typed, not schema-validated), which is why buildForkScopePrefix
646
+ // still spells out the explicit chat target: the reply path must not
647
+ // depend on the host having shipped this yet.
648
+ const spawn: SpawnChildEffect = {
649
+ type: 'spawnChildSession',
650
+ childRef: decision.childRef,
651
+ input: buildForkScopePrefix(chatId, threadRootId) + framedText,
652
+ subject,
653
+ invocationContext,
654
+ };
655
+ return [spawn, ...timerEffects];
626
656
  }
627
657
  // fall through: black-holed spawn rides main below.
628
658
  }
@@ -917,6 +947,20 @@ async function handleParallAgentEventInner(
917
947
  event: AgentEvent,
918
948
  ctx: ConnectorContext,
919
949
  ): Promise<AgentEventEffect[]> {
950
+ // Turn-observed latch (delivery.ts's close gate): ONLY the turn lifecycle
951
+ // proves this binding will get a turn_completed, so the deliver-side
952
+ // redundant close can stand down and let turn_completed settle each folded
953
+ // batch whole. Step events (observe: [steps] without turn) and
954
+ // child_spawn_failed (pushed regardless of observe scopes) reach bindings
955
+ // that never see a turn end — arming on those would suppress their ONLY
956
+ // terminal ledger path, and keep refreshing the suppression.
957
+ if (
958
+ event.type === 'turn_started' ||
959
+ event.type === 'turn_completed' ||
960
+ event.type === 'turn_failed'
961
+ ) {
962
+ await noteTurnEventsObserved(ctx);
963
+ }
920
964
  // spawnChildSession's asynchronous error channel — not turn-scoped and
921
965
  // pushed regardless of observe scopes. Handle it before anything
922
966
  // subject-gated.
package/src/session.ts CHANGED
@@ -53,6 +53,50 @@ export const TURN_ENVELOPE_KEY = `turnEnvelope:${MAIN_SESSION_KEY}`;
53
53
  export const SESSION_CACHE_KEY = `session:${MAIN_SESSION_KEY}`;
54
54
  const childSessionCacheKey = (childRef: string) => `sessionChild:${childRef}`;
55
55
 
56
+ /**
57
+ * Turn-observed latch: proof this binding observes the TURN lifecycle, armed
58
+ * by turn_started / turn_completed / turn_failed only. deliver()'s redundant
59
+ * turn-end close exists only for bindings WITHOUT `observe: [turn]` — on an
60
+ * observing binding that close races turn_completed and can consume the
61
+ * trigger's fence FIRST, splitting a folded turn's [trigger, sibling] batch
62
+ * so the sibling's complete finds no covering reply and sweeps to no_action
63
+ * (R-06). Step events and child_spawn_failed must NOT arm it: they reach
64
+ * bindings that never receive a turn end, whose deliver-side close is their
65
+ * only terminal ledger path. The TTL keeps the fallback able to return should
66
+ * a binding's turn observation ever be turned off: a day with zero turn
67
+ * events ages the latch out and the deliver-side close resumes.
68
+ */
69
+ export const TURN_EVENTS_OBSERVED_KEY = `turnEventsObserved:${MAIN_SESSION_KEY}`;
70
+ export const TURN_OBSERVED_TTL_MS = 24 * 60 * 60 * 1000;
71
+
72
+ export async function noteTurnEventsObserved(ctx: ConnectorContext): Promise<void> {
73
+ // A failed write is not propagated: throwing would fail the whole hook and
74
+ // buy a platform replay of an event whose other effects (steps, session
75
+ // patches) already landed, to re-arm a latch the NEXT turn event re-arms
76
+ // anyway — every turn opens with turn_started. The read below is where a
77
+ // store failure actually has to be handled safely.
78
+ await ctx.store?.set(TURN_EVENTS_OBSERVED_KEY, ctx.now()).catch(() => {});
79
+ }
80
+
81
+ export async function turnEventsObserved(ctx: ConnectorContext): Promise<boolean> {
82
+ // No store at all: nothing was ever armed, so the fallback close is this
83
+ // binding's only terminal path — it must run.
84
+ if (!ctx.store) return false;
85
+ let at: unknown;
86
+ try {
87
+ at = await ctx.store.get(TURN_EVENTS_OBSERVED_KEY);
88
+ } catch {
89
+ // A transient read failure must NOT read as "this binding does not observe
90
+ // turns". That answer runs the deliver-side close, the destructive branch:
91
+ // it consumes the trigger's fence and can split a folded batch, which is
92
+ // R-06 itself. Failing closed costs at most a missed fallback close, which
93
+ // the existing 30min TTL re-emit heals; failing open costs a sibling swept
94
+ // to no_action, which nothing recovers.
95
+ return true;
96
+ }
97
+ return typeof at === 'number' && ctx.now() - at < TURN_OBSERVED_TTL_MS;
98
+ }
99
+
56
100
  /**
57
101
  * Conversation key for envelope subjects and fork routing. Thread-aware:
58
102
  * parel's turn grouping (F2) and mid-turn injection (F3) key on the envelope