pi-advisor-flow 0.1.9 → 0.2.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.
- package/README.md +15 -11
- package/extensions/index.ts +26 -1
- package/package.json +33 -21
- package/src/commands.ts +396 -123
- package/src/config.ts +405 -99
- package/src/conversation.ts +217 -47
- package/src/herdr.ts +142 -30
- package/src/session-state.ts +190 -41
- package/src/tools.ts +804 -199
- package/src/ui.ts +376 -106
package/src/conversation.ts
CHANGED
|
@@ -1,53 +1,223 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
advisorToolResultMaxBytesRef,
|
|
4
|
+
advisorToolResultMaxLinesRef,
|
|
5
|
+
DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES,
|
|
6
|
+
DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
|
|
7
|
+
} from "./config.js";
|
|
2
8
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
9
|
+
type RecordValue = Record<string, unknown>;
|
|
10
|
+
|
|
11
|
+
const isRecord = (value: unknown): value is RecordValue =>
|
|
12
|
+
Boolean(value) && typeof value === "object";
|
|
13
|
+
|
|
14
|
+
const contentParts = (content: unknown): unknown[] => {
|
|
15
|
+
if (typeof content === "string") {
|
|
16
|
+
return [content];
|
|
17
|
+
}
|
|
18
|
+
return Array.isArray(content) ? content : [];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const textFromPart = (part: unknown): string => {
|
|
22
|
+
if (typeof part === "string") {
|
|
23
|
+
return part;
|
|
24
|
+
}
|
|
25
|
+
if (!isRecord(part) || part.type !== "text") {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
return typeof part.text === "string" ? part.text : "";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const textFrom = (content: unknown): string =>
|
|
32
|
+
contentParts(content).map(textFromPart).join("\n").trim();
|
|
33
|
+
|
|
34
|
+
const byteLength = (value: string) => Buffer.byteLength(value, "utf8");
|
|
35
|
+
|
|
36
|
+
export interface ToolResultTruncation {
|
|
37
|
+
content: string;
|
|
38
|
+
omittedLines: number;
|
|
39
|
+
totalBytes: number;
|
|
40
|
+
totalLines: number;
|
|
41
|
+
truncated: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const capToolResult = (
|
|
45
|
+
value: string,
|
|
46
|
+
maxLines = DEFAULT_ADVISOR_TOOL_RESULT_MAX_LINES,
|
|
47
|
+
maxBytes = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES
|
|
48
|
+
): ToolResultTruncation => {
|
|
49
|
+
const lines = value.split("\n");
|
|
50
|
+
const totalLines = lines.length;
|
|
51
|
+
const totalBytes = byteLength(value);
|
|
52
|
+
if ((maxLines === 0 || maxBytes === 0) && value.length > 0) {
|
|
53
|
+
return {
|
|
54
|
+
content: "[Tool result omitted: configured limit is zero]",
|
|
55
|
+
omittedLines: totalLines,
|
|
56
|
+
totalBytes,
|
|
57
|
+
totalLines,
|
|
58
|
+
truncated: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
62
|
+
return {
|
|
63
|
+
content: value,
|
|
64
|
+
omittedLines: 0,
|
|
65
|
+
totalBytes,
|
|
66
|
+
totalLines,
|
|
67
|
+
truncated: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const marker = "[... omitted tool-result section ...]";
|
|
72
|
+
const markerBytes = byteLength(marker);
|
|
73
|
+
if (maxBytes < markerBytes || maxLines === 1) {
|
|
74
|
+
const content = [...marker].reduce(
|
|
75
|
+
(result, character) =>
|
|
76
|
+
byteLength(result + character) <= maxBytes
|
|
77
|
+
? result + character
|
|
78
|
+
: result,
|
|
79
|
+
""
|
|
80
|
+
);
|
|
81
|
+
return {
|
|
82
|
+
content,
|
|
83
|
+
omittedLines: totalLines,
|
|
84
|
+
totalBytes,
|
|
85
|
+
totalLines,
|
|
86
|
+
truncated: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const headCount = Math.floor((maxLines - 1) / 2);
|
|
90
|
+
const tailCount = maxLines - 1 - headCount;
|
|
91
|
+
const collect = (
|
|
92
|
+
candidates: string[],
|
|
93
|
+
maxEntries: number,
|
|
94
|
+
maxContentBytes: number
|
|
95
|
+
) => {
|
|
96
|
+
const selected: string[] = [];
|
|
97
|
+
let used = 0;
|
|
98
|
+
for (const line of candidates.slice(0, maxEntries)) {
|
|
99
|
+
const next = used + byteLength(line) + (selected.length ? 1 : 0);
|
|
100
|
+
if (next > maxContentBytes) {
|
|
101
|
+
break;
|
|
46
102
|
}
|
|
47
|
-
|
|
48
|
-
|
|
103
|
+
selected.push(line);
|
|
104
|
+
used = next;
|
|
105
|
+
}
|
|
106
|
+
return selected;
|
|
107
|
+
};
|
|
108
|
+
const availableBytes = maxBytes - markerBytes - 2;
|
|
109
|
+
const head = collect(lines, headCount, Math.floor(availableBytes / 2));
|
|
110
|
+
const tail = collect(
|
|
111
|
+
lines.slice(Math.max(head.length, lines.length - tailCount)),
|
|
112
|
+
tailCount,
|
|
113
|
+
availableBytes - byteLength(head.join("\n"))
|
|
114
|
+
);
|
|
115
|
+
const content = [...head, marker, ...tail].join("\n");
|
|
116
|
+
return {
|
|
117
|
+
content,
|
|
118
|
+
omittedLines: Math.max(0, totalLines - head.length - tail.length),
|
|
119
|
+
totalBytes,
|
|
120
|
+
totalLines,
|
|
121
|
+
truncated: true,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const assistantEntry = (message: RecordValue): string | undefined => {
|
|
126
|
+
const parts: string[] = [];
|
|
127
|
+
const text = textFrom(message.content);
|
|
128
|
+
if (text) {
|
|
129
|
+
parts.push(text);
|
|
130
|
+
}
|
|
131
|
+
for (const part of contentParts(message.content)) {
|
|
132
|
+
if (!isRecord(part) || part.type !== "toolCall") {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
parts.push(
|
|
136
|
+
`[Tool Call: ${String(part.name)}(${JSON.stringify(part.arguments)})]`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return parts.length > 0 ? `Executor: ${parts.join("\n")}` : undefined;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const toolResultEntry = (
|
|
143
|
+
message: RecordValue,
|
|
144
|
+
toolResultMaxLines: number,
|
|
145
|
+
toolResultMaxBytes: number
|
|
146
|
+
): string => {
|
|
147
|
+
const status = message.isError ? "Error " : "";
|
|
148
|
+
const capped = capToolResult(
|
|
149
|
+
textFrom(message.content),
|
|
150
|
+
toolResultMaxLines,
|
|
151
|
+
toolResultMaxBytes
|
|
152
|
+
);
|
|
153
|
+
const toolName =
|
|
154
|
+
typeof message.toolName === "string" ? message.toolName : "unknown";
|
|
155
|
+
return `[Tool Result for ${toolName}] (${status}output):\n${capped.content}`;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const conversationEntry = (
|
|
159
|
+
entry: unknown,
|
|
160
|
+
toolResultMaxLines: number,
|
|
161
|
+
toolResultMaxBytes: number
|
|
162
|
+
): string | undefined => {
|
|
163
|
+
if (!isRecord(entry)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (entry.type === "compaction" && typeof entry.summary === "string") {
|
|
167
|
+
return `[System Compaction Summary]: ${entry.summary}`;
|
|
168
|
+
}
|
|
169
|
+
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const { message } = entry;
|
|
173
|
+
if (message.role === "user") {
|
|
174
|
+
const text = textFrom(message.content);
|
|
175
|
+
return text ? `User: ${text}` : undefined;
|
|
176
|
+
}
|
|
177
|
+
if (message.role === "assistant") {
|
|
178
|
+
return assistantEntry(message);
|
|
179
|
+
}
|
|
180
|
+
if (message.role === "toolResult" || message.role === "tool") {
|
|
181
|
+
return toolResultEntry(message, toolResultMaxLines, toolResultMaxBytes);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const selectRecentEntries = (entries: string[], maxChars: number): string => {
|
|
186
|
+
const separator = "\n\n";
|
|
187
|
+
const joined = entries.join(separator);
|
|
188
|
+
if (joined.length <= maxChars || maxChars === Number.MAX_SAFE_INTEGER) {
|
|
189
|
+
return joined;
|
|
190
|
+
}
|
|
191
|
+
const selected: string[] = [];
|
|
192
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
193
|
+
const candidate = [entries[index], ...selected].join(separator);
|
|
194
|
+
if (candidate.length <= maxChars || selected.length === 0) {
|
|
195
|
+
selected.unshift(entries[index]);
|
|
196
|
+
} else {
|
|
197
|
+
break;
|
|
49
198
|
}
|
|
50
199
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
200
|
+
const omitted = entries.length - selected.length;
|
|
201
|
+
const suffix = selected.length
|
|
202
|
+
? `${separator}${selected.join(separator)}`
|
|
203
|
+
: "";
|
|
204
|
+
return `[Older context omitted: ${omitted} complete entr${omitted === 1 ? "y" : "ies"}]${suffix}`;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const recentConversation = (
|
|
208
|
+
ctx: ExtensionContext,
|
|
209
|
+
maxChars = 15_000,
|
|
210
|
+
toolResultMaxLines = advisorToolResultMaxLinesRef,
|
|
211
|
+
toolResultMaxBytes = advisorToolResultMaxBytesRef
|
|
212
|
+
): string => {
|
|
213
|
+
if (maxChars === 0) {
|
|
214
|
+
return "";
|
|
215
|
+
}
|
|
216
|
+
const entries = ctx.sessionManager
|
|
217
|
+
.getBranch()
|
|
218
|
+
.map((entry) =>
|
|
219
|
+
conversationEntry(entry, toolResultMaxLines, toolResultMaxBytes)
|
|
220
|
+
)
|
|
221
|
+
.filter((entry): entry is string => entry !== undefined);
|
|
222
|
+
return selectRecentEntries(entries, maxChars);
|
|
53
223
|
};
|
package/src/herdr.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import net from "node:net";
|
|
2
|
+
import { advisorHerdrIntegrationRef } from "./config.js";
|
|
2
3
|
|
|
3
4
|
const SOURCE = "pi-advisor:advisor-activity";
|
|
4
5
|
const BLOCK_SOURCE = "pi-advisor:advisor-block";
|
|
6
|
+
const NOTIFICATION_SOURCE = "pi-advisor:advisor-notification";
|
|
5
7
|
const HERDR_PI_SOURCE = "herdr:pi";
|
|
6
|
-
let sequence = Date.now() *
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
let sequence = Date.now() * 1000;
|
|
9
|
+
const nextSequence = () => {
|
|
10
|
+
sequence += 1;
|
|
11
|
+
return sequence;
|
|
12
|
+
};
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
export interface HerdrMetadataRequest {
|
|
11
15
|
id: string;
|
|
12
16
|
method: "pane.report_metadata";
|
|
13
17
|
params: {
|
|
@@ -19,17 +23,56 @@ type HerdrRequest = {
|
|
|
19
23
|
clear_state_labels?: true;
|
|
20
24
|
seq: number;
|
|
21
25
|
};
|
|
22
|
-
}
|
|
23
|
-
|
|
26
|
+
}
|
|
27
|
+
export interface HerdrNotificationRequest {
|
|
28
|
+
id: string;
|
|
29
|
+
method: "notification.show";
|
|
30
|
+
params: {
|
|
31
|
+
title: string;
|
|
32
|
+
body: string;
|
|
33
|
+
position: "top-left";
|
|
34
|
+
sound: "request";
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export type HerdrRequest = HerdrMetadataRequest | HerdrNotificationRequest;
|
|
24
38
|
type Report = (request: HerdrRequest) => void;
|
|
25
39
|
|
|
40
|
+
const isControlCharacter = (character: string) =>
|
|
41
|
+
character <= "\u001f" || character === "\u007f";
|
|
42
|
+
|
|
43
|
+
const cleanNotification = (value: string, max: number) =>
|
|
44
|
+
[...value]
|
|
45
|
+
.map((character) => (isControlCharacter(character) ? " " : character))
|
|
46
|
+
.join("")
|
|
47
|
+
.replace(/\s+/g, " ")
|
|
48
|
+
.trim()
|
|
49
|
+
.slice(0, max);
|
|
50
|
+
|
|
51
|
+
export const createHerdrNotificationRequest = (
|
|
52
|
+
title: string,
|
|
53
|
+
body: string
|
|
54
|
+
): HerdrNotificationRequest => ({
|
|
55
|
+
id: `${NOTIFICATION_SOURCE}:${nextSequence()}`,
|
|
56
|
+
method: "notification.show",
|
|
57
|
+
params: {
|
|
58
|
+
body: cleanNotification(body, 240),
|
|
59
|
+
position: "top-left",
|
|
60
|
+
sound: "request",
|
|
61
|
+
title: cleanNotification(title, 80),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
26
65
|
const sendToHerdr: Report = (request) => {
|
|
27
|
-
if (process.env.HERDR_ENV !== "1")
|
|
66
|
+
if (process.env.HERDR_ENV !== "1") {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
28
69
|
const paneId = process.env.HERDR_PANE_ID;
|
|
29
70
|
const socketPath = process.env.HERDR_SOCKET_PATH;
|
|
30
|
-
if (!paneId
|
|
31
|
-
|
|
32
|
-
|
|
71
|
+
if (!(paneId && socketPath)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const endpoint =
|
|
75
|
+
process.platform === "win32" ? `\\\\.\\pipe\\${socketPath}` : socketPath;
|
|
33
76
|
const socket = net.createConnection(endpoint);
|
|
34
77
|
const timeout = setTimeout(() => socket.destroy(), 500);
|
|
35
78
|
timeout.unref?.();
|
|
@@ -41,22 +84,41 @@ const sendToHerdr: Report = (request) => {
|
|
|
41
84
|
|
|
42
85
|
export class HerdrAdvisorActivity {
|
|
43
86
|
#activeConsultations = 0;
|
|
44
|
-
|
|
45
|
-
|
|
87
|
+
private readonly report: Report;
|
|
88
|
+
private readonly enabled: () => boolean;
|
|
89
|
+
|
|
90
|
+
constructor(
|
|
91
|
+
report: Report = sendToHerdr,
|
|
92
|
+
enabled: () => boolean = () => true
|
|
93
|
+
) {
|
|
94
|
+
this.report = report;
|
|
95
|
+
this.enabled = enabled;
|
|
96
|
+
}
|
|
46
97
|
|
|
47
98
|
start() {
|
|
99
|
+
if (!this.enabled()) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
48
102
|
this.#activeConsultations += 1;
|
|
49
|
-
if (this.#activeConsultations === 1)
|
|
103
|
+
if (this.#activeConsultations === 1) {
|
|
104
|
+
this.safeReport(false);
|
|
105
|
+
}
|
|
50
106
|
}
|
|
51
107
|
|
|
52
108
|
finish() {
|
|
53
|
-
if (this.#activeConsultations === 0)
|
|
109
|
+
if (this.#activeConsultations === 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
54
112
|
this.#activeConsultations -= 1;
|
|
55
|
-
if (this.#activeConsultations === 0)
|
|
113
|
+
if (this.#activeConsultations === 0) {
|
|
114
|
+
this.safeReport(true);
|
|
115
|
+
}
|
|
56
116
|
}
|
|
57
117
|
|
|
58
118
|
clear() {
|
|
59
|
-
if (this.#activeConsultations === 0)
|
|
119
|
+
if (this.#activeConsultations === 0) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
60
122
|
this.#activeConsultations = 0;
|
|
61
123
|
this.safeReport(true);
|
|
62
124
|
}
|
|
@@ -65,20 +127,22 @@ export class HerdrAdvisorActivity {
|
|
|
65
127
|
try {
|
|
66
128
|
this.report(this.request(clear));
|
|
67
129
|
} catch {
|
|
68
|
-
|
|
130
|
+
/* Herdr is optional. */
|
|
69
131
|
}
|
|
70
132
|
}
|
|
71
133
|
|
|
72
|
-
private request(clear: boolean):
|
|
134
|
+
private request(clear: boolean): HerdrMetadataRequest {
|
|
73
135
|
return {
|
|
74
136
|
id: `${SOURCE}:${nextSequence()}`,
|
|
75
137
|
method: "pane.report_metadata",
|
|
76
138
|
params: {
|
|
77
|
-
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
78
|
-
source: SOURCE,
|
|
79
139
|
agent: "pi",
|
|
80
140
|
applies_to_source: HERDR_PI_SOURCE,
|
|
81
|
-
|
|
141
|
+
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
142
|
+
source: SOURCE,
|
|
143
|
+
...(clear
|
|
144
|
+
? { clear_state_labels: true }
|
|
145
|
+
: { state_labels: { working: "seeking advice" } }),
|
|
82
146
|
seq: nextSequence(),
|
|
83
147
|
},
|
|
84
148
|
};
|
|
@@ -87,25 +151,49 @@ export class HerdrAdvisorActivity {
|
|
|
87
151
|
|
|
88
152
|
export class HerdrAdvisorBlock {
|
|
89
153
|
#blocked = false;
|
|
90
|
-
|
|
91
|
-
|
|
154
|
+
private readonly report: Report;
|
|
155
|
+
private readonly enabled: () => boolean;
|
|
156
|
+
|
|
157
|
+
constructor(
|
|
158
|
+
report: Report = sendToHerdr,
|
|
159
|
+
enabled: () => boolean = () => true
|
|
160
|
+
) {
|
|
161
|
+
this.report = report;
|
|
162
|
+
this.enabled = enabled;
|
|
163
|
+
}
|
|
92
164
|
|
|
93
165
|
set(reason: string) {
|
|
166
|
+
if (!this.enabled()) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
94
169
|
this.#blocked = true;
|
|
95
170
|
this.safeReport({ blocked: reason });
|
|
96
171
|
}
|
|
97
172
|
|
|
98
173
|
clear() {
|
|
99
|
-
|
|
174
|
+
const wasBlocked: boolean = this.#blocked;
|
|
100
175
|
this.#blocked = false;
|
|
176
|
+
if (!wasBlocked) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!this.enabled()) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
101
182
|
try {
|
|
102
183
|
this.report({
|
|
103
184
|
id: `${BLOCK_SOURCE}:${nextSequence()}`,
|
|
104
185
|
method: "pane.report_metadata",
|
|
105
|
-
params: {
|
|
186
|
+
params: {
|
|
187
|
+
agent: "pi",
|
|
188
|
+
applies_to_source: HERDR_PI_SOURCE,
|
|
189
|
+
clear_state_labels: true,
|
|
190
|
+
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
191
|
+
seq: nextSequence(),
|
|
192
|
+
source: BLOCK_SOURCE,
|
|
193
|
+
},
|
|
106
194
|
});
|
|
107
195
|
} catch {
|
|
108
|
-
|
|
196
|
+
/* Herdr is optional. */
|
|
109
197
|
}
|
|
110
198
|
}
|
|
111
199
|
|
|
@@ -114,13 +202,37 @@ export class HerdrAdvisorBlock {
|
|
|
114
202
|
this.report({
|
|
115
203
|
id: `${BLOCK_SOURCE}:${nextSequence()}`,
|
|
116
204
|
method: "pane.report_metadata",
|
|
117
|
-
params: {
|
|
205
|
+
params: {
|
|
206
|
+
agent: "pi",
|
|
207
|
+
applies_to_source: HERDR_PI_SOURCE,
|
|
208
|
+
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
209
|
+
seq: nextSequence(),
|
|
210
|
+
source: BLOCK_SOURCE,
|
|
211
|
+
state_labels: labels,
|
|
212
|
+
},
|
|
118
213
|
});
|
|
119
214
|
} catch {
|
|
120
|
-
|
|
215
|
+
/* Herdr is optional. */
|
|
121
216
|
}
|
|
122
217
|
}
|
|
123
218
|
}
|
|
124
219
|
|
|
125
|
-
export const
|
|
126
|
-
|
|
220
|
+
export const notifyHerdrAdvisorFailure = (title: string, body: string) => {
|
|
221
|
+
if (!advisorHerdrIntegrationRef) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
sendToHerdr(createHerdrNotificationRequest(title, body));
|
|
226
|
+
} catch {
|
|
227
|
+
/* Herdr transport never changes Advisor safety. */
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export const herdrAdvisorActivity = new HerdrAdvisorActivity(
|
|
232
|
+
sendToHerdr,
|
|
233
|
+
() => advisorHerdrIntegrationRef
|
|
234
|
+
);
|
|
235
|
+
export const herdrAdvisorBlock = new HerdrAdvisorBlock(
|
|
236
|
+
sendToHerdr,
|
|
237
|
+
() => advisorHerdrIntegrationRef
|
|
238
|
+
);
|