@parall/agent-core 1.31.0 → 1.32.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.
Files changed (58) hide show
  1. package/dist/bridge-workspace.js +12 -12
  2. package/dist/dispatch-adapter.d.ts +15 -8
  3. package/dist/dispatch-adapter.d.ts.map +1 -1
  4. package/dist/event-format.d.ts +1 -1
  5. package/dist/event-format.d.ts.map +1 -1
  6. package/dist/event-format.js +68 -25
  7. package/dist/gateway-base.d.ts +14 -13
  8. package/dist/gateway-base.d.ts.map +1 -1
  9. package/dist/gateway-base.js +650 -313
  10. package/dist/index.d.ts +15 -13
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +13 -12
  13. package/dist/internal/attachment-input.d.ts +3 -3
  14. package/dist/internal/attachment-input.d.ts.map +1 -1
  15. package/dist/internal/attachment-input.js +61 -58
  16. package/dist/logger.d.ts +1 -1
  17. package/dist/platform-config.d.ts +28 -2
  18. package/dist/platform-config.d.ts.map +1 -1
  19. package/dist/platform-config.js +42 -11
  20. package/dist/prompt-fragments.d.ts +1 -1
  21. package/dist/prompt-fragments.d.ts.map +1 -1
  22. package/dist/prompt-fragments.js +28 -10
  23. package/dist/provider-config.d.ts +9 -0
  24. package/dist/provider-config.d.ts.map +1 -1
  25. package/dist/provider-config.js +13 -2
  26. package/dist/routing.d.ts +5 -5
  27. package/dist/routing.js +6 -6
  28. package/dist/session-state.d.ts +16 -0
  29. package/dist/session-state.d.ts.map +1 -1
  30. package/dist/session-state.js +45 -0
  31. package/dist/skills/index.d.ts +5 -4
  32. package/dist/skills/index.d.ts.map +1 -1
  33. package/dist/skills/index.js +28 -21
  34. package/dist/skills/parall-clips.d.ts +2 -0
  35. package/dist/skills/parall-clips.d.ts.map +1 -0
  36. package/dist/skills/parall-clips.js +44 -0
  37. package/dist/telemetry.d.ts +27 -0
  38. package/dist/telemetry.d.ts.map +1 -0
  39. package/dist/telemetry.js +205 -0
  40. package/dist/types.d.ts +18 -2
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +11 -2
  43. package/src/bridge-workspace.ts +12 -12
  44. package/src/dispatch-adapter.ts +31 -8
  45. package/src/event-format.ts +80 -30
  46. package/src/gateway-base.ts +988 -445
  47. package/src/index.ts +23 -13
  48. package/src/internal/attachment-input.ts +127 -100
  49. package/src/logger.ts +1 -1
  50. package/src/platform-config.ts +61 -16
  51. package/src/prompt-fragments.ts +28 -10
  52. package/src/provider-config.ts +14 -2
  53. package/src/routing.ts +11 -11
  54. package/src/session-state.ts +62 -0
  55. package/src/skills/index.ts +34 -23
  56. package/src/skills/parall-clips.ts +44 -0
  57. package/src/telemetry.ts +252 -0
  58. package/src/types.ts +18 -2
@@ -84,9 +84,9 @@ Messages may arrive with a \`[Thread: prll://msg_xxx]\` line in the event block,
84
84
 
85
85
  /** Extracts the `command` string from a shell/bash tool call's input payload. */
86
86
  export function extractShellCommand(input: unknown): string | undefined {
87
- if (!input || typeof input !== "object") return undefined;
87
+ if (!input || typeof input !== 'object') return undefined;
88
88
  const command = (input as { command?: unknown }).command;
89
- return typeof command === "string" && command.trim() ? command.trim() : undefined;
89
+ return typeof command === 'string' && command.trim() ? command.trim() : undefined;
90
90
  }
91
91
 
92
92
  /**
@@ -101,24 +101,24 @@ export function extractShellCommand(input: unknown): string | undefined {
101
101
  * `no-reply` subcommand).
102
102
  */
103
103
  export function parseParallCliInvocation(command: string): string[] | null {
104
- const tokens = command.replace(/\s+/g, " ").trim().split(" ");
104
+ const tokens = command.replace(/\s+/g, ' ').trim().split(' ');
105
105
  let i = 0;
106
- if (tokens[i] === "parall") {
106
+ if (tokens[i] === 'parall') {
107
107
  i++;
108
- } else if (tokens[i] === "npx") {
108
+ } else if (tokens[i] === 'npx') {
109
109
  i++;
110
- while (i < tokens.length && tokens[i].startsWith("-")) i++;
110
+ while (i < tokens.length && tokens[i].startsWith('-')) i++;
111
111
  if (i >= tokens.length || !/^@parall\/cli(?:@.+)?$/.test(tokens[i])) return null;
112
112
  i++;
113
- } else if (tokens[i] === "pnpm") {
113
+ } else if (tokens[i] === 'pnpm') {
114
114
  i++;
115
- if (i < tokens.length && (tokens[i] === "exec" || tokens[i] === "dlx")) i++;
116
- if (i >= tokens.length || tokens[i] !== "parall") return null;
115
+ if (i < tokens.length && (tokens[i] === 'exec' || tokens[i] === 'dlx')) i++;
116
+ if (i >= tokens.length || tokens[i] !== 'parall') return null;
117
117
  i++;
118
118
  } else {
119
119
  return null;
120
120
  }
121
- return tokens.slice(i).filter((t) => !t.startsWith("-"));
121
+ return tokens.slice(i).filter((t) => !t.startsWith('-'));
122
122
  }
123
123
 
124
124
  /**
@@ -132,7 +132,7 @@ export function isParallSendCommand(command: string | undefined): boolean {
132
132
  if (!command) return false;
133
133
  const sub = parseParallCliInvocation(command);
134
134
  if (!sub || sub.length === 0) return false;
135
- return sub[0] === "dm" || (sub[0] === "messages" && sub[1] === "send");
135
+ return sub[0] === 'dm' || (sub[0] === 'messages' && sub[1] === 'send');
136
136
  }
137
137
 
138
138
  /**
@@ -144,5 +144,5 @@ export function isParallSendCommand(command: string | undefined): boolean {
144
144
  export function isParallNoReplyCommand(command: string | undefined): boolean {
145
145
  if (!command) return false;
146
146
  const sub = parseParallCliInvocation(command);
147
- return sub?.[0] === "no-reply";
147
+ return sub?.[0] === 'no-reply';
148
148
  }
@@ -1,5 +1,5 @@
1
- import type { ParallClient } from "@parall/sdk";
2
- import type { ParallEvent } from "./types.js";
1
+ import type { ParallClient } from '@parall/sdk';
2
+ import type { ParallEvent } from './types.js';
3
3
 
4
4
  export type GatewayLogger = {
5
5
  info: (msg: string) => void;
@@ -29,16 +29,31 @@ export type DispatchContext = {
29
29
 
30
30
  export type RuntimeEvent =
31
31
  | {
32
- type: "runtime_session";
32
+ type: 'runtime_session';
33
33
  runtimeSessionId: string;
34
34
  runtimeLaneKey?: string;
35
35
  runtimeRef?: Record<string, unknown>;
36
36
  }
37
- | { type: "thinking"; text: string; groupKey?: string }
38
- | { type: "tool_call"; callId: string; toolName: string; input: unknown; startedAt?: string; groupKey?: string }
39
- | { type: "tool_result"; callId: string; toolName: string; output: string; error?: string; durationMs?: number; groupKey?: string }
40
- | { type: "text"; text: string; project?: boolean; groupKey?: string }
41
- | { type: "error"; message: string };
37
+ | { type: 'thinking'; text: string; groupKey?: string }
38
+ | {
39
+ type: 'tool_call';
40
+ callId: string;
41
+ toolName: string;
42
+ input: unknown;
43
+ startedAt?: string;
44
+ groupKey?: string;
45
+ }
46
+ | {
47
+ type: 'tool_result';
48
+ callId: string;
49
+ toolName: string;
50
+ output: string;
51
+ error?: string;
52
+ durationMs?: number;
53
+ groupKey?: string;
54
+ }
55
+ | { type: 'text'; text: string; project?: boolean; groupKey?: string }
56
+ | { type: 'error'; message: string };
42
57
 
43
58
  export type DispatchOpts = {
44
59
  event: ParallEvent;
@@ -98,4 +113,12 @@ export interface DispatchAdapter {
98
113
 
99
114
  /** Return the path to the session's history file on disk, if the runtime persists it. */
100
115
  getSessionHistoryPath?(sessionKey: string): string | undefined;
116
+
117
+ /**
118
+ * Abort the in-flight dispatch for the given session. Called by the gateway
119
+ * when the dispatch deadline is exceeded. Implementations should unblock the
120
+ * `dispatch()` generator (e.g. close a turn sink, end stdin, fail a stream)
121
+ * so the `for await` loop in `runDispatch` exits naturally. Must be idempotent.
122
+ */
123
+ abortDispatch?(sessionKey: string): void;
101
124
  }
@@ -1,31 +1,58 @@
1
- import type { ForkResult, ParallEvent } from "./types.js";
1
+ import type { ForkResult, ParallEvent } from './types.js';
2
2
 
3
3
  function sanitizeMeta(value: string): string {
4
- return value.replace(/[\r\n]+/g, " ").replace(/[[\]|]/g, " ").trim();
4
+ return value
5
+ .replace(/[\r\n]+/g, ' ')
6
+ .replace(/[[\]|]/g, ' ')
7
+ .trim();
5
8
  }
6
9
 
7
10
  export function buildEventBody(event: ParallEvent): string {
8
11
  const lines: string[] = [];
9
- if (event.type === "message") {
12
+ if (event.type === 'message') {
10
13
  lines.push(`[Event: message.new]`);
11
14
  const chatLabel = event.targetName
12
15
  ? `"${event.targetName}" (prll://${event.targetId})`
13
16
  : `prll://${event.targetId}`;
14
- lines.push(`[Chat: ${chatLabel} | type: ${event.targetType ?? "unknown"}]`);
17
+ lines.push(`[Chat: ${chatLabel} | type: ${event.targetType ?? 'unknown'}]`);
15
18
  lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
16
19
  lines.push(`[Message ID: prll://${event.messageId}]`);
17
- if (event.threadRootId) lines.push(`[Thread: prll://${event.threadRootId}]`);
20
+ if (event.threadRootId) {
21
+ const threadMeta = [
22
+ `prll://${event.threadRootId}`,
23
+ event.threadReplyCount != null ? `${event.threadReplyCount} replies` : null,
24
+ event.threadUnreadCount != null && event.threadUnreadCount > 0
25
+ ? `${event.threadUnreadCount} unread`
26
+ : null,
27
+ event.threadUnreadCount != null && event.threadUnreadCount > 0 && event.threadUnreadSince
28
+ ? `since: prll://${event.threadUnreadSince}`
29
+ : null,
30
+ ]
31
+ .filter(Boolean)
32
+ .join(' | ');
33
+ lines.push(`[Thread: ${threadMeta}]`);
34
+ }
35
+ if (event.unreadCount != null && event.unreadCount > 1) {
36
+ const countStr = event.unreadCount >= 1000 ? '999+' : String(event.unreadCount);
37
+ const sinceStr = event.unreadSince ? ` | since: prll://${event.unreadSince}` : '';
38
+ let line = `[Unread: ${countStr} messages${sinceStr}]`;
39
+ if (event.unreadCount > 50) line += ` — fetch recent context with --limit, not all`;
40
+ lines.push(line);
41
+ }
18
42
  if (event.noReply) lines.push(`[Hint: no_reply]`);
19
43
  if (event.attachments?.length) {
20
44
  for (const att of event.attachments) {
21
- const sizeStr = att.fileSize >= 1048576
22
- ? `${(att.fileSize / 1048576).toFixed(1)}MB`
23
- : `${Math.round(att.fileSize / 1024)}KB`;
24
- lines.push(`[Attachment: prll://${att.id} | ${sanitizeMeta(att.mimeType)} | ${sizeStr} | ${sanitizeMeta(att.fileName)}]`);
45
+ const sizeStr =
46
+ att.fileSize >= 1048576
47
+ ? `${(att.fileSize / 1048576).toFixed(1)}MB`
48
+ : `${Math.round(att.fileSize / 1024)}KB`;
49
+ lines.push(
50
+ `[Attachment: prll://${att.id} | ${sanitizeMeta(att.mimeType)} | ${sizeStr} | ${sanitizeMeta(att.fileName)}]`,
51
+ );
25
52
  }
26
53
  }
27
- lines.push("", event.body);
28
- } else if (event.type === "task_comment") {
54
+ lines.push('', event.body);
55
+ } else if (event.type === 'task_comment') {
29
56
  lines.push(`[Event: task.comment.created]`);
30
57
  const taskLabel = event.targetName
31
58
  ? `${event.targetName} (prll://${event.targetId})`
@@ -34,20 +61,34 @@ export function buildEventBody(event: ParallEvent): string {
34
61
  if (event.deliveryReason) lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
35
62
  lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
36
63
  lines.push(`[Comment ID: prll://${event.messageId}]`);
37
- lines.push("", event.body);
38
- } else if (event.type === "approval") {
64
+ lines.push('', event.body);
65
+ } else if (event.type === 'wiki_comment') {
66
+ lines.push(`[Event: wiki.comment.created]`);
67
+ const target = event.replyTargetUri ?? `prll://${event.targetId}`;
68
+ if (event.targetType === 'changeset') {
69
+ lines.push(`[Wiki Changeset: ${target}]`);
70
+ } else {
71
+ lines.push(
72
+ `[Wiki: ${event.targetName ? `${sanitizeMeta(event.targetName)} (${target})` : target}]`,
73
+ );
74
+ }
75
+ if (event.deliveryReason) lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
76
+ lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
77
+ lines.push(`[Comment ID: prll://${event.messageId}]`);
78
+ lines.push('', event.body);
79
+ } else if (event.type === 'approval') {
39
80
  lines.push(`[Event: approval.decided]`);
40
81
  lines.push(`[Approval: prll://${event.messageId}]`);
41
82
  lines.push(`[Chat: prll://${event.targetId}]`);
42
83
  lines.push(`[Decided by: ${event.senderName} (prll://${event.senderId})]`);
43
- lines.push("", event.body);
44
- } else if (event.type === "schedule") {
84
+ lines.push('', event.body);
85
+ } else if (event.type === 'schedule') {
45
86
  lines.push(`[Event: schedule.fired]`);
46
87
  lines.push(`[Schedule: prll://${event.targetId}]`);
47
88
  lines.push(`[Run: prll://${event.messageId}]`);
48
89
  if (event.scheduledFireAt) lines.push(`[Scheduled at: ${sanitizeMeta(event.scheduledFireAt)}]`);
49
90
  if (event.attachedUri) lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
50
- lines.push("", event.body);
91
+ lines.push('', event.body);
51
92
  } else {
52
93
  lines.push(`[Event: task.assigned]`);
53
94
  const taskLabel = event.targetName
@@ -55,31 +96,36 @@ export function buildEventBody(event: ParallEvent): string {
55
96
  : `prll://${event.targetId}`;
56
97
  lines.push(`[Task: ${taskLabel}]`);
57
98
  lines.push(`[Assigned by: ${event.senderName} (prll://${event.senderId})]`);
58
- lines.push("", event.body);
99
+ lines.push('', event.body);
59
100
  }
60
- return lines.join("\n") + buildSendMessageHint(event);
101
+ return lines.join('\n') + buildSendMessageHint(event);
61
102
  }
62
103
 
63
104
  export function buildEventBodyForForkResult(event: ParallEvent): string {
64
- return buildEventBody(event).replace(/\n<system-reminder>[\s\S]*<\/system-reminder>$/, "");
105
+ return buildEventBody(event).replace(/\n<system-reminder>[\s\S]*<\/system-reminder>$/, '');
65
106
  }
66
107
 
67
108
  function buildSendMessageHint(event: ParallEvent): string {
68
- if (event.noReply) return "";
109
+ if (event.noReply) return '';
110
+
111
+ if (event.type === 'wiki_comment' && event.replyTargetUri) {
112
+ const where = event.targetType === 'changeset' ? 'this changeset comment' : 'this wiki page';
113
+ return `\n<system-reminder>To reply on ${where}, run: \`parall comments add --target "${event.replyTargetUri}" --body "..."\` (read the thread first with \`parall comments list --target "${event.replyTargetUri}"\`). To message someone instead, use \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
114
+ }
69
115
 
70
- if (event.targetId.startsWith("cht_")) {
116
+ if (event.targetId.startsWith('cht_')) {
71
117
  return `\n<system-reminder>To reply, run: \`parall messages send prll://${event.targetId} --text "..."\` — your plain text output is not delivered to the chat.</system-reminder>`;
72
118
  }
73
119
 
74
- if (event.targetId.startsWith("tsk_")) {
120
+ if (event.targetId.startsWith('tsk_')) {
75
121
  return `\n<system-reminder>To respond, use the CLI: \`parall tasks update\` / \`parall tasks comments add\`. To message someone, use \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
76
122
  }
77
123
 
78
- if (event.targetId.startsWith("sch_")) {
124
+ if (event.targetId.startsWith('sch_')) {
79
125
  return `\n<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
80
126
  }
81
127
 
82
- return "";
128
+ return '';
83
129
  }
84
130
 
85
131
  export function buildForkScopePrefix(event: ParallEvent): string {
@@ -90,17 +136,21 @@ export function buildForkScopePrefix(event: ParallEvent): string {
90
136
  }
91
137
 
92
138
  export function buildForkResultPrefix(results: ForkResult[]): string {
93
- if (!results.length) return "";
139
+ if (!results.length) return '';
94
140
  const blocks = results.map((result) => {
95
141
  const lines: string[] = [];
96
142
  for (const body of result.eventBodies) {
97
143
  lines.push(body);
98
144
  }
99
- lines.push(`[This event was handled by a parallel fork session. Do NOT re-handle, re-reply, or duplicate work for it.]`);
100
- lines.push(`[Fork summary: ${result.agentSummary ? sanitizeMeta(result.agentSummary) : "No fork summary available the fork completed without producing a text summary. Check the target chat/task for any actions the fork already took before acting."}]`);
101
- if (result.actions.length) lines.push(`[Fork actions: ${result.actions.join("; ")}]`);
145
+ lines.push(
146
+ `[This event was handled by a parallel fork session. Do NOT re-handle, re-reply, or duplicate work for it.]`,
147
+ );
148
+ lines.push(
149
+ `[Fork summary: ${result.agentSummary ? sanitizeMeta(result.agentSummary) : 'No fork summary available — the fork completed without producing a text summary. Check the target chat/task for any actions the fork already took before acting.'}]`,
150
+ );
151
+ if (result.actions.length) lines.push(`[Fork actions: ${result.actions.join('; ')}]`);
102
152
  if (result.historyPath) lines.push(`[Fork history: ${result.historyPath}]`);
103
- return lines.join("\n");
153
+ return lines.join('\n');
104
154
  });
105
- return blocks.join("\n\n") + "\n\n---\n\n";
155
+ return blocks.join('\n\n') + '\n\n---\n\n';
106
156
  }