pi-advisor-flow 0.1.8 → 0.2.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/README.md +15 -11
- package/extensions/index.ts +17 -1
- package/package.json +25 -21
- package/src/commands.ts +346 -99
- package/src/config.ts +345 -94
- package/src/conversation.ts +199 -48
- package/src/herdr.ts +145 -30
- package/src/session-state.ts +182 -41
- package/src/tools.ts +696 -173
- package/src/ui.ts +317 -81
package/src/conversation.ts
CHANGED
|
@@ -1,53 +1,204 @@
|
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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 availableBytes = Math.max(0, maxBytes - byteLength(marker) - 2);
|
|
73
|
+
const headCount = Math.max(1, Math.floor(Math.max(1, maxLines - 1) / 2));
|
|
74
|
+
const tailCount = Math.max(1, Math.max(1, maxLines - 1) - headCount);
|
|
75
|
+
const head: string[] = [];
|
|
76
|
+
let headBytes = 0;
|
|
77
|
+
for (const line of lines.slice(0, headCount)) {
|
|
78
|
+
const next = headBytes + byteLength(line) + (head.length ? 1 : 0);
|
|
79
|
+
if (head.length && next > Math.ceil(availableBytes / 2)) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
head.push(line);
|
|
83
|
+
headBytes = next;
|
|
84
|
+
}
|
|
85
|
+
const tail: string[] = [];
|
|
86
|
+
let tailBytes = 0;
|
|
87
|
+
const tailStart = Math.max(head.length, lines.length - tailCount);
|
|
88
|
+
for (const line of lines.slice(tailStart)) {
|
|
89
|
+
const next = tailBytes + byteLength(line) + (tail.length ? 1 : 0);
|
|
90
|
+
if (tail.length && next > Math.floor(availableBytes / 2)) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
tail.push(line);
|
|
94
|
+
tailBytes = next;
|
|
95
|
+
}
|
|
96
|
+
const content = [...head, marker, ...tail].join("\n");
|
|
97
|
+
return {
|
|
98
|
+
content,
|
|
99
|
+
omittedLines: Math.max(0, totalLines - head.length - tail.length),
|
|
100
|
+
totalBytes,
|
|
101
|
+
totalLines,
|
|
102
|
+
truncated: true,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const assistantEntry = (message: RecordValue): string | undefined => {
|
|
107
|
+
const parts: string[] = [];
|
|
108
|
+
const text = textFrom(message.content);
|
|
109
|
+
if (text) {
|
|
110
|
+
parts.push(text);
|
|
111
|
+
}
|
|
112
|
+
for (const part of contentParts(message.content)) {
|
|
113
|
+
if (!isRecord(part) || part.type !== "toolCall") {
|
|
114
|
+
continue;
|
|
49
115
|
}
|
|
116
|
+
parts.push(
|
|
117
|
+
`[Tool Call: ${String(part.name)}(${JSON.stringify(part.arguments)})]`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return parts.length > 0 ? `Executor: ${parts.join("\n")}` : undefined;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const toolResultEntry = (
|
|
124
|
+
message: RecordValue,
|
|
125
|
+
toolResultMaxLines: number,
|
|
126
|
+
toolResultMaxBytes: number
|
|
127
|
+
): string => {
|
|
128
|
+
const status = message.isError ? "Error " : "";
|
|
129
|
+
const capped = capToolResult(
|
|
130
|
+
textFrom(message.content),
|
|
131
|
+
toolResultMaxLines,
|
|
132
|
+
toolResultMaxBytes
|
|
133
|
+
);
|
|
134
|
+
const toolName =
|
|
135
|
+
typeof message.toolName === "string" ? message.toolName : "unknown";
|
|
136
|
+
return `[Tool Result for ${toolName}] (${status}output):\n${capped.content}`;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const conversationEntry = (
|
|
140
|
+
entry: unknown,
|
|
141
|
+
toolResultMaxLines: number,
|
|
142
|
+
toolResultMaxBytes: number
|
|
143
|
+
): string | undefined => {
|
|
144
|
+
if (!isRecord(entry)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (entry.type === "compaction" && typeof entry.summary === "string") {
|
|
148
|
+
return `[System Compaction Summary]: ${entry.summary}`;
|
|
149
|
+
}
|
|
150
|
+
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const message = entry.message;
|
|
154
|
+
if (message.role === "user") {
|
|
155
|
+
const text = textFrom(message.content);
|
|
156
|
+
return text ? `User: ${text}` : undefined;
|
|
157
|
+
}
|
|
158
|
+
if (message.role === "assistant") {
|
|
159
|
+
return assistantEntry(message);
|
|
50
160
|
}
|
|
51
|
-
|
|
52
|
-
|
|
161
|
+
if (message.role === "toolResult" || message.role === "tool") {
|
|
162
|
+
return toolResultEntry(message, toolResultMaxLines, toolResultMaxBytes);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const selectRecentEntries = (entries: string[], maxChars: number): string => {
|
|
167
|
+
const separator = "\n\n";
|
|
168
|
+
const joined = entries.join(separator);
|
|
169
|
+
if (joined.length <= maxChars || maxChars === Number.MAX_SAFE_INTEGER) {
|
|
170
|
+
return joined;
|
|
171
|
+
}
|
|
172
|
+
const selected: string[] = [];
|
|
173
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
174
|
+
const candidate = [entries[index], ...selected].join(separator);
|
|
175
|
+
if (candidate.length <= maxChars || selected.length === 0) {
|
|
176
|
+
selected.unshift(entries[index]);
|
|
177
|
+
} else {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const omitted = entries.length - selected.length;
|
|
182
|
+
const suffix = selected.length
|
|
183
|
+
? `${separator}${selected.join(separator)}`
|
|
184
|
+
: "";
|
|
185
|
+
return `[Older context omitted: ${omitted} complete entr${omitted === 1 ? "y" : "ies"}]${suffix}`;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const recentConversation = (
|
|
189
|
+
ctx: ExtensionContext,
|
|
190
|
+
maxChars = 15_000,
|
|
191
|
+
toolResultMaxLines = advisorToolResultMaxLinesRef,
|
|
192
|
+
toolResultMaxBytes = advisorToolResultMaxBytesRef
|
|
193
|
+
): string => {
|
|
194
|
+
if (maxChars === 0) {
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
const entries = ctx.sessionManager
|
|
198
|
+
.getBranch()
|
|
199
|
+
.map((entry) =>
|
|
200
|
+
conversationEntry(entry, toolResultMaxLines, toolResultMaxBytes)
|
|
201
|
+
)
|
|
202
|
+
.filter((entry): entry is string => entry !== undefined);
|
|
203
|
+
return selectRecentEntries(entries, maxChars);
|
|
53
204
|
};
|
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,40 +151,91 @@ 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
|
-
if (!this
|
|
174
|
+
if (!this.isBlocked()) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
100
177
|
this.#blocked = false;
|
|
178
|
+
if (!this.enabled()) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
101
181
|
try {
|
|
102
182
|
this.report({
|
|
103
183
|
id: `${BLOCK_SOURCE}:${nextSequence()}`,
|
|
104
184
|
method: "pane.report_metadata",
|
|
105
|
-
params: {
|
|
185
|
+
params: {
|
|
186
|
+
agent: "pi",
|
|
187
|
+
applies_to_source: HERDR_PI_SOURCE,
|
|
188
|
+
clear_state_labels: true,
|
|
189
|
+
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
190
|
+
seq: nextSequence(),
|
|
191
|
+
source: BLOCK_SOURCE,
|
|
192
|
+
},
|
|
106
193
|
});
|
|
107
194
|
} catch {
|
|
108
|
-
|
|
195
|
+
/* Herdr is optional. */
|
|
109
196
|
}
|
|
110
197
|
}
|
|
111
198
|
|
|
199
|
+
private isBlocked() {
|
|
200
|
+
return this.#blocked;
|
|
201
|
+
}
|
|
202
|
+
|
|
112
203
|
private safeReport(labels: { blocked: string }) {
|
|
113
204
|
try {
|
|
114
205
|
this.report({
|
|
115
206
|
id: `${BLOCK_SOURCE}:${nextSequence()}`,
|
|
116
207
|
method: "pane.report_metadata",
|
|
117
|
-
params: {
|
|
208
|
+
params: {
|
|
209
|
+
agent: "pi",
|
|
210
|
+
applies_to_source: HERDR_PI_SOURCE,
|
|
211
|
+
pane_id: process.env.HERDR_PANE_ID ?? "",
|
|
212
|
+
seq: nextSequence(),
|
|
213
|
+
source: BLOCK_SOURCE,
|
|
214
|
+
state_labels: labels,
|
|
215
|
+
},
|
|
118
216
|
});
|
|
119
217
|
} catch {
|
|
120
|
-
|
|
218
|
+
/* Herdr is optional. */
|
|
121
219
|
}
|
|
122
220
|
}
|
|
123
221
|
}
|
|
124
222
|
|
|
125
|
-
export const
|
|
126
|
-
|
|
223
|
+
export const notifyHerdrAdvisorFailure = (title: string, body: string) => {
|
|
224
|
+
if (!advisorHerdrIntegrationRef) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
sendToHerdr(createHerdrNotificationRequest(title, body));
|
|
229
|
+
} catch {
|
|
230
|
+
/* Herdr transport never changes Advisor safety. */
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const herdrAdvisorActivity = new HerdrAdvisorActivity(
|
|
235
|
+
sendToHerdr,
|
|
236
|
+
() => advisorHerdrIntegrationRef
|
|
237
|
+
);
|
|
238
|
+
export const herdrAdvisorBlock = new HerdrAdvisorBlock(
|
|
239
|
+
sendToHerdr,
|
|
240
|
+
() => advisorHerdrIntegrationRef
|
|
241
|
+
);
|