@parall/parel-channel 1.57.1 → 1.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,8 +36,12 @@ export interface ChatPromptArgs {
36
36
  chatId: string;
37
37
  /** Display name of the chat; undefined when unresolved (best-effort). */
38
38
  chatName?: string;
39
- /** Sender user id (usr_); display-name enrichment is a follow-up. */
39
+ /** Sender user id (usr_). */
40
40
  senderId?: string;
41
+ /** Sender display name; rendered before the id when resolved (best-effort). */
42
+ senderName?: string;
43
+ /** Chat shape (direct/group); the type segment is omitted when unresolved. */
44
+ chatType?: string;
41
45
  /**
42
46
  * The inbound message id (msg_); anchors the forwarded-message hint's
43
47
  * explicit --from so coalesced turns don't lean on the single trigger id.
@@ -1 +1 @@
1
- {"version":3,"file":"channel-prompt.d.ts","sourceRoot":"","sources":["../src/channel-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA+BH,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;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,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,CAqB5D;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,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,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,CA2BlE"}
1
+ {"version":3,"file":"channel-prompt.d.ts","sourceRoot":"","sources":["../src/channel-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA+BH,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,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,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,CAwC5D;AAgBD,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,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,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,CA2BlE"}
@@ -73,16 +73,31 @@ function attachmentLines(attachments) {
73
73
  * not re-created here.
74
74
  */
75
75
  export function buildChatPrompt(args) {
76
- const chatLabel = args.chatName
77
- ? `${sanitizeMeta(args.chatId)} ${sanitizeMeta(args.chatName)}`
78
- : sanitizeMeta(args.chatId);
79
- const lines = [`[Chat: ${chatLabel}]`];
80
- if (args.senderId)
81
- lines.push(`[From: ${sanitizeMeta(args.senderId)}]`);
76
+ // agent-core's event-format.ts message branch is the SSOT for this frame:
77
+ // same lines, same order, same send-hint. The only sanctioned deltas are
78
+ // absent-field fallbacks (no chat name → bare URI, no sender name → bare
79
+ // id, no chat type → segment omitted) — agent-core's host always has them.
80
+ const lines = [`[Event: message.new]`];
81
+ const chatUri = `prll://${args.chatId}`;
82
+ const chatLabel = args.chatName ? `"${sanitizeMeta(args.chatName)}" (${chatUri})` : chatUri;
83
+ const typeSeg = args.chatType ? ` | type: ${sanitizeMeta(args.chatType)}` : '';
84
+ lines.push(`[Chat: ${chatLabel}${typeSeg}]`);
85
+ if (args.senderId) {
86
+ lines.push(args.senderName
87
+ ? `[From: ${sanitizeMeta(args.senderName)} (prll://${args.senderId})]`
88
+ : `[From: prll://${args.senderId}]`);
89
+ }
90
+ if (args.messageId)
91
+ lines.push(`[Message ID: prll://${args.messageId}]`);
82
92
  if (args.threadRootId)
83
- lines.push(`[Thread: ${sanitizeMeta(args.threadRootId)}]`);
93
+ lines.push(`[Thread: prll://${sanitizeMeta(args.threadRootId)}]`);
84
94
  if (args.deliveryReason)
85
95
  lines.push(`[Delivery: ${sanitizeMeta(args.deliveryReason)}]`);
96
+ if (args.deliveryReason === 'watcher' && args.threadRootId) {
97
+ // agent-core's threadWatcherHint, verbatim — the cooperative nudge under
98
+ // the server's agent-loop ceiling (agent-dm-loop-prevention.md Layer 4).
99
+ lines.push(`[Hint: watcher — you were not addressed in this reply. Reply only if you add something new; otherwise run \`parall no-reply\`. If this thread is not relevant to you, or you have nothing more to contribute, run \`parall messages unwatch prll://${sanitizeMeta(args.threadRootId)}\` so later replies stop waking you.]`);
100
+ }
86
101
  if (args.noReply)
87
102
  lines.push(`[Hint: no_reply]`);
88
103
  if (isForwardedMessageBody(args.text)) {
@@ -94,7 +109,21 @@ export function buildChatPrompt(args) {
94
109
  }
95
110
  lines.push(...attachmentLines(args.attachments));
96
111
  lines.push('', args.text);
97
- return lines.join('\n');
112
+ return lines.join('\n') + chatSendHint(args);
113
+ }
114
+ /**
115
+ * agent-core's buildSendMessageHint chat branch, verbatim: every chat event
116
+ * ends with the concrete send command. The standing system prompt teaches the
117
+ * CLI-only reply contract too, but a per-event reminder is what the standard
118
+ * runtimes ship — and weak models demonstrably drop the send step without it.
119
+ */
120
+ function chatSendHint(args) {
121
+ if (args.noReply)
122
+ return '';
123
+ if (args.threadRootId) {
124
+ return `\n<system-reminder>To reply in this thread, run \`parall messages send prll://${args.chatId} --thread-root-id ${sanitizeMeta(args.threadRootId)} --text-file - <<'EOF'\` … \`EOF\` — the quoted heredoc keeps \`$\`, backticks and apostrophes literal (plain \`--text "$1,000"\` sends \`,000\`). Your plain text output is not delivered to the chat.</system-reminder>`;
125
+ }
126
+ return `\n<system-reminder>To reply, run \`parall messages send prll://${args.chatId} --text-file - <<'EOF'\` … \`EOF\` — the quoted heredoc keeps \`$\`, backticks and apostrophes literal (plain \`--text "$1,000"\` sends \`,000\`). Your plain text output is not delivered to the chat.</system-reminder>`;
98
127
  }
99
128
  export function buildChannelPrompt(args) {
100
129
  const providerLabel = sanitizeMeta(args.provider ?? 'external IM');
@@ -342,7 +342,16 @@ async function planApprovalDecided(ctx, approvalId, chatId, actor) {
342
342
  if (!approval?.id)
343
343
  return STALE;
344
344
  const statusLabel = approval.status === 'approved' ? 'Approved' : 'Rejected';
345
- const execInfo = approval.execution_status ? ` | execution: ${approval.execution_status}` : '';
345
+ // A failed execution names its cause on the same line — the agent must not
346
+ // need a second read to tell a version conflict from anything else.
347
+ // agent-core's formatApprovalExecution and the server's Go materializer
348
+ // render the identical suffix; keep the three in sync.
349
+ const execError = approval.execution_error
350
+ ? ` | error: ${sanitizeMeta(approval.execution_error)}`
351
+ : '';
352
+ const execInfo = approval.execution_status
353
+ ? ` | execution: ${approval.execution_status}${execError}`
354
+ : '';
346
355
  const resolvedChat = chatId ?? approval.chat_id;
347
356
  const lines = [`[Event: approval.decided]`];
348
357
  lines.push(`[Approval: prll://${approval.id}]`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/parel-channel",
3
- "version": "1.57.1",
3
+ "version": "1.58.0",
4
4
  "description": "Parall channel plugin for the parel runtime — lets a parel agent treat Parall as a managed_ws channel (receive dispatches, reply via the Parall API)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -67,8 +67,12 @@ export interface ChatPromptArgs {
67
67
  chatId: string;
68
68
  /** Display name of the chat; undefined when unresolved (best-effort). */
69
69
  chatName?: string;
70
- /** Sender user id (usr_); display-name enrichment is a follow-up. */
70
+ /** Sender user id (usr_). */
71
71
  senderId?: string;
72
+ /** Sender display name; rendered before the id when resolved (best-effort). */
73
+ senderName?: string;
74
+ /** Chat shape (direct/group); the type segment is omitted when unresolved. */
75
+ chatType?: string;
72
76
  /**
73
77
  * The inbound message id (msg_); anchors the forwarded-message hint's
74
78
  * explicit --from so coalesced turns don't lean on the single trigger id.
@@ -113,13 +117,32 @@ function attachmentLines(attachments: PromptAttachment[] | undefined): string[]
113
117
  * not re-created here.
114
118
  */
115
119
  export function buildChatPrompt(args: ChatPromptArgs): string {
116
- const chatLabel = args.chatName
117
- ? `${sanitizeMeta(args.chatId)} ${sanitizeMeta(args.chatName)}`
118
- : sanitizeMeta(args.chatId);
119
- const lines = [`[Chat: ${chatLabel}]`];
120
- if (args.senderId) lines.push(`[From: ${sanitizeMeta(args.senderId)}]`);
121
- if (args.threadRootId) lines.push(`[Thread: ${sanitizeMeta(args.threadRootId)}]`);
120
+ // agent-core's event-format.ts message branch is the SSOT for this frame:
121
+ // same lines, same order, same send-hint. The only sanctioned deltas are
122
+ // absent-field fallbacks (no chat name → bare URI, no sender name → bare
123
+ // id, no chat type → segment omitted) — agent-core's host always has them.
124
+ const lines = [`[Event: message.new]`];
125
+ const chatUri = `prll://${args.chatId}`;
126
+ const chatLabel = args.chatName ? `"${sanitizeMeta(args.chatName)}" (${chatUri})` : chatUri;
127
+ const typeSeg = args.chatType ? ` | type: ${sanitizeMeta(args.chatType)}` : '';
128
+ lines.push(`[Chat: ${chatLabel}${typeSeg}]`);
129
+ if (args.senderId) {
130
+ lines.push(
131
+ args.senderName
132
+ ? `[From: ${sanitizeMeta(args.senderName)} (prll://${args.senderId})]`
133
+ : `[From: prll://${args.senderId}]`,
134
+ );
135
+ }
136
+ if (args.messageId) lines.push(`[Message ID: prll://${args.messageId}]`);
137
+ if (args.threadRootId) lines.push(`[Thread: prll://${sanitizeMeta(args.threadRootId)}]`);
122
138
  if (args.deliveryReason) lines.push(`[Delivery: ${sanitizeMeta(args.deliveryReason)}]`);
139
+ if (args.deliveryReason === 'watcher' && args.threadRootId) {
140
+ // agent-core's threadWatcherHint, verbatim — the cooperative nudge under
141
+ // the server's agent-loop ceiling (agent-dm-loop-prevention.md Layer 4).
142
+ lines.push(
143
+ `[Hint: watcher — you were not addressed in this reply. Reply only if you add something new; otherwise run \`parall no-reply\`. If this thread is not relevant to you, or you have nothing more to contribute, run \`parall messages unwatch prll://${sanitizeMeta(args.threadRootId)}\` so later replies stop waking you.]`,
144
+ );
145
+ }
123
146
  if (args.noReply) lines.push(`[Hint: no_reply]`);
124
147
  if (isForwardedMessageBody(args.text)) {
125
148
  // Embed the concrete --from anchor: parel snapshots one invocation
@@ -132,7 +155,21 @@ export function buildChatPrompt(args: ChatPromptArgs): string {
132
155
  }
133
156
  lines.push(...attachmentLines(args.attachments));
134
157
  lines.push('', args.text);
135
- return lines.join('\n');
158
+ return lines.join('\n') + chatSendHint(args);
159
+ }
160
+
161
+ /**
162
+ * agent-core's buildSendMessageHint chat branch, verbatim: every chat event
163
+ * ends with the concrete send command. The standing system prompt teaches the
164
+ * CLI-only reply contract too, but a per-event reminder is what the standard
165
+ * runtimes ship — and weak models demonstrably drop the send step without it.
166
+ */
167
+ function chatSendHint(args: ChatPromptArgs): string {
168
+ if (args.noReply) return '';
169
+ if (args.threadRootId) {
170
+ return `\n<system-reminder>To reply in this thread, run \`parall messages send prll://${args.chatId} --thread-root-id ${sanitizeMeta(args.threadRootId)} --text-file - <<'EOF'\` … \`EOF\` — the quoted heredoc keeps \`$\`, backticks and apostrophes literal (plain \`--text "$1,000"\` sends \`,000\`). Your plain text output is not delivered to the chat.</system-reminder>`;
171
+ }
172
+ return `\n<system-reminder>To reply, run \`parall messages send prll://${args.chatId} --text-file - <<'EOF'\` … \`EOF\` — the quoted heredoc keeps \`$\`, backticks and apostrophes literal (plain \`--text "$1,000"\` sends \`,000\`). Your plain text output is not delivered to the chat.</system-reminder>`;
136
173
  }
137
174
 
138
175
  export interface ChannelPromptArgs {
@@ -508,6 +508,7 @@ interface ApprovalRow {
508
508
  title?: string;
509
509
  status?: string;
510
510
  execution_status?: string;
511
+ execution_error?: string | null;
511
512
  chat_id?: string;
512
513
  decided_by?: string;
513
514
  decision_reason?: string | null;
@@ -532,7 +533,16 @@ async function planApprovalDecided(
532
533
  if (!approval?.id) return STALE;
533
534
 
534
535
  const statusLabel = approval.status === 'approved' ? 'Approved' : 'Rejected';
535
- const execInfo = approval.execution_status ? ` | execution: ${approval.execution_status}` : '';
536
+ // A failed execution names its cause on the same line — the agent must not
537
+ // need a second read to tell a version conflict from anything else.
538
+ // agent-core's formatApprovalExecution and the server's Go materializer
539
+ // render the identical suffix; keep the three in sync.
540
+ const execError = approval.execution_error
541
+ ? ` | error: ${sanitizeMeta(approval.execution_error)}`
542
+ : '';
543
+ const execInfo = approval.execution_status
544
+ ? ` | execution: ${approval.execution_status}${execError}`
545
+ : '';
536
546
  const resolvedChat = chatId ?? approval.chat_id;
537
547
 
538
548
  const lines = [`[Event: approval.decided]`];