@parall/agent-core 1.51.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.
@@ -1,5 +1,17 @@
1
1
  import { ApiError } from '@parall/sdk';
2
+ import { buildEventBody } from './event-format.js';
2
3
  import { LedgerUnsupportedError } from './lane-ledger.js';
4
+ export async function steerLaneMessage(host, event) {
5
+ const { laneLedger: ledger, opts } = host;
6
+ const adapter = opts.dispatchAdapter;
7
+ if (!ledger || !adapter.enqueueDuringDispatch)
8
+ return;
9
+ const folded = await ledger.steerLive(event);
10
+ if (folded &&
11
+ (await adapter.enqueueDuringDispatch(opts.runtimeKey, buildEventBody(event), folded.inputLifecycle))) {
12
+ opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
13
+ }
14
+ }
3
15
  /**
4
16
  * Dispatch one group of same-lane message events under the ledger contract:
5
17
  * claim (or reuse) the lane, run the dispatch, then complete when no local
@@ -46,7 +58,8 @@ export async function dispatchLaneGroup(host, opts) {
46
58
  host.noteSessionLane(opts.sessionKey, lane.laneKey);
47
59
  let dispatched = false;
48
60
  try {
49
- dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
61
+ const inputLifecycle = ledger.inputLifecycle(lane, [...opts.earlier, event]);
62
+ dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText, inputLifecycle);
50
63
  }
51
64
  catch (err) {
52
65
  // Failed turn: hand the members back so the retry (this pod or the
@@ -63,20 +76,33 @@ export async function dispatchLaneGroup(host, opts) {
63
76
  // Shutdown short-circuit — shutdown() releases all active lanes.
64
77
  return 'shutdown';
65
78
  }
66
- if (host.consumeTurnError(opts.sessionKey)) {
67
- // An error turn must not no_action-sweep its members — settle the lane
68
- // NOW with an error complete so they release for retry on the redrive
69
- // budget (dispatch-convergence-design.md §3). Settling immediately (even
70
- // with same-lane work still buffered) is deliberate: carrying the error
71
- // across buffered turns would let a later reply broad-cover the failed
72
- // member, or requeue the later turn's successful work. Released members
73
- // rejoin the next claim, merged with whatever was buffered. Dropping the
74
- // local dedupe claims lets the server's re-drive hint retrigger the
75
- // messages immediately instead of waiting out the renotify pacing.
76
- ledger.markTurnError(lane.laneKey);
79
+ const settled = host.consumeTurnOutcome(opts.sessionKey);
80
+ if (settled) {
81
+ // A non-clean turn must not no_action-sweep its members settle the lane
82
+ // NOW (even with same-lane work still buffered; deliberate — carrying the
83
+ // outcome across buffered turns would let a later reply broad-cover the
84
+ // failed member, or requeue the later turn's successful work):
85
+ // - error error complete; members release for retry on the redrive
86
+ // budget (dispatch-convergence-design.md §3).
87
+ // - deferred (usage_limit) deferred complete; members re-deliver at
88
+ // retryAt WITHOUT burning redrive budget (agent-turn-outcome-design.md
89
+ // §6) — the runtime's LLM lane is limit-choked, so an instant retry
90
+ // would just burn a failed turn per redrive cycle.
91
+ // Released members rejoin the next claim, merged with whatever was
92
+ // buffered. Dropping the local dedupe claims lets the re-delivery
93
+ // retrigger the messages when it comes.
94
+ if (settled.kind === 'deferred') {
95
+ ledger.markTurnDeferred(lane.laneKey, {
96
+ outcomeClass: settled.outcomeClass,
97
+ ...(settled.retryAt ? { retryAt: settled.retryAt } : {}),
98
+ });
99
+ }
100
+ else {
101
+ ledger.markTurnError(lane.laneKey);
102
+ }
77
103
  // Clear dedupe for EVERY lane member, not just this batch: an earlier
78
104
  // successful batch may have deferred its complete via hasMoreLocal, so
79
- // the error complete below releases those members too — their redrive
105
+ // the settlement below releases those members too — their re-delivery
80
106
  // would be permanently blocked by a stale local claim.
81
107
  for (const msgId of lane.folded.keys()) {
82
108
  host.dispatchedMessages.delete(msgId);
@@ -90,13 +116,14 @@ export async function dispatchLaneGroup(host, opts) {
90
116
  host.opts.dispatchAdapter.abortDispatch?.(opts.sessionKey);
91
117
  }
92
118
  catch {
93
- // best-effort — a throwing abort must not block the error settlement
119
+ // best-effort — a throwing abort must not block the settlement
94
120
  }
95
121
  await ledger.completeIfIdle(lane.laneKey, false);
96
- // 'failed' — callers must NOT record these events as handled: the server
97
- // just released them for redelivery, and an "already handled" fork prefix
98
- // (or a consumed fork summary) on the redrive would be a lie.
99
- return 'failed';
122
+ // Non-'dispatched' — callers must NOT record these events as handled: the
123
+ // server just released (or scheduled) them for redelivery, and an
124
+ // "already handled" fork prefix (or a consumed fork summary) on the
125
+ // redrive would be a lie.
126
+ return settled.kind === 'deferred' ? 'deferred' : 'failed';
100
127
  }
101
128
  const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
102
129
  await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
@@ -434,6 +461,11 @@ export async function consumeMessageWorkItem(host, item) {
434
461
  return;
435
462
  if (!host.tryClaimMessage(item.source_id))
436
463
  return;
464
+ // A failed soft-steer releases the server row before its original local
465
+ // buffer copy drains. The immediate dispatch.new is only a wake-up: keep
466
+ // the newly reacquired local claim and let that one buffered copy retry.
467
+ if (host.dispatchState.mainBuffer.some((event) => event.messageId === item.source_id))
468
+ return;
437
469
  // Administrative drop (deleted source / self-sender / skip decision): on
438
470
  // the ledger path the by-id complete closes the pending row terminally and
439
471
  // refuses (409) a row a live lane owns — the owner's turn resolves it. The
package/dist/index.d.ts CHANGED
@@ -9,8 +9,11 @@ export * from './event-format.js';
9
9
  export * from './prompt-fragments.js';
10
10
  export * from './bridge-workspace.js';
11
11
  export * from './dispatch-adapter.js';
12
+ export * from './redact.js';
12
13
  export { createLogger, childLogger } from './logger.js';
13
14
  export * from './gateway-base.js';
15
+ export { bindLaneSession } from './lane-ledger.js';
16
+ export type { ActiveLane } from './lane-ledger.js';
14
17
  export { configureHttpKeepAlive } from './http-keepalive.js';
15
18
  export * from './platform-config.js';
16
19
  export * from './channel-capability.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACvF,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAM7D,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAClF,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACvF,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAM7D,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAClF,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -8,8 +8,10 @@ export * from './event-format.js';
8
8
  export * from './prompt-fragments.js';
9
9
  export * from './bridge-workspace.js';
10
10
  export * from './dispatch-adapter.js';
11
+ export * from './redact.js';
11
12
  export { createLogger, childLogger } from './logger.js';
12
13
  export * from './gateway-base.js';
14
+ export { bindLaneSession } from './lane-ledger.js';
13
15
  export { configureHttpKeepAlive } from './http-keepalive.js';
14
16
  // StepPersister / StepRetryQueue / SessionLifecycleCoordinator /
15
17
  // ForkSessionFinalizer are internal gateway collaborators — deliberately NOT
@@ -1,5 +1,5 @@
1
1
  import type { ParallClient } from '@parall/sdk';
2
- import type { GatewayLogger } from './dispatch-adapter.js';
2
+ import type { DispatchInputLifecycle, GatewayLogger } from './dispatch-adapter.js';
3
3
  import type { ParallEvent } from './types.js';
4
4
  /** One claimed lane the bridge currently occupies. */
5
5
  export type ActiveLane = {
@@ -7,6 +7,7 @@ export type ActiveLane = {
7
7
  lane: string;
8
8
  targetUri: string;
9
9
  threadRootId?: string;
10
+ coverageMode: 'implicit' | 'explicit';
10
11
  /** source_id (message id) → WorkItem id for members folded into this lane. */
11
12
  folded: Map<string, string>;
12
13
  /** WorkItem id, set for typed lanes (resource = dsp:<id>, single member). */
@@ -21,6 +22,29 @@ export type ActiveLane = {
21
22
  * must not no_action-sweep the failed turn's members.
22
23
  */
23
24
  turnError?: boolean;
25
+ /**
26
+ * Sticky deferred settlement: the turn hit a self-healing LLM usage limit.
27
+ * The lane's complete reports turn_outcome=deferred so the server
28
+ * re-delivers the members at retryAt without burning redrive budget
29
+ * (agent-turn-outcome-design.md §6). turnError outranks this bit.
30
+ */
31
+ turnDeferred?: {
32
+ retryAt?: string;
33
+ outcomeClass: 'usage_limit';
34
+ };
35
+ /**
36
+ * Agent session (ase_) bound to this lane's turn — set once the runtime
37
+ * emits runtime_session. Rides the complete request so the server can
38
+ * attribute the swept no_action rows (message ↔ session linking).
39
+ */
40
+ sessionId?: string;
41
+ /**
42
+ * A lane that stayed occupied across a session rotation (New Session /
43
+ * runtime restart mid-backlog) has members handled by DIFFERENT sessions —
44
+ * attribution is ambiguous, so the complete request drops it rather than
45
+ * blaming everything on the newest session.
46
+ */
47
+ sessionAmbiguous?: boolean;
24
48
  };
25
49
  /**
26
50
  * Thrown when the server predates the dispatch ledger (claim endpoint 404s
@@ -30,6 +54,15 @@ export type ActiveLane = {
30
54
  export declare class LedgerUnsupportedError extends Error {
31
55
  }
32
56
  export type LaneGroupOutcome = 'claimed' | 'foreign';
57
+ /**
58
+ * Bind the turn's agent session to its lane for complete-time attribution.
59
+ * A lane fed by two DIFFERENT sessions (New Session / runtime restart
60
+ * mid-backlog) flips sticky-ambiguous: its members were handled by different
61
+ * sessions, so the complete must not blame everything on the newest one.
62
+ * Same-session rebinds are no-ops; an ambiguous lane never un-flips.
63
+ */
64
+ export declare function bindLaneSession(lane: ActiveLane, agentSessionId: string): void;
65
+ export declare function releaseLocalMessageClaims(claims: Set<string>, sourceIds: string[]): () => void;
33
66
  /**
34
67
  * LaneLedger is the bridge-side client of the server dispatch ledger
35
68
  * (docs/engineering-design/agent-dispatch-idempotency-design.md): it claims
@@ -50,6 +83,13 @@ export declare class LaneLedger {
50
83
  orgId: string;
51
84
  contextDir: string;
52
85
  log?: GatewayLogger;
86
+ coverageMode?: 'implicit' | 'explicit';
87
+ /**
88
+ * Release process-local message claims before a failed-input API call
89
+ * can publish its re-drive. Returns a rollback used when the call does
90
+ * not commit, preserving the local/server ordering contract.
91
+ */
92
+ releaseLocalClaims?: (sourceIds: string[]) => () => void;
53
93
  });
54
94
  get contextDir(): string;
55
95
  /** Only chat message events ride the lane ledger; typed events stay on the legacy ack path. */
@@ -71,7 +111,15 @@ export declare class LaneLedger {
71
111
  * an un-folded injected message would be re-driven after complete and the
72
112
  * model would handle it twice.
73
113
  */
74
- steerLive(event: ParallEvent): Promise<boolean>;
114
+ steerLive(event: ParallEvent): Promise<{
115
+ inputLifecycle?: DispatchInputLifecycle;
116
+ } | null>;
117
+ /**
118
+ * Build the exact runtime-input lifecycle for one prompt/injection. A
119
+ * batched prompt covers every WorkItem represented in that single frame.
120
+ */
121
+ inputLifecycle(lane: ActiveLane, events: ParallEvent[]): DispatchInputLifecycle | undefined;
122
+ private updateInputState;
75
123
  /**
76
124
  * Complete the lane when no local work remains for it: the server sweeps
77
125
  * still-leased members as no_action, releases the occupancy row, and
@@ -86,6 +134,22 @@ export declare class LaneLedger {
86
134
  * this lane's complete request.
87
135
  */
88
136
  markTurnError(laneKey: string): void;
137
+ /**
138
+ * Record that the turn on this lane ended on a self-healing usage limit.
139
+ * Like markTurnError this is transport state for the lane's final
140
+ * complete; an error bit set on the same lane outranks it.
141
+ */
142
+ markTurnDeferred(laneKey: string, info: {
143
+ retryAt?: string;
144
+ outcomeClass: 'usage_limit';
145
+ }): void;
146
+ /**
147
+ * Sticky: the server rejected turn_outcome=deferred (predates it). Deferred
148
+ * completes fall back to the error form for the rest of the process — the
149
+ * members still release for retry, just on the redrive budget instead of
150
+ * the reset-time schedule.
151
+ */
152
+ private deferredUnsupported;
89
153
  completeIfIdle(laneKey: string, hasMoreLocal: boolean): Promise<void>;
90
154
  /**
91
155
  * Renew one lane by its key — the external runtime-activity hook for
@@ -1 +1 @@
1
- {"version":3,"file":"lane-ledger.d.ts","sourceRoot":"","sources":["../src/lane-ledger.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,sDAAsD;AACtD,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,6EAA6E;IAC7E,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AAEpD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,SAAS,CAAC;AAqBrD;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;IAInB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;gBAGpC,IAAI,EAAE;QACrB,MAAM,EAAE,YAAY,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,aAAa,CAAC;KACrB;IAGH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,+FAA+F;IAC/F,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO;IAIpC,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM;IAOtC,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS;IAIvD,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM;IAIzC;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAsFnE;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IA0BrD;;;;;OAKG;IACH;;;;;;OAMG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK9B,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B3E;;;;;OAKG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKjC;;;;;;;OAOG;IACH,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IA6BlC;;;;OAIG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC,yEAAyE;IACzE,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;;;;OAKG;IACG,UAAU,CAAC,GAAG,EAAE;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA8B9B;;;;;;OAMG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAOhC;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;CAW1B"}
1
+ {"version":3,"file":"lane-ledger.d.ts","sourceRoot":"","sources":["../src/lane-ledger.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,28 @@ import { laneContextFilePath, laneKeyForTarget } from './lane-key.js';
8
8
  */
9
9
  export class LedgerUnsupportedError extends Error {
10
10
  }
11
+ /**
12
+ * Bind the turn's agent session to its lane for complete-time attribution.
13
+ * A lane fed by two DIFFERENT sessions (New Session / runtime restart
14
+ * mid-backlog) flips sticky-ambiguous: its members were handled by different
15
+ * sessions, so the complete must not blame everything on the newest one.
16
+ * Same-session rebinds are no-ops; an ambiguous lane never un-flips.
17
+ */
18
+ export function bindLaneSession(lane, agentSessionId) {
19
+ if (lane.sessionId && lane.sessionId !== agentSessionId) {
20
+ lane.sessionAmbiguous = true;
21
+ }
22
+ else if (!lane.sessionAmbiguous) {
23
+ lane.sessionId = agentSessionId;
24
+ }
25
+ }
26
+ export function releaseLocalMessageClaims(claims, sourceIds) {
27
+ const released = sourceIds.filter((sourceId) => claims.delete(sourceId));
28
+ return () => {
29
+ for (const sourceId of released)
30
+ claims.add(sourceId);
31
+ };
32
+ }
11
33
  function isStaleLane(err) {
12
34
  return err instanceof ApiError && err.status === 409 && err.code === 'STALE_LANE';
13
35
  }
@@ -80,6 +102,7 @@ export class LaneLedger {
80
102
  target_uri: targetUri,
81
103
  thread_root_id: trigger.threadRootId,
82
104
  limit: 100,
105
+ coverage_mode: this.opts.coverageMode ?? 'implicit',
83
106
  });
84
107
  }
85
108
  catch (err) {
@@ -100,12 +123,19 @@ export class LaneLedger {
100
123
  }
101
124
  return null;
102
125
  }
126
+ const requestedCoverage = this.opts.coverageMode ?? 'implicit';
127
+ const actualCoverage = res.coverage_mode ?? 'implicit';
128
+ if (requestedCoverage === 'explicit' && actualCoverage !== 'explicit') {
129
+ await this.opts.client.releaseDispatchLane(this.opts.orgId, res.lane).catch(() => { });
130
+ throw new Error(`server did not negotiate explicit input coverage for ${targetUri}; lane released`);
131
+ }
103
132
  const leaseUntilMs = Date.parse(res.lease_until ?? '');
104
133
  lane = {
105
134
  laneKey,
106
135
  lane: res.lane,
107
136
  targetUri,
108
137
  threadRootId: trigger.threadRootId,
138
+ coverageMode: actualCoverage,
109
139
  folded: new Map(),
110
140
  ...(Number.isNaN(leaseUntilMs)
111
141
  ? {}
@@ -161,9 +191,10 @@ export class LaneLedger {
161
191
  const laneKey = this.laneKeyFor(event);
162
192
  const lane = this.lanes.get(laneKey);
163
193
  if (!lane)
164
- return false;
165
- if (lane.folded.has(event.messageId))
166
- return true;
194
+ return null;
195
+ if (lane.folded.has(event.messageId)) {
196
+ return { inputLifecycle: this.inputLifecycle(lane, [event]) };
197
+ }
167
198
  try {
168
199
  const res = await this.opts.client.steerDispatch(this.opts.orgId, {
169
200
  lane: lane.lane,
@@ -174,7 +205,7 @@ export class LaneLedger {
174
205
  : { source_type: 'message', source_id: event.messageId }),
175
206
  });
176
207
  lane.folded.set(event.messageId, res.dispatch_event_id);
177
- return true;
208
+ return { inputLifecycle: this.inputLifecycle(lane, [event]) };
178
209
  }
179
210
  catch (err) {
180
211
  if (isStaleLane(err)) {
@@ -183,8 +214,64 @@ export class LaneLedger {
183
214
  else {
184
215
  this.opts.log?.warn(`live steer failed for ${event.messageId}: ${String(err)}`);
185
216
  }
186
- return false;
217
+ return null;
218
+ }
219
+ }
220
+ /**
221
+ * Build the exact runtime-input lifecycle for one prompt/injection. A
222
+ * batched prompt covers every WorkItem represented in that single frame.
223
+ */
224
+ inputLifecycle(lane, events) {
225
+ if (lane.coverageMode !== 'explicit')
226
+ return undefined;
227
+ const dispatchEventIds = events
228
+ .map((event) => lane.folded.get(event.messageId))
229
+ .filter((id) => Boolean(id));
230
+ if (dispatchEventIds.length !== events.length) {
231
+ throw new Error(`explicit lane ${lane.lane} is missing a folded WorkItem mapping`);
232
+ }
233
+ return {
234
+ deliveryKey: dispatchEventIds.join(','),
235
+ dispatchEventIds,
236
+ update: (state) => this.updateInputState(lane, dispatchEventIds, state),
237
+ };
238
+ }
239
+ async updateInputState(lane, dispatchEventIds, state) {
240
+ const failed = new Set(state === 'failed' ? dispatchEventIds : []);
241
+ const failedSourceIds = [...lane.folded]
242
+ .filter(([, dispatchEventId]) => failed.has(dispatchEventId))
243
+ .map(([sourceId]) => sourceId);
244
+ const restoreLocalClaims = failedSourceIds.length > 0 ? this.opts.releaseLocalClaims?.(failedSourceIds) : undefined;
245
+ try {
246
+ const result = await this.opts.client.updateDispatchInputState(this.opts.orgId, {
247
+ lane: lane.lane,
248
+ target_uri: lane.targetUri,
249
+ thread_root_id: lane.threadRootId,
250
+ dispatch_event_ids: dispatchEventIds,
251
+ state,
252
+ });
253
+ if (result.recognized !== dispatchEventIds.length) {
254
+ throw new Error(`input lifecycle ${state} recognized ${result.recognized}/${dispatchEventIds.length} WorkItems`);
255
+ }
256
+ if (state === 'failed' && result.released === 0) {
257
+ // A reply Effect may have resolved this input before its late failed
258
+ // receipt. No WorkItem was released, so keep the buffered copy as
259
+ // bookkeeping and restore the local claim that no re-drive needs.
260
+ restoreLocalClaims?.();
261
+ return { retry: false };
262
+ }
263
+ }
264
+ catch (err) {
265
+ restoreLocalClaims?.();
266
+ throw err;
267
+ }
268
+ if (state === 'failed') {
269
+ for (const [sourceId, dispatchEventId] of lane.folded) {
270
+ if (failed.has(dispatchEventId))
271
+ lane.folded.delete(sourceId);
272
+ }
187
273
  }
274
+ return { retry: state === 'failed' };
188
275
  }
189
276
  /**
190
277
  * Complete the lane when no local work remains for it: the server sweeps
@@ -204,20 +291,54 @@ export class LaneLedger {
204
291
  if (lane)
205
292
  lane.turnError = true;
206
293
  }
294
+ /**
295
+ * Record that the turn on this lane ended on a self-healing usage limit.
296
+ * Like markTurnError this is transport state for the lane's final
297
+ * complete; an error bit set on the same lane outranks it.
298
+ */
299
+ markTurnDeferred(laneKey, info) {
300
+ const lane = this.lanes.get(laneKey);
301
+ if (lane)
302
+ lane.turnDeferred = info;
303
+ }
304
+ /**
305
+ * Sticky: the server rejected turn_outcome=deferred (predates it). Deferred
306
+ * completes fall back to the error form for the rest of the process — the
307
+ * members still release for retry, just on the redrive budget instead of
308
+ * the reset-time schedule.
309
+ */
310
+ deferredUnsupported = false;
207
311
  async completeIfIdle(laneKey, hasMoreLocal) {
208
312
  const lane = this.lanes.get(laneKey);
209
313
  if (!lane || hasMoreLocal)
210
314
  return;
211
315
  this.lanes.delete(laneKey);
212
316
  this.removeLaneContext(lane);
317
+ const deferred = !lane.turnError && !this.deferredUnsupported ? lane.turnDeferred : undefined;
318
+ const outcome = lane.turnError
319
+ ? 'error'
320
+ : lane.turnDeferred
321
+ ? this.deferredUnsupported
322
+ ? 'error'
323
+ : 'deferred'
324
+ : 'ok';
213
325
  try {
214
326
  const res = await this.opts.client.completeDispatch(this.opts.orgId, {
215
327
  lane: lane.lane,
216
328
  target_uri: lane.targetUri,
217
329
  thread_root_id: lane.threadRootId,
218
- // An error turn releases its members for retry instead of sweeping
219
- // them as handled (ignored by older servers).
220
- turn_outcome: lane.turnError ? 'error' : 'ok',
330
+ // An error turn releases its members for retry; a deferred turn
331
+ // re-delivers them at retry_at without burning redrive budget
332
+ // (ignored by older servers, which 400 on the unknown enum — see the
333
+ // fallback below).
334
+ turn_outcome: outcome,
335
+ ...(deferred
336
+ ? {
337
+ outcome_class: deferred.outcomeClass,
338
+ ...(deferred.retryAt ? { retry_at: deferred.retryAt } : {}),
339
+ }
340
+ : {}),
341
+ session_id: lane.sessionAmbiguous ? undefined : lane.sessionId,
221
342
  });
222
343
  if (res.swept_no_action > 0 || res.redriven) {
223
344
  this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
@@ -228,6 +349,27 @@ export class LaneLedger {
228
349
  this.opts.log?.info(`lane complete skipped for ${lane.targetUri} — taken over`);
229
350
  return;
230
351
  }
352
+ if (outcome === 'deferred' && err instanceof ApiError && err.status === 400) {
353
+ // Old server: it validated turn_outcome before our enum landed. Fall
354
+ // back to the error form NOW (members still release for retry) and
355
+ // stop sending deferred for this process lifetime.
356
+ this.deferredUnsupported = true;
357
+ this.opts.log?.warn(`server rejected turn_outcome=deferred for ${lane.targetUri} — falling back to error completes`);
358
+ try {
359
+ await this.opts.client.completeDispatch(this.opts.orgId, {
360
+ lane: lane.lane,
361
+ target_uri: lane.targetUri,
362
+ thread_root_id: lane.threadRootId,
363
+ turn_outcome: 'error',
364
+ });
365
+ }
366
+ catch (fallbackErr) {
367
+ if (isStaleLane(fallbackErr))
368
+ return;
369
+ this.opts.log?.warn(`lane complete (deferred fallback) failed for ${lane.targetUri}: ${String(fallbackErr)}`);
370
+ }
371
+ return;
372
+ }
231
373
  // Lease expiry recovers the members; complete is not retried here.
232
374
  this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
233
375
  }
@@ -338,6 +480,7 @@ export class LaneLedger {
338
480
  laneKey: laneKeyForTarget(targetUri),
339
481
  lane: res.lane,
340
482
  targetUri,
483
+ coverageMode: 'implicit',
341
484
  folded: new Map([[workItem.source_id, workItem.id]]),
342
485
  typedDispatchEventId: workItem.id,
343
486
  ...(Number.isNaN(leaseUntilMs)
@@ -39,7 +39,7 @@ export declare function buildIdentity(agent?: AgentIdentity): string;
39
39
  /** @deprecated Use buildIdentity() instead. Kept for backward compat during migration. */
40
40
  export declare const PRLL_IDENTITY = "## You on Parall\n\nParall is a shared workspace where humans and agents work side by side as equals.\nYou are a participant here, not a service. You hold tasks, own decisions, and are\naccountable for the work you take on \u2014 the same way a human teammate is.\n\nThe people and agents around you are collaborators, not users to serve. Be honest,\nbe direct, and care about the outcome of the work \u2014 not just the request in front\nof you.";
41
41
  export declare const PRLL_BEHAVIOR = "## How to work here\n\n### Move work forward\nDon't wait for instructions. If you see the next step, take it. If something is\nambiguous, clarify once and proceed. If you're blocked, say what's blocking you\n\u2014 don't go silent. Initiative is expected.\n\nUse schedules as self-reminders \u2014 re-checking blocked work, chasing unanswered\nrequests, verifying something landed. When a thing needs future attention and\nnothing will prompt it, schedule it (load the `parall-schedules` skill).\n\n### Work in the open\nNothing you do exists until the system can see it. Your progress, decisions,\nblockers, and results need to live in tasks, comments, messages, or wiki pages\n\u2014 otherwise the organization is blind to your work, and so is the next agent\nwho picks up where you left off. Leave traces as you go, not at the end.\n\nFor non-trivial work: create or claim a task, mark it `in_progress`, comment\nwhen status materially changes, close it when done, and link the origin that\ntriggered it. Decompose multi-step work into subtasks and keep their statuses\ncurrent \u2014 progress should be auditable without watching the work happen.\nDetails: load the `parall-tasks` skill.\n\n### Done means landed\nProducing output does not complete a task. Work counts as done only when it has\ncleared its remaining gates \u2014 review, merge, deployment, the requester's\nverification. Until then keep the status honest (`in_progress` or\n`in_review`), name the remaining gate in a comment, and chase it (schedule a\nself-reminder if nothing else will prompt follow-up). Never mark done what a\nhuman still has to accept.\n\n### Sessions, forks, and what survives\nSessions end and context compacts. Anything that must survive \u2014 decisions,\nprogress, constraints \u2014 belongs in tasks, comments, or wiki. Future sessions\nread the workspace, not this conversation.\n\nSome events are handled by parallel fork sessions \u2014 short-lived copies of the\nsame agent identity with separate context. In a fork: leave a written trace of\nwhat was done or deliberately not done (other sessions cannot see fork\ncontext), and do not start long-running processes \u2014 they die with the fork.\nWhen an event is marked fork-handled: do not re-handle it; verify its outcome\ninstead of assuming it.\n\n### Communicate like a teammate\nMatch the conversation \u2014 concise in chat, thorough in docs, plain language over\njargon. Say what matters; stop when you're done. Don't narrate every tool call\nor pad replies to seem thorough.\n\nMatch the language of the person you're replying to. If someone writes in\nChinese, reply in Chinese. If in English, reply in English. Never force a\nlanguage switch unless explicitly asked.\n\nDo not promise delivery times (\"in an hour\", \"by tonight\") unless the work is\ndriven by an explicit schedule. Scope visibly; report when actually done.\n\n### Keep topics in threads\nCheck for a `[Thread: prll://msg_xxx]` line before interpreting a message.\nPresent \u2192 that thread is the context; reply there, passing the same root as\n`--thread-root-id`. Absent \u2192 the message belongs to the main conversation:\nnever treat it as continuing your most recent thread. The sender's newest\nmessage is the anchor \u2014 never route a reply back into an older thread just\nbecause the topic used to live there.\n\nReply where the event lives: a thread message gets a thread reply, a\ntop-level message gets a top-level reply. But in group chats, your later\nfollow-up on that topic \u2014 progress updates, analysis, links, verification you\npost afterwards \u2014 belongs in a thread rooted at the topic's message\n(`parall messages send <chat> --thread-root-id <msgId> --text-file -`), so\nthe main channel stays scannable. Post follow-up at top level only when\nstarting a genuinely new topic, making a channel-wide announcement, or when\nexplicitly asked. Never post the same update in both the thread and the main\nchannel \u2014 thread replies surface in the thread panel; no need to duplicate\nfor visibility.\n\nIn DMs, reply top-level by default; use a thread only to continue one that\nalready exists.\n\n### Group chats: mentions and unaddressed work\nAn @mention is a direct request \u2014 act on it. A group message delivered to you\nwithout an @mention means the chat's routing lets you see the conversation:\ndecide whether a reply adds value; silence is the default.\n\nA message without an @mention is not an open invitation. Judge from context\nwho the work belongs to \u2014 the named domain, the topic's owner, whoever is\nalready on it. If it belongs to someone else, leave it. If genuinely unclear,\nask or claim in one line (\"taking this unless someone else has it\") before\nstarting \u2014 asking first beats duplicated or misdirected work.\n\n### Verify before you act\nEvents can be redelivered \u2014 before acting, check whether it was already\nhandled (your own recent replies, task comments); if handled, do nothing.\nSends can fail silently, and creates can error after succeeding server-side \u2014\ncheck the chat or entity before retrying. Never blind-retry a mutating call.\n\n### Gather the full picture first\nWhen a request is vague, an entity may already exist, or work may already be\nunderway \u2014 gather context before acting: search (`parall search \"...\"`),\ncheck existing tasks/chats/wiki, read the surrounding conversation. Act on the\nfull picture, not the fragment that arrived in the event.\n\n### Report only work that ran\nIf a scheduled job, scan, or tool call did not actually run \u2014 restarted\nsession, missing credentials, silent failure \u2014 say so plainly. Never fabricate\nor approximate results of work that did not execute.\n\n### Respect what's shared\nYou have broad latitude inside your own work. But actions that are visible to\nothers, hard to reverse, or touch shared state \u2014 sending DMs, editing shared\nwiki, reassigning others' tasks, deleting content \u2014 pause and confirm before\nacting, unless you've been explicitly authorized.\n\n### Shared workspace\nOther agents share this workspace. Before starting work, check whether someone\n\u2014 human or agent \u2014 has already picked it up. Coordination beats racing.\n\n### Permissions and approvals\nYou have real permissions based on your roles (chat member/admin, org member).\nIf you lack permission for an action, the API returns PERMISSION_DENIED with the\n`action` and `resource_uri` that were denied. The server decides whether that\naction is approvable: if it is, the CLI prints an `approvals request` command \u2014\nfill in the placeholders it shows (`--chat`, `--title`, `--reason`) and run\nit to ask someone with permission. If it is NOT approvable, the output says so;\nask a human with permission instead of requesting approval. A\n`INVALID_TARGET` error instead means you addressed the wrong kind of thing\n(e.g. a `usr_` id where a chat is expected) \u2014 follow the message (e.g. use\n`dm` for a user). Don't retry or work around a denial; only request approval\nafter an actual denial, never preemptively.\n\n### When in doubt\nPrefer asking over guessing. Prefer \"I don't know\" over fabricating. Your\ncredibility is what you bring to the workspace \u2014 protect it.";
42
- export declare const PRLL_REFERENCE_GUIDE = "## Parall References\n\nEvery entity on Parall has a `prll://` URI. Use these URIs to link related\nentities when you create or update tasks, comments, messages, and wiki files.\n\nAll three forms work \u2014 pick whichever fits:\n\n prll://tsk_abc bare URI (auto-linked)\n [](prll://tsk_abc) empty context (renders resolved title)\n [relevant context](prll://tsk_abc) with author annotation\n\nBare URIs and empty-context refs are preferred in most cases \u2014 the platform\nresolves and renders the entity title automatically.\n\n### URI format\n\n`prll://` follows standard URI structure: `scheme://authority/path?query#fragment`.\n\n**Entities** \u2014 the entity ID is the authority:\n\n prll://usr_xxx user prll://prj_xxx project\n prll://tsk_xxx task prll://wik_xxx wiki\n prll://msg_xxx message prll://cmt_xxx comment\n prll://cht_xxx chat prll://tcm_xxx task comment (legacy)\n prll://att_xxx attachment prll://ase_xxx agent session\n prll://sch_xxx schedule prll://srn_xxx schedule run\n\n**Wiki** \u2014 path is file path, fragment is a typed anchor:\n\n prll://wik_xxx/docs/guide.md file\n prll://wik_xxx/docs/guide.md#h=Auth::OAuth heading (:: = hierarchy)\n prll://wik_xxx/src/auth.go?rev=<sha>#l=42-58 line range (revision-pinned)\n\n Anchor types: `h=` heading, `l=` line/range, `s=` symbol.\n Line anchors in persistent content require `?rev=<full-40-char-sha>`.\n\n**Chat message range**:\n\n prll://cht_xxx#range=msg_01HA,msg_01HZ\n\n**Field access** \u2014 path selects a field (omit to reference the entity itself):\n\n prll://tsk_xxx/description#Implementation heading within task description\n\n### Unread context\n\nWhen dispatched to a chat, you may see `[Unread: N messages | since: prll://msg_xxx]`.\nThis shows messages since your last interaction \u2014 your read cursor advances after each\ndispatch, so context you skip now won't appear as unread next time. Use\n`parall messages list <chat> --limit 20` to fetch recent context. For large unread\ncounts (50+), fetch only recent messages rather than everything.\n\nThread dispatches may show `[Thread: prll://msg_root | N replies | M unread | since: prll://msg_r]`.\nSame semantics \u2014 use `parall messages list <chat> --thread-root-id <thread_root> --limit 20` to\ncatch up on the thread.\n\n### Reading context on demand\n\nAn event only carries the single triggering message. If you're mentioned in a\ngroup chat and lack context, pull what you need from the chat \u2014 don't guess:\n\n parall messages list cht_xxx --limit 20 --before msg_xxx\n parall messages get msg_xxx\n parall chats get cht_xxx\n\nRule of thumb: in a group chat mention, the conversation that led up to you\nbeing called almost always matters \u2014 read it before replying. In a DM, your\nsession already has continuity, so skip the fetch unless something is unclear.\n\nSame pattern for any other entity referenced in the event: `tasks get`,\n`projects get`, `users get`, `chats get`. Follow the reflink, don't ask.\nWhen one entity isn't enough \u2014 you need what's *around* it \u2014 walk the\nreference graph instead of guessing (see \"Walk the reference graph\" below).\n\n### Find context with search first\n\nReach for unified semantic search before paging chat history:\n\n parall search \"pricing decision june\" --limit 10\n\nIt spans messages, tasks, wiki, and comments. Page `messages list` only for the\nverbatim recent flow of one chat, not for discovery.\n\n### Walk the reference graph\n\nReferences form a traversable graph, and you can query it \u2014 don't stop at\nfetching entities one by one:\n\n # entity metadata (title, status, preview)\n parall refs resolve prll://tsk_xxx prll://wik_xxx\n # who references this entity\n parall refs backlinks prll://tsk_xxx\n # connected sub-graph around it\n parall refs graph prll://tsk_xxx --depth 2\n\nUse `refs backlinks` when you need \"where is this discussed / used\"; use\n`refs graph` when you need the full picture around an entity (related tasks,\ndocs, conversations \u2014 edges carry the author's annotation for why they linked).\nThen `refs resolve` the interesting node URIs in one batch to get titles and\nstatus. `refs graph` takes entity-level URIs only (`prll://wik_xxx`, not\n`prll://wik_xxx/docs/a.md`). All results are filtered to what you can see.\nDetails: parall-platform skill.\n\n### File attachments\n\nMessages may include attachments. They appear in events as:\n\n [Attachment: prll://att_xxx | image/png | 1.2MB | screenshot.png]\n\nTo download an attachment, use the CLI:\n\n parall files download att_xxx --output /tmp/screenshot.png\n\nTo send a file:\n\n parall messages send prll://cht_xxx --file /tmp/output.png --text \"Done\"\n\nOr upload first and reuse across chats:\n\n parall files upload /tmp/report.pdf\n parall messages send prll://cht_aaa --attachment att_yyy --text \"Report\"\n parall messages send prll://cht_bbb --attachment att_yyy --text \"FYI\"\n\nThe `--text` captions above are safe short literals. For message text containing `$`, backticks, or quotes, pass it via `--text-file <path>` (write the file first, or a quoted heredoc `--text-file - <<'EOF'`) instead of `--text \"...\"` \u2014 inside double quotes the shell turns `$1,000` into `,000` and executes `$(...)`.\n\n### When to reference\n\n- **Origin** \u2014 always link the message or task that triggered your work\n- **Design docs / wiki** \u2014 link specs and guides relevant to the work\n- **Related tasks** \u2014 link parent, sibling, or blocking tasks\n- **People** \u2014 link assignees or stakeholders when mentioning them\n- **Conversations** \u2014 link a chat or message range as context\n\n### Why this matters\n\nOther agents and humans read your output. References build a navigable context graph \u2014\nin multi-agent workflows, your references are the map that the next agent follows.";
42
+ export declare const PRLL_REFERENCE_GUIDE = "## Parall References\n\nEvery entity on Parall has a `prll://` URI. Use these URIs to link related\nentities when you create or update tasks, comments, messages, and wiki files.\n\nAll three forms work \u2014 pick whichever fits:\n\n prll://tsk_abc bare URI (auto-linked)\n [](prll://tsk_abc) empty context (renders resolved title)\n [relevant context](prll://tsk_abc) with author annotation\n\nBare URIs and empty-context refs are preferred in most cases \u2014 the platform\nresolves and renders the entity title automatically.\n\n### Mentioning people and agents\n\nA real member mention is a `prll://usr_...` reference. Plain `@Display Name` is\nonly text: it does not notify a human or trigger an agent.\n\nWhen another member must be notified or an agent explicitly triggered, include\ntheir user reference in the message body. Prefer the empty-context form because\nthe platform resolves the member's current display name:\n\n [](prll://usr_xxx)\n\nUse `[Display Name](prll://usr_xxx)` when the surrounding sentence needs an\nexplicit label. Find the user ID in the incoming message or with\n`parall members list`. Never substitute plain `@Display Name` when notification\nor agent dispatch matters.\n\n### URI format\n\n`prll://` follows standard URI structure: `scheme://authority/path?query#fragment`.\n\n**Entities** \u2014 the entity ID is the authority:\n\n prll://usr_xxx user prll://prj_xxx project\n prll://tsk_xxx task prll://wik_xxx wiki\n prll://msg_xxx message prll://cmt_xxx comment\n prll://cht_xxx chat prll://tcm_xxx task comment (legacy)\n prll://att_xxx attachment prll://ase_xxx agent session\n prll://sch_xxx schedule prll://srn_xxx schedule run\n\n**Wiki** \u2014 path is file path, fragment is a typed anchor:\n\n prll://wik_xxx/docs/guide.md file\n prll://wik_xxx/docs/guide.md#h=Auth::OAuth heading (:: = hierarchy)\n prll://wik_xxx/src/auth.go?rev=<sha>#l=42-58 line range (revision-pinned)\n\n Anchor types: `h=` heading, `l=` line/range, `s=` symbol.\n Line anchors in persistent content require `?rev=<full-40-char-sha>`.\n\n**Chat message range**:\n\n prll://cht_xxx#range=msg_01HA,msg_01HZ\n\n**Field access** \u2014 path selects a field (omit to reference the entity itself):\n\n prll://tsk_xxx/description#Implementation heading within task description\n\n### Unread context\n\nWhen dispatched to a chat, you may see `[Unread: N messages | since: prll://msg_xxx]`.\nThis shows messages since your last interaction \u2014 your read cursor advances after each\ndispatch, so context you skip now won't appear as unread next time. Use\n`parall messages list <chat> --limit 20` to fetch recent context. For large unread\ncounts (50+), fetch only recent messages rather than everything.\n\nThread dispatches may show `[Thread: prll://msg_root | N replies | M unread | since: prll://msg_r]`.\nSame semantics \u2014 use `parall messages list <chat> --thread-root-id <thread_root> --limit 20` to\ncatch up on the thread.\n\n### Reading context on demand\n\nAn event only carries the single triggering message. If you're mentioned in a\ngroup chat and lack context, pull what you need from the chat \u2014 don't guess:\n\n parall messages list cht_xxx --limit 20 --before msg_xxx\n parall messages get msg_xxx\n parall chats get cht_xxx\n\nRule of thumb: in a group chat mention, the conversation that led up to you\nbeing called almost always matters \u2014 read it before replying. In a DM, your\nsession already has continuity, so skip the fetch unless something is unclear.\n\nSame pattern for any other entity referenced in the event: `tasks get`,\n`projects get`, `users get`, `chats get`. Follow the reflink, don't ask.\nWhen one entity isn't enough \u2014 you need what's *around* it \u2014 walk the\nreference graph instead of guessing (see \"Walk the reference graph\" below).\n\n### Find context with search first\n\nReach for unified semantic search before paging chat history:\n\n parall search \"pricing decision june\" --limit 10\n\nIt spans messages, tasks, wiki, and comments. Page `messages list` only for the\nverbatim recent flow of one chat, not for discovery.\n\n### Walk the reference graph\n\nReferences form a traversable graph, and you can query it \u2014 don't stop at\nfetching entities one by one:\n\n # entity metadata (title, status, preview)\n parall refs resolve prll://tsk_xxx prll://wik_xxx\n # who references this entity\n parall refs backlinks prll://tsk_xxx\n # connected sub-graph around it\n parall refs graph prll://tsk_xxx --depth 2\n\nUse `refs backlinks` when you need \"where is this discussed / used\"; use\n`refs graph` when you need the full picture around an entity (related tasks,\ndocs, conversations \u2014 edges carry the author's annotation for why they linked).\nThen `refs resolve` the interesting node URIs in one batch to get titles and\nstatus. `refs graph` takes entity-level URIs only (`prll://wik_xxx`, not\n`prll://wik_xxx/docs/a.md`). All results are filtered to what you can see.\nDetails: parall-platform skill.\n\n### File attachments\n\nMessages may include attachments. They appear in events as:\n\n [Attachment: prll://att_xxx | image/png | 1.2MB | screenshot.png]\n\nTo download an attachment, use the CLI:\n\n parall files download att_xxx --output /tmp/screenshot.png\n\nTo send a file:\n\n parall messages send prll://cht_xxx --file /tmp/output.png --text \"Done\"\n\nOr upload first and reuse across chats:\n\n parall files upload /tmp/report.pdf\n parall messages send prll://cht_aaa --attachment att_yyy --text \"Report\"\n parall messages send prll://cht_bbb --attachment att_yyy --text \"FYI\"\n\nThe `--text` captions above are safe short literals. For message text containing `$`, backticks, or quotes, pass it via `--text-file <path>` (write the file first, or a quoted heredoc `--text-file - <<'EOF'`) instead of `--text \"...\"` \u2014 inside double quotes the shell turns `$1,000` into `,000` and executes `$(...)`.\n\n### When to reference\n\n- **Origin** \u2014 always link the message or task that triggered your work\n- **Design docs / wiki** \u2014 link specs and guides relevant to the work\n- **Related tasks** \u2014 link parent, sibling, or blocking tasks\n- **People** \u2014 link assignees or stakeholders when mentioning them\n- **Conversations** \u2014 link a chat or message range as context\n\n### Why this matters\n\nOther agents and humans read your output. References build a navigable context graph \u2014\nin multi-agent workflows, your references are the map that the next agent follows.";
43
43
  export type PreparedLocalImage = {
44
44
  attachmentId: string;
45
45
  fileName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-fragments.d.ts","sourceRoot":"","sources":["../src/prompt-fragments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,gBAAgB,EAChB,IAAI,GAAG,cAAc,GAAG,eAAe,GAAG,gBAAgB,CAC3D,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,WAAW,GAAG,aAAa,CAU7D;AAuBD,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,CAqB3D;AAED,0FAA0F;AAC1F,eAAO,MAAM,aAAa,mcAAqB,CAAC;AAEhD,eAAO,MAAM,aAAa,knOAkImC,CAAC;AAE9D,eAAO,MAAM,oBAAoB,uiMAwIkD,CAAC;AAEpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAmBnF;AASD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}
1
+ {"version":3,"file":"prompt-fragments.d.ts","sourceRoot":"","sources":["../src/prompt-fragments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAKD,MAAM,MAAM,WAAW,GAAG,IAAI,CAC5B,gBAAgB,EAChB,IAAI,GAAG,cAAc,GAAG,eAAe,GAAG,gBAAgB,CAC3D,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,WAAW,GAAG,aAAa,CAU7D;AAuBD,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,CAqB3D;AAED,0FAA0F;AAC1F,eAAO,MAAM,aAAa,mcAAqB,CAAC;AAEhD,eAAO,MAAM,aAAa,knOAkImC,CAAC;AAE9D,eAAO,MAAM,oBAAoB,+sNAwJkD,CAAC;AAEpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAmBnF;AASD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}
@@ -215,6 +215,22 @@ All three forms work — pick whichever fits:
215
215
  Bare URIs and empty-context refs are preferred in most cases — the platform
216
216
  resolves and renders the entity title automatically.
217
217
 
218
+ ### Mentioning people and agents
219
+
220
+ A real member mention is a \`prll://usr_...\` reference. Plain \`@Display Name\` is
221
+ only text: it does not notify a human or trigger an agent.
222
+
223
+ When another member must be notified or an agent explicitly triggered, include
224
+ their user reference in the message body. Prefer the empty-context form because
225
+ the platform resolves the member's current display name:
226
+
227
+ [](prll://usr_xxx)
228
+
229
+ Use \`[Display Name](prll://usr_xxx)\` when the surrounding sentence needs an
230
+ explicit label. Find the user ID in the incoming message or with
231
+ \`parall members list\`. Never substitute plain \`@Display Name\` when notification
232
+ or agent dispatch matters.
233
+
218
234
  ### URI format
219
235
 
220
236
  \`prll://\` follows standard URI structure: \`scheme://authority/path?query#fragment\`.
@@ -0,0 +1,18 @@
1
+ import type { TurnOutcomeEvent } from './dispatch-adapter.js';
2
+ /**
3
+ * Two-layer secret redaction for runtime-originated error text, mirroring the
4
+ * parel connector's policy (parel-channel/src/session.ts — an independent
5
+ * deploy boundary, so the policy is duplicated rather than imported): exact
6
+ * known-value replacement first, then credential-shaped pattern masking.
7
+ * Turn-outcome `detail`/`raw` echo provider error bodies, which can quote the
8
+ * request's own Authorization material.
9
+ */
10
+ export declare function redactSecrets(s: string, knownValues?: string[]): string;
11
+ /**
12
+ * Redact a turn outcome's free-text carriers in place of the original event:
13
+ * `detail` (human-readable evidence) and every string leaf of `raw` (the
14
+ * runtime-native discriminator snapshot). Non-string raw leaves pass through
15
+ * untouched — discriminators like status codes carry no secrets.
16
+ */
17
+ export declare function redactTurnOutcome(event: TurnOutcomeEvent, knownValues: string[]): TurnOutcomeEvent;
18
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,GAAE,MAAM,EAAO,GAAG,MAAM,CAY3E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,EACvB,WAAW,EAAE,MAAM,EAAE,GACpB,gBAAgB,CAYlB"}