@parall/agent-core 1.30.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.
- package/dist/bridge-workspace.js +12 -12
- package/dist/dispatch-adapter.d.ts +15 -8
- package/dist/dispatch-adapter.d.ts.map +1 -1
- package/dist/event-format.d.ts +1 -1
- package/dist/event-format.d.ts.map +1 -1
- package/dist/event-format.js +68 -25
- package/dist/gateway-base.d.ts +15 -13
- package/dist/gateway-base.d.ts.map +1 -1
- package/dist/gateway-base.js +662 -312
- package/dist/index.d.ts +15 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -11
- package/dist/internal/attachment-input.d.ts +3 -3
- package/dist/internal/attachment-input.d.ts.map +1 -1
- package/dist/internal/attachment-input.js +61 -58
- package/dist/logger.d.ts +1 -1
- package/dist/platform-config.d.ts +28 -2
- package/dist/platform-config.d.ts.map +1 -1
- package/dist/platform-config.js +42 -11
- package/dist/prompt-fragments.d.ts +1 -1
- package/dist/prompt-fragments.d.ts.map +1 -1
- package/dist/prompt-fragments.js +28 -10
- package/dist/provider-config.d.ts +20 -0
- package/dist/provider-config.d.ts.map +1 -0
- package/dist/provider-config.js +41 -0
- package/dist/routing.d.ts +5 -5
- package/dist/routing.js +6 -6
- package/dist/session-state.d.ts +16 -0
- package/dist/session-state.d.ts.map +1 -1
- package/dist/session-state.js +45 -0
- package/dist/skills/index.d.ts +5 -4
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +28 -21
- package/dist/skills/parall-clips.d.ts +2 -0
- package/dist/skills/parall-clips.d.ts.map +1 -0
- package/dist/skills/parall-clips.js +44 -0
- package/dist/telemetry.d.ts +27 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +205 -0
- package/dist/types.d.ts +18 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +11 -2
- package/src/bridge-workspace.ts +12 -12
- package/src/dispatch-adapter.ts +31 -8
- package/src/event-format.ts +80 -30
- package/src/gateway-base.ts +998 -442
- package/src/index.ts +23 -12
- package/src/internal/attachment-input.ts +127 -100
- package/src/logger.ts +1 -1
- package/src/platform-config.ts +61 -16
- package/src/prompt-fragments.ts +28 -10
- package/src/provider-config.ts +51 -0
- package/src/routing.ts +11 -11
- package/src/session-state.ts +62 -0
- package/src/skills/index.ts +34 -23
- package/src/skills/parall-clips.ts +44 -0
- package/src/telemetry.ts +252 -0
- package/src/types.ts +18 -2
package/src/bridge-workspace.ts
CHANGED
|
@@ -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 !==
|
|
87
|
+
if (!input || typeof input !== 'object') return undefined;
|
|
88
88
|
const command = (input as { command?: unknown }).command;
|
|
89
|
-
return typeof command ===
|
|
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,
|
|
104
|
+
const tokens = command.replace(/\s+/g, ' ').trim().split(' ');
|
|
105
105
|
let i = 0;
|
|
106
|
-
if (tokens[i] ===
|
|
106
|
+
if (tokens[i] === 'parall') {
|
|
107
107
|
i++;
|
|
108
|
-
} else if (tokens[i] ===
|
|
108
|
+
} else if (tokens[i] === 'npx') {
|
|
109
109
|
i++;
|
|
110
|
-
while (i < tokens.length && tokens[i].startsWith(
|
|
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] ===
|
|
113
|
+
} else if (tokens[i] === 'pnpm') {
|
|
114
114
|
i++;
|
|
115
|
-
if (i < tokens.length && (tokens[i] ===
|
|
116
|
-
if (i >= tokens.length || tokens[i] !==
|
|
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] ===
|
|
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] ===
|
|
147
|
+
return sub?.[0] === 'no-reply';
|
|
148
148
|
}
|
package/src/dispatch-adapter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ParallClient } from
|
|
2
|
-
import type { ParallEvent } from
|
|
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:
|
|
32
|
+
type: 'runtime_session';
|
|
33
33
|
runtimeSessionId: string;
|
|
34
34
|
runtimeLaneKey?: string;
|
|
35
35
|
runtimeRef?: Record<string, unknown>;
|
|
36
36
|
}
|
|
37
|
-
| { type:
|
|
38
|
-
| {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
}
|
package/src/event-format.ts
CHANGED
|
@@ -1,31 +1,58 @@
|
|
|
1
|
-
import type { ForkResult, ParallEvent } from
|
|
1
|
+
import type { ForkResult, ParallEvent } from './types.js';
|
|
2
2
|
|
|
3
3
|
function sanitizeMeta(value: string): string {
|
|
4
|
-
return value
|
|
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 ===
|
|
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 ??
|
|
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)
|
|
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 =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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(
|
|
28
|
-
} else if (event.type ===
|
|
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(
|
|
38
|
-
} else if (event.type ===
|
|
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(
|
|
44
|
-
} else if (event.type ===
|
|
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(
|
|
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(
|
|
99
|
+
lines.push('', event.body);
|
|
59
100
|
}
|
|
60
|
-
return lines.join(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
100
|
-
|
|
101
|
-
|
|
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(
|
|
153
|
+
return lines.join('\n');
|
|
104
154
|
});
|
|
105
|
-
return blocks.join(
|
|
155
|
+
return blocks.join('\n\n') + '\n\n---\n\n';
|
|
106
156
|
}
|