@parall/parel-channel 1.52.0 → 1.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -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;AA0H3B,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;AAkoBD;;;;;;;;;;;;;;;;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 = {
@@ -751,6 +760,18 @@ export function handleParallAgentEvent(event, ctx) {
751
760
  return serialized(ctx, () => handleParallAgentEventInner(event, ctx));
752
761
  }
753
762
  async function handleParallAgentEventInner(event, ctx) {
763
+ // Turn-observed latch (delivery.ts's close gate): ONLY the turn lifecycle
764
+ // proves this binding will get a turn_completed, so the deliver-side
765
+ // redundant close can stand down and let turn_completed settle each folded
766
+ // batch whole. Step events (observe: [steps] without turn) and
767
+ // child_spawn_failed (pushed regardless of observe scopes) reach bindings
768
+ // that never see a turn end — arming on those would suppress their ONLY
769
+ // terminal ledger path, and keep refreshing the suppression.
770
+ if (event.type === 'turn_started' ||
771
+ event.type === 'turn_completed' ||
772
+ event.type === 'turn_failed') {
773
+ await noteTurnEventsObserved(ctx);
774
+ }
754
775
  // spawnChildSession's asynchronous error channel — not turn-scoped and
755
776
  // pushed regardless of observe scopes. Handle it before anything
756
777
  // 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.1",
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/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,
@@ -355,6 +356,7 @@ async function effectsForDispatch(
355
356
  content?: { text?: string };
356
357
  thread_root_id?: string;
357
358
  sender_id?: string;
359
+ hints?: { no_reply?: boolean };
358
360
  attachments?: Array<{
359
361
  id?: string;
360
362
  file_name?: string;
@@ -392,6 +394,10 @@ async function effectsForDispatch(
392
394
  // prefetch could never persist its cache row.
393
395
  const chatName = await cachedChatName(ctx, chatId);
394
396
  const senderId = msg?.sender_id ? String(msg.sender_id) : undefined;
397
+ // Structured no-reply hint (R-10): render the same [Hint: no_reply] line
398
+ // agent-core injects for the standard runtimes, instead of silently
399
+ // dropping the hint and betting on the body text.
400
+ const noReply = msg?.hints?.no_reply === true;
395
401
  const framedText = buildChatPrompt({
396
402
  chatId,
397
403
  chatName,
@@ -399,6 +405,7 @@ async function effectsForDispatch(
399
405
  threadRootId,
400
406
  text,
401
407
  attachments,
408
+ noReply,
402
409
  });
403
410
  // Deterministic source-derived envelope id (+ attempt suffix on re-emits):
404
411
  // complete-sources strips the suffix back to the source, and every re-emit
@@ -437,6 +444,10 @@ async function effectsForDispatch(
437
444
  PRLL_DISPATCH_SOURCE_TYPE: sourceType,
438
445
  PRLL_DISPATCH_SOURCE_ID: sourceId,
439
446
  ...(threadRootId ? { PRLL_THREAD_ROOT_ID: threadRootId } : {}),
447
+ // Same env contract the standard runtimes' context file carries — the
448
+ // CLI already parses PRLL_NO_REPLY; today it is informational (prompt
449
+ // hint above does the suppression), but the context must not lie.
450
+ ...(noReply ? { PRLL_NO_REPLY: '1' } : {}),
440
451
  ...(capabilityEnv ?? {}),
441
452
  };
442
453
 
@@ -917,6 +928,20 @@ async function handleParallAgentEventInner(
917
928
  event: AgentEvent,
918
929
  ctx: ConnectorContext,
919
930
  ): Promise<AgentEventEffect[]> {
931
+ // Turn-observed latch (delivery.ts's close gate): ONLY the turn lifecycle
932
+ // proves this binding will get a turn_completed, so the deliver-side
933
+ // redundant close can stand down and let turn_completed settle each folded
934
+ // batch whole. Step events (observe: [steps] without turn) and
935
+ // child_spawn_failed (pushed regardless of observe scopes) reach bindings
936
+ // that never see a turn end — arming on those would suppress their ONLY
937
+ // terminal ledger path, and keep refreshing the suppression.
938
+ if (
939
+ event.type === 'turn_started' ||
940
+ event.type === 'turn_completed' ||
941
+ event.type === 'turn_failed'
942
+ ) {
943
+ await noteTurnEventsObserved(ctx);
944
+ }
920
945
  // spawnChildSession's asynchronous error channel — not turn-scoped and
921
946
  // pushed regardless of observe scopes. Handle it before anything
922
947
  // 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