@sentry/junior 0.74.0 → 0.75.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/agent-hooks-2HEB4C3Q.js +33 -0
- package/dist/api-reference.d.ts +1 -1
- package/dist/app.js +5211 -5316
- package/dist/build/copy-build-content.d.ts +1 -1
- package/dist/chat/agent-dispatch/context.d.ts +2 -3
- package/dist/chat/agent-dispatch/types.d.ts +2 -1
- package/dist/chat/config.d.ts +2 -0
- package/dist/chat/conversations/configured.d.ts +2 -0
- package/dist/chat/credentials/subject.d.ts +3 -3
- package/dist/chat/plugins/agent-hooks.d.ts +13 -13
- package/dist/chat/plugins/credential-hooks.d.ts +6 -6
- package/dist/chat/plugins/db.d.ts +31 -0
- package/dist/chat/plugins/logging.d.ts +2 -2
- package/dist/chat/plugins/package-discovery.d.ts +2 -1
- package/dist/chat/plugins/registry.d.ts +4 -0
- package/dist/chat/plugins/state.d.ts +3 -5
- package/dist/chat/plugins/types.d.ts +1 -0
- package/dist/chat/plugins/validation.d.ts +5 -0
- package/dist/chat/prompt.d.ts +11 -1
- package/dist/chat/respond.d.ts +10 -1
- package/dist/chat/runtime/slack-runtime.d.ts +6 -1
- package/dist/chat/sandbox/egress-credentials.d.ts +8 -8
- package/dist/chat/sandbox/sandbox.d.ts +2 -2
- package/dist/chat/sql/db.d.ts +3 -0
- package/dist/chat/sql/executor.d.ts +7 -0
- package/dist/chat/sql/neon.d.ts +2 -4
- package/dist/chat/sql/postgres.d.ts +6 -0
- package/dist/chat/task-execution/state.d.ts +7 -2
- package/dist/chat/task-execution/worker.d.ts +1 -1
- package/dist/chat/tools/agent-tools.d.ts +2 -2
- package/dist/chat/tools/types.d.ts +3 -0
- package/dist/{chunk-7Q5YOUUT.js → chunk-2RWFUS5F.js} +47 -10
- package/dist/{chunk-YRDS7VKO.js → chunk-62FUNJYS.js} +3 -54
- package/dist/{chunk-M4FLLXXD.js → chunk-74HO27II.js} +1 -1
- package/dist/chunk-BNJIEFQC.js +115 -0
- package/dist/{chunk-YOHFWWBV.js → chunk-C3AM4Z4J.js} +1 -103
- package/dist/chunk-D7NFH5GD.js +570 -0
- package/dist/chunk-EE6PJWY4.js +130 -0
- package/dist/{chunk-CYUI7JU5.js → chunk-EIYL7I4S.js} +1 -1
- package/dist/{chunk-GM7HTXYC.js → chunk-FCZO7LAR.js} +13 -2
- package/dist/{chunk-2LUZA3LY.js → chunk-JEELK46E.js} +5 -5
- package/dist/chunk-MCMROINU.js +12 -0
- package/dist/chunk-NPVUAXUE.js +694 -0
- package/dist/{chunk-OR6NQJ5E.js → chunk-OJODNL2P.js} +3 -3
- package/dist/{chunk-3BYAPS6B.js → chunk-OK4KKR7B.js} +1 -11
- package/dist/chunk-OZSPLAQ4.js +71 -0
- package/dist/{chunk-KVZL5NZS.js → chunk-Q3XNY442.js} +17 -7
- package/dist/{chunk-SQGMG7OD.js → chunk-TQ74BATR.js} +100 -58
- package/dist/{chunk-JL2SLRAT.js → chunk-UJ7OTHPO.js} +76 -312
- package/dist/{chunk-HYHKTFG2.js → chunk-VNTLUFTY.js} +80 -843
- package/dist/chunk-WBZ4M5N5.js +59 -0
- package/dist/{chunk-6UP2Z2RZ.js → chunk-XJHDZUGD.js} +7 -7
- package/dist/chunk-Y2CM7HXH.js +111 -0
- package/dist/{chunk-F6HWCPOC.js → chunk-ZNNTSPNF.js} +1 -1
- package/dist/cli/chat.js +52 -2
- package/dist/cli/check.js +6 -5
- package/dist/cli/snapshot-warmup.js +10 -9
- package/dist/cli/upgrade.js +256 -16
- package/dist/db-A3ILH67H.js +20 -0
- package/dist/handlers/sandbox-egress-route.d.ts +4 -0
- package/dist/handlers/slack-webhook.d.ts +4 -0
- package/dist/handlers/webhooks.d.ts +6 -13
- package/dist/nitro.js +34 -89
- package/dist/plugin-module.d.ts +21 -0
- package/dist/plugins-OMJKLRJ2.js +13 -0
- package/dist/plugins.d.ts +6 -4
- package/dist/registry-NLZFIW23.js +46 -0
- package/dist/reporting/conversations.d.ts +3 -3
- package/dist/reporting.d.ts +6 -5
- package/dist/reporting.js +23 -17
- package/dist/{runner-27NP2TEO.js → runner-LUQZ5G67.js} +18 -13
- package/dist/validation-VMCPP3YO.js +15 -0
- package/package.json +11 -9
|
@@ -1,851 +1,41 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createNeonJuniorSqlExecutor,
|
|
3
2
|
createSqlStore,
|
|
4
3
|
createStateConversationStore
|
|
5
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UJ7OTHPO.js";
|
|
6
5
|
import {
|
|
7
|
-
isConversationChannel,
|
|
8
|
-
isConversationScopedChannel,
|
|
9
|
-
isDmChannel,
|
|
10
|
-
normalizeSlackConversationId,
|
|
11
6
|
parseDestination
|
|
12
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-WBZ4M5N5.js";
|
|
8
|
+
import {
|
|
9
|
+
createJuniorSqlExecutor
|
|
10
|
+
} from "./chunk-D7NFH5GD.js";
|
|
13
11
|
import {
|
|
14
12
|
SANDBOX_DATA_ROOT,
|
|
15
13
|
SANDBOX_WORKSPACE_ROOT,
|
|
16
14
|
sandboxSkillDir
|
|
17
15
|
} from "./chunk-G3E7SCME.js";
|
|
18
|
-
import {
|
|
19
|
-
listReferenceFiles,
|
|
20
|
-
soulPathCandidates,
|
|
21
|
-
worldPathCandidates
|
|
22
|
-
} from "./chunk-KVZL5NZS.js";
|
|
23
16
|
import {
|
|
24
17
|
getConnectedStateContext,
|
|
25
18
|
getStateAdapter
|
|
26
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ZNNTSPNF.js";
|
|
27
20
|
import {
|
|
28
21
|
TURN_CONTEXT_TAG,
|
|
29
22
|
botConfig,
|
|
30
23
|
getChatConfig
|
|
31
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-FCZO7LAR.js";
|
|
25
|
+
import {
|
|
26
|
+
listReferenceFiles,
|
|
27
|
+
soulPathCandidates,
|
|
28
|
+
worldPathCandidates
|
|
29
|
+
} from "./chunk-Q3XNY442.js";
|
|
32
30
|
import {
|
|
33
|
-
isActorUserId,
|
|
34
|
-
parseActorUserId,
|
|
35
31
|
parseStoredSlackRequester,
|
|
36
32
|
storedSlackRequesterSchema
|
|
37
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-EIYL7I4S.js";
|
|
38
34
|
import {
|
|
39
35
|
isRecord,
|
|
40
|
-
logException,
|
|
41
36
|
logInfo,
|
|
42
37
|
logWarn
|
|
43
|
-
} from "./chunk-
|
|
44
|
-
|
|
45
|
-
// src/chat/plugins/logging.ts
|
|
46
|
-
function createAgentPluginLogger(plugin) {
|
|
47
|
-
return {
|
|
48
|
-
info(message, metadata) {
|
|
49
|
-
logInfo(
|
|
50
|
-
"agent_plugin_log_info",
|
|
51
|
-
{},
|
|
52
|
-
{ "app.plugin.name": plugin, ...metadata },
|
|
53
|
-
message
|
|
54
|
-
);
|
|
55
|
-
},
|
|
56
|
-
warn(message, metadata) {
|
|
57
|
-
logWarn(
|
|
58
|
-
"agent_plugin_log_warn",
|
|
59
|
-
{},
|
|
60
|
-
{ "app.plugin.name": plugin, ...metadata },
|
|
61
|
-
message
|
|
62
|
-
);
|
|
63
|
-
},
|
|
64
|
-
error(message, metadata) {
|
|
65
|
-
logException(
|
|
66
|
-
new Error(message),
|
|
67
|
-
"agent_plugin_log_error",
|
|
68
|
-
{},
|
|
69
|
-
{ "app.plugin.name": plugin, ...metadata },
|
|
70
|
-
message
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// src/chat/plugins/state.ts
|
|
77
|
-
import { createHash } from "crypto";
|
|
78
|
-
var MAX_PLUGIN_STATE_KEY_LENGTH = 512;
|
|
79
|
-
function hashKeyPart(value) {
|
|
80
|
-
return createHash("sha256").update(value).digest("hex").slice(0, 32);
|
|
81
|
-
}
|
|
82
|
-
function pluginStateKey(plugin, key2) {
|
|
83
|
-
return `junior:plugin_state:${hashKeyPart(plugin)}:${hashKeyPart(key2)}`;
|
|
84
|
-
}
|
|
85
|
-
function validatePluginStateKey(key2) {
|
|
86
|
-
if (!key2.trim()) {
|
|
87
|
-
throw new Error("Plugin state key is required");
|
|
88
|
-
}
|
|
89
|
-
if (key2.length > MAX_PLUGIN_STATE_KEY_LENGTH) {
|
|
90
|
-
throw new Error("Plugin state key exceeds the maximum length");
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function legacyStateKey(key2, options) {
|
|
94
|
-
for (const prefix of options?.legacyStatePrefixes ?? []) {
|
|
95
|
-
const trimmed = prefix.trim();
|
|
96
|
-
if (!trimmed) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (key2 === trimmed || key2.startsWith(`${trimmed}:`)) {
|
|
100
|
-
return key2;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return void 0;
|
|
104
|
-
}
|
|
105
|
-
function createPluginState(plugin, options) {
|
|
106
|
-
return {
|
|
107
|
-
async delete(key2) {
|
|
108
|
-
validatePluginStateKey(key2);
|
|
109
|
-
const state = getStateAdapter();
|
|
110
|
-
await state.connect();
|
|
111
|
-
await state.delete(pluginStateKey(plugin, key2));
|
|
112
|
-
const legacyKey = legacyStateKey(key2, options);
|
|
113
|
-
if (legacyKey) {
|
|
114
|
-
await state.delete(legacyKey);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
async get(key2) {
|
|
118
|
-
validatePluginStateKey(key2);
|
|
119
|
-
const state = getStateAdapter();
|
|
120
|
-
await state.connect();
|
|
121
|
-
const value = await state.get(pluginStateKey(plugin, key2));
|
|
122
|
-
if (value !== null && value !== void 0) {
|
|
123
|
-
return value;
|
|
124
|
-
}
|
|
125
|
-
const legacyKey = legacyStateKey(key2, options);
|
|
126
|
-
return legacyKey ? await state.get(legacyKey) ?? void 0 : void 0;
|
|
127
|
-
},
|
|
128
|
-
async set(key2, value, ttlMs) {
|
|
129
|
-
validatePluginStateKey(key2);
|
|
130
|
-
const state = getStateAdapter();
|
|
131
|
-
await state.connect();
|
|
132
|
-
await state.set(pluginStateKey(plugin, key2), value, ttlMs);
|
|
133
|
-
},
|
|
134
|
-
async setIfNotExists(key2, value, ttlMs) {
|
|
135
|
-
validatePluginStateKey(key2);
|
|
136
|
-
const state = getStateAdapter();
|
|
137
|
-
await state.connect();
|
|
138
|
-
const legacyKey = legacyStateKey(key2, options);
|
|
139
|
-
if (legacyKey) {
|
|
140
|
-
const existing = await state.get(legacyKey);
|
|
141
|
-
if (existing !== null && existing !== void 0) {
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return await state.setIfNotExists(
|
|
146
|
-
pluginStateKey(plugin, key2),
|
|
147
|
-
value,
|
|
148
|
-
ttlMs
|
|
149
|
-
);
|
|
150
|
-
},
|
|
151
|
-
async withLock(key2, ttlMs, callback) {
|
|
152
|
-
validatePluginStateKey(key2);
|
|
153
|
-
const state = getStateAdapter();
|
|
154
|
-
await state.connect();
|
|
155
|
-
const lockKey = legacyStateKey(key2, options) ?? pluginStateKey(plugin, key2);
|
|
156
|
-
const lock = await state.acquireLock(lockKey, ttlMs);
|
|
157
|
-
if (!lock) {
|
|
158
|
-
throw new Error(`Could not acquire plugin state lock for ${key2}`);
|
|
159
|
-
}
|
|
160
|
-
try {
|
|
161
|
-
return await callback();
|
|
162
|
-
} finally {
|
|
163
|
-
await state.releaseLock(lock);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// src/chat/tools/slack/context.ts
|
|
170
|
-
function getSlackToolContext(context) {
|
|
171
|
-
if (context.source.platform !== "slack") {
|
|
172
|
-
return void 0;
|
|
173
|
-
}
|
|
174
|
-
return {
|
|
175
|
-
destination: context.destination?.platform === "slack" ? context.destination : void 0,
|
|
176
|
-
source: context.source,
|
|
177
|
-
requester: context.requester?.platform === "slack" ? context.requester : void 0,
|
|
178
|
-
destinationChannelId: context.destination?.platform === "slack" ? context.destination.channelId : void 0,
|
|
179
|
-
messageTs: context.source.messageTs,
|
|
180
|
-
sourceChannelId: context.source.channelId,
|
|
181
|
-
teamId: context.source.teamId,
|
|
182
|
-
threadTs: context.source.threadTs
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// src/chat/credentials/subject.ts
|
|
187
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
188
|
-
var CREDENTIAL_SUBJECT_HMAC_CONTEXT = "junior.credential_subject.v1";
|
|
189
|
-
var CREDENTIAL_SUBJECT_SIGNATURE_VERSION = "v1";
|
|
190
|
-
function getCredentialSubjectSecret() {
|
|
191
|
-
return process.env.JUNIOR_SECRET?.trim() || void 0;
|
|
192
|
-
}
|
|
193
|
-
function buildPayload(input) {
|
|
194
|
-
return [
|
|
195
|
-
CREDENTIAL_SUBJECT_HMAC_CONTEXT,
|
|
196
|
-
input.allowedWhen,
|
|
197
|
-
input.teamId,
|
|
198
|
-
input.channelId,
|
|
199
|
-
input.userId
|
|
200
|
-
].join("\0");
|
|
201
|
-
}
|
|
202
|
-
function signPayload(secret, payload) {
|
|
203
|
-
const digest = createHmac("sha256", secret).update(payload).digest("hex");
|
|
204
|
-
return `${CREDENTIAL_SUBJECT_SIGNATURE_VERSION}=${digest}`;
|
|
205
|
-
}
|
|
206
|
-
function timingSafeMatch(expected, actual) {
|
|
207
|
-
const expectedBuffer = Buffer.from(expected);
|
|
208
|
-
const actualBuffer = Buffer.from(actual);
|
|
209
|
-
if (expectedBuffer.length !== actualBuffer.length) {
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
return timingSafeEqual(expectedBuffer, actualBuffer);
|
|
213
|
-
}
|
|
214
|
-
function createSlackDirectCredentialSubject(input) {
|
|
215
|
-
const channelId = normalizeSlackConversationId(input.channelId);
|
|
216
|
-
const teamId = input.teamId?.trim();
|
|
217
|
-
const userId = parseActorUserId(input.userId);
|
|
218
|
-
if (!channelId || !teamId || !userId || !isDmChannel(channelId)) {
|
|
219
|
-
return void 0;
|
|
220
|
-
}
|
|
221
|
-
return {
|
|
222
|
-
type: "user",
|
|
223
|
-
userId,
|
|
224
|
-
allowedWhen: "private-direct-conversation"
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
function bindSlackDirectCredentialSubject(input) {
|
|
228
|
-
const channelId = normalizeSlackConversationId(input.channelId);
|
|
229
|
-
const teamId = input.teamId.trim();
|
|
230
|
-
const secret = getCredentialSubjectSecret();
|
|
231
|
-
const { subject } = input;
|
|
232
|
-
const userId = parseActorUserId(subject.userId);
|
|
233
|
-
if (!channelId || !teamId || !secret || !isDmChannel(channelId) || subject.type !== "user" || !userId || subject.allowedWhen !== "private-direct-conversation") {
|
|
234
|
-
return void 0;
|
|
235
|
-
}
|
|
236
|
-
return {
|
|
237
|
-
type: "user",
|
|
238
|
-
userId,
|
|
239
|
-
allowedWhen: subject.allowedWhen,
|
|
240
|
-
binding: {
|
|
241
|
-
type: "slack-direct-conversation",
|
|
242
|
-
teamId,
|
|
243
|
-
channelId,
|
|
244
|
-
signature: signPayload(
|
|
245
|
-
secret,
|
|
246
|
-
buildPayload({
|
|
247
|
-
allowedWhen: subject.allowedWhen,
|
|
248
|
-
teamId,
|
|
249
|
-
channelId,
|
|
250
|
-
userId
|
|
251
|
-
})
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
function verifySlackDirectCredentialSubject(input) {
|
|
257
|
-
const channelId = normalizeSlackConversationId(input.channelId);
|
|
258
|
-
const secret = getCredentialSubjectSecret();
|
|
259
|
-
if (!channelId || !secret) {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
const { subject } = input;
|
|
263
|
-
const binding = subject.binding;
|
|
264
|
-
if (subject.type !== "user" || !isActorUserId(subject.userId) || subject.allowedWhen !== "private-direct-conversation" || !binding || binding.type !== "slack-direct-conversation" || typeof binding.signature !== "string" || !binding.signature || binding.teamId !== input.teamId || binding.channelId !== channelId) {
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
const expected = signPayload(
|
|
268
|
-
secret,
|
|
269
|
-
buildPayload({
|
|
270
|
-
allowedWhen: subject.allowedWhen,
|
|
271
|
-
teamId: binding.teamId,
|
|
272
|
-
channelId: binding.channelId,
|
|
273
|
-
userId: subject.userId
|
|
274
|
-
})
|
|
275
|
-
);
|
|
276
|
-
return timingSafeMatch(expected, binding.signature);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// src/chat/tools/channel-capabilities.ts
|
|
280
|
-
function resolveChannelCapabilities(channelId) {
|
|
281
|
-
return {
|
|
282
|
-
canCreateCanvas: isConversationScopedChannel(channelId),
|
|
283
|
-
canPostToChannel: isConversationChannel(channelId),
|
|
284
|
-
canAddReactions: isConversationScopedChannel(channelId)
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// src/chat/plugins/agent-hooks.ts
|
|
289
|
-
var AgentPluginHookDeniedError = class extends Error {
|
|
290
|
-
constructor(message) {
|
|
291
|
-
super(message);
|
|
292
|
-
this.name = "AgentPluginHookDeniedError";
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
var agentPlugins = [];
|
|
296
|
-
var AGENT_PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
297
|
-
var AGENT_PLUGIN_TOOL_NAME_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
298
|
-
var OPERATIONAL_REPORT_MAX_METRICS = 8;
|
|
299
|
-
var OPERATIONAL_REPORT_MAX_RECORD_SETS = 8;
|
|
300
|
-
var OPERATIONAL_REPORT_MAX_FIELDS = 8;
|
|
301
|
-
var OPERATIONAL_REPORT_MAX_RECORDS = 25;
|
|
302
|
-
var OPERATIONAL_REPORT_MAX_LABEL_LENGTH = 80;
|
|
303
|
-
var OPERATIONAL_REPORT_MAX_VALUE_LENGTH = 160;
|
|
304
|
-
var AGENT_PLUGIN_ROUTE_METHODS = /* @__PURE__ */ new Set([
|
|
305
|
-
"GET",
|
|
306
|
-
"POST",
|
|
307
|
-
"PUT",
|
|
308
|
-
"PATCH",
|
|
309
|
-
"DELETE",
|
|
310
|
-
"HEAD",
|
|
311
|
-
"OPTIONS",
|
|
312
|
-
"ALL"
|
|
313
|
-
]);
|
|
314
|
-
function isRecord2(value) {
|
|
315
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
316
|
-
}
|
|
317
|
-
function validateLegacyStatePrefixes(plugin) {
|
|
318
|
-
const prefixes = plugin.legacyStatePrefixes;
|
|
319
|
-
if (prefixes === void 0) {
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
if (!Array.isArray(prefixes)) {
|
|
323
|
-
throw new Error(
|
|
324
|
-
`Plugin "${plugin.name}" legacyStatePrefixes must be an array`
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
const allowedPrefix = `junior:${plugin.name}`;
|
|
328
|
-
for (const rawPrefix of prefixes) {
|
|
329
|
-
const prefix = typeof rawPrefix === "string" ? rawPrefix.trim() : "";
|
|
330
|
-
if (!prefix) {
|
|
331
|
-
throw new Error(
|
|
332
|
-
`Plugin "${plugin.name}" legacy state prefixes must be non-empty strings`
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
if (prefix !== allowedPrefix && !prefix.startsWith(`${allowedPrefix}:`)) {
|
|
336
|
-
throw new Error(
|
|
337
|
-
`Plugin "${plugin.name}" legacy state prefix "${prefix}" must stay under "${allowedPrefix}"`
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function validateAgentPlugins(plugins) {
|
|
343
|
-
const seen = /* @__PURE__ */ new Set();
|
|
344
|
-
for (const plugin of plugins) {
|
|
345
|
-
if (!AGENT_PLUGIN_NAME_RE.test(plugin.name)) {
|
|
346
|
-
throw new Error(
|
|
347
|
-
`Plugin name "${plugin.name}" must be a lowercase plugin identifier`
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
if (seen.has(plugin.name)) {
|
|
351
|
-
throw new Error(`Duplicate plugin name "${plugin.name}"`);
|
|
352
|
-
}
|
|
353
|
-
seen.add(plugin.name);
|
|
354
|
-
validateLegacyStatePrefixes(plugin);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
function setAgentPlugins(plugins) {
|
|
358
|
-
validateAgentPlugins(plugins);
|
|
359
|
-
const previous = agentPlugins;
|
|
360
|
-
agentPlugins = [...plugins].sort(
|
|
361
|
-
(left, right) => left.name.localeCompare(right.name)
|
|
362
|
-
);
|
|
363
|
-
return previous;
|
|
364
|
-
}
|
|
365
|
-
function getAgentPlugins() {
|
|
366
|
-
return [...agentPlugins];
|
|
367
|
-
}
|
|
368
|
-
function getAgentPluginTools(context) {
|
|
369
|
-
const tools = {};
|
|
370
|
-
for (const plugin of getAgentPlugins()) {
|
|
371
|
-
const hook = plugin.hooks?.tools;
|
|
372
|
-
if (!hook) {
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
const log = createAgentPluginLogger(plugin.name);
|
|
376
|
-
const destination = context.destination;
|
|
377
|
-
const slackToolContext = getSlackToolContext(context);
|
|
378
|
-
const credentialSubject = slackToolContext ? createSlackDirectCredentialSubject({
|
|
379
|
-
channelId: slackToolContext.sourceChannelId,
|
|
380
|
-
teamId: slackToolContext.teamId,
|
|
381
|
-
userId: slackToolContext.requester?.userId
|
|
382
|
-
}) : void 0;
|
|
383
|
-
const slackContext = slackToolContext ? {
|
|
384
|
-
channelCapabilities: resolveChannelCapabilities(
|
|
385
|
-
slackToolContext.sourceChannelId
|
|
386
|
-
),
|
|
387
|
-
...credentialSubject ? { credentialSubject } : {}
|
|
388
|
-
} : void 0;
|
|
389
|
-
const pluginContext = context.source.platform === "slack" ? {
|
|
390
|
-
plugin: { name: plugin.name },
|
|
391
|
-
log,
|
|
392
|
-
requester: context.requester?.platform === "slack" ? context.requester : void 0,
|
|
393
|
-
conversationId: context.conversationId,
|
|
394
|
-
destination: destination?.platform === "slack" ? destination : void 0,
|
|
395
|
-
slack: slackContext,
|
|
396
|
-
source: context.source,
|
|
397
|
-
userText: context.userText,
|
|
398
|
-
state: createPluginState(plugin.name, {
|
|
399
|
-
legacyStatePrefixes: plugin.legacyStatePrefixes
|
|
400
|
-
})
|
|
401
|
-
} : {
|
|
402
|
-
plugin: { name: plugin.name },
|
|
403
|
-
log,
|
|
404
|
-
requester: context.requester?.platform === "local" ? context.requester : void 0,
|
|
405
|
-
conversationId: context.conversationId,
|
|
406
|
-
destination: destination?.platform === "local" ? destination : void 0,
|
|
407
|
-
source: context.source,
|
|
408
|
-
userText: context.userText,
|
|
409
|
-
state: createPluginState(plugin.name, {
|
|
410
|
-
legacyStatePrefixes: plugin.legacyStatePrefixes
|
|
411
|
-
})
|
|
412
|
-
};
|
|
413
|
-
const pluginTools = hook(pluginContext);
|
|
414
|
-
for (const [name, tool] of Object.entries(pluginTools)) {
|
|
415
|
-
if (!AGENT_PLUGIN_TOOL_NAME_RE.test(name)) {
|
|
416
|
-
throw new Error(
|
|
417
|
-
`Plugin tool "${name}" from plugin "${plugin.name}" must be a camelCase identifier`
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
if (tools[name]) {
|
|
421
|
-
throw new Error(
|
|
422
|
-
`Duplicate plugin tool "${name}" from plugin "${plugin.name}"`
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
tools[name] = tool;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return tools;
|
|
429
|
-
}
|
|
430
|
-
function routeMethods(route, pluginName) {
|
|
431
|
-
const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
|
|
432
|
-
if (methods.length === 0) {
|
|
433
|
-
throw new Error(
|
|
434
|
-
`Plugin route "${route.path}" from plugin "${pluginName}" must declare at least one method`
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
for (const method of methods) {
|
|
438
|
-
if (!AGENT_PLUGIN_ROUTE_METHODS.has(method)) {
|
|
439
|
-
throw new Error(
|
|
440
|
-
`Plugin route "${route.path}" from plugin "${pluginName}" has invalid method "${String(method)}"`
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (methods.includes("ALL") && methods.length > 1) {
|
|
445
|
-
throw new Error(
|
|
446
|
-
`Plugin route "${route.path}" from plugin "${pluginName}" must not combine ALL with explicit methods`
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
return methods;
|
|
450
|
-
}
|
|
451
|
-
function getAgentPluginRoutes() {
|
|
452
|
-
const routes = [];
|
|
453
|
-
const seen = /* @__PURE__ */ new Set();
|
|
454
|
-
const methodsByPath = /* @__PURE__ */ new Map();
|
|
455
|
-
for (const plugin of getAgentPlugins()) {
|
|
456
|
-
const hook = plugin.hooks?.routes;
|
|
457
|
-
if (!hook) {
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
const log = createAgentPluginLogger(plugin.name);
|
|
461
|
-
const pluginRoutes = hook({
|
|
462
|
-
plugin: { name: plugin.name },
|
|
463
|
-
log
|
|
464
|
-
});
|
|
465
|
-
if (!Array.isArray(pluginRoutes)) {
|
|
466
|
-
throw new Error(
|
|
467
|
-
`Plugin routes hook from plugin "${plugin.name}" must return an array`
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
for (const route of pluginRoutes) {
|
|
471
|
-
if (!isRecord2(route)) {
|
|
472
|
-
throw new Error(
|
|
473
|
-
`Plugin route from plugin "${plugin.name}" must be an object`
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
if (typeof route.path !== "string" || !route.path.startsWith("/")) {
|
|
477
|
-
throw new Error(
|
|
478
|
-
`Plugin route "${route.path}" from plugin "${plugin.name}" must start with /`
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
if (typeof route.handler !== "function") {
|
|
482
|
-
throw new Error(
|
|
483
|
-
`Plugin route "${route.path}" from plugin "${plugin.name}" must provide a handler`
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
const methods = routeMethods(route, plugin.name);
|
|
487
|
-
const pathMethods = methodsByPath.get(route.path) ?? /* @__PURE__ */ new Set();
|
|
488
|
-
if (pathMethods.has("ALL") || methods.includes("ALL") && pathMethods.size > 0) {
|
|
489
|
-
throw new Error(
|
|
490
|
-
`Plugin route "${route.path}" conflicts with an ALL route for the same path`
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
for (const method of methods) {
|
|
494
|
-
const key2 = `${method}:${route.path}`;
|
|
495
|
-
if (seen.has(key2)) {
|
|
496
|
-
throw new Error(`Duplicate plugin route "${method} ${route.path}"`);
|
|
497
|
-
}
|
|
498
|
-
seen.add(key2);
|
|
499
|
-
pathMethods.add(method);
|
|
500
|
-
}
|
|
501
|
-
methodsByPath.set(route.path, pathMethods);
|
|
502
|
-
routes.push({
|
|
503
|
-
...route,
|
|
504
|
-
pluginName: plugin.name
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
return routes;
|
|
509
|
-
}
|
|
510
|
-
function trustedSlackConversationUrl(pluginName, link) {
|
|
511
|
-
const url = typeof link?.url === "string" ? link.url.trim() : "";
|
|
512
|
-
if (!url) {
|
|
513
|
-
return void 0;
|
|
514
|
-
}
|
|
515
|
-
let parsed;
|
|
516
|
-
try {
|
|
517
|
-
parsed = new URL(url);
|
|
518
|
-
} catch (error) {
|
|
519
|
-
throw new Error(
|
|
520
|
-
`Plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`,
|
|
521
|
-
{ cause: error }
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
525
|
-
throw new Error(
|
|
526
|
-
`Plugin "${pluginName}" slackConversationLink must return an absolute http(s) URL`
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
return parsed.toString();
|
|
530
|
-
}
|
|
531
|
-
function getAgentPluginSlackConversationLink(conversationId) {
|
|
532
|
-
for (const plugin of getAgentPlugins()) {
|
|
533
|
-
const hook = plugin.hooks?.slackConversationLink;
|
|
534
|
-
if (!hook) {
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
const log = createAgentPluginLogger(plugin.name);
|
|
538
|
-
const link = hook({
|
|
539
|
-
plugin: { name: plugin.name },
|
|
540
|
-
log,
|
|
541
|
-
conversationId
|
|
542
|
-
});
|
|
543
|
-
const url = trustedSlackConversationUrl(plugin.name, link);
|
|
544
|
-
if (url) {
|
|
545
|
-
return { url };
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
return void 0;
|
|
549
|
-
}
|
|
550
|
-
function pluginReadState(state) {
|
|
551
|
-
return {
|
|
552
|
-
get: state.get
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
function operationalReportText(value, maxLength) {
|
|
556
|
-
if (typeof value !== "string") {
|
|
557
|
-
return void 0;
|
|
558
|
-
}
|
|
559
|
-
const trimmed = value.trim();
|
|
560
|
-
if (!trimmed) {
|
|
561
|
-
return void 0;
|
|
562
|
-
}
|
|
563
|
-
return trimmed.length <= maxLength ? trimmed : `${trimmed.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
564
|
-
}
|
|
565
|
-
function operationalReportTone(tone) {
|
|
566
|
-
return tone === "danger" || tone === "good" || tone === "neutral" || tone === "warning" ? tone : void 0;
|
|
567
|
-
}
|
|
568
|
-
function sanitizeOperationalReport(args) {
|
|
569
|
-
const metrics = args.report.metrics?.slice(0, OPERATIONAL_REPORT_MAX_METRICS).map((metric) => {
|
|
570
|
-
const label = operationalReportText(
|
|
571
|
-
metric.label,
|
|
572
|
-
OPERATIONAL_REPORT_MAX_LABEL_LENGTH
|
|
573
|
-
);
|
|
574
|
-
const value = operationalReportText(
|
|
575
|
-
metric.value,
|
|
576
|
-
OPERATIONAL_REPORT_MAX_VALUE_LENGTH
|
|
577
|
-
);
|
|
578
|
-
if (!label || !value) {
|
|
579
|
-
return void 0;
|
|
580
|
-
}
|
|
581
|
-
const sanitizedMetric = { label, value };
|
|
582
|
-
const tone = operationalReportTone(metric.tone);
|
|
583
|
-
if (tone) {
|
|
584
|
-
sanitizedMetric.tone = tone;
|
|
585
|
-
}
|
|
586
|
-
return sanitizedMetric;
|
|
587
|
-
}).filter((metric) => Boolean(metric));
|
|
588
|
-
const recordSets = args.report.recordSets?.slice(0, OPERATIONAL_REPORT_MAX_RECORD_SETS).map((recordSet, recordSetIndex) => {
|
|
589
|
-
const title2 = operationalReportText(
|
|
590
|
-
recordSet.title,
|
|
591
|
-
OPERATIONAL_REPORT_MAX_LABEL_LENGTH
|
|
592
|
-
);
|
|
593
|
-
if (!title2) {
|
|
594
|
-
return void 0;
|
|
595
|
-
}
|
|
596
|
-
const fields = recordSet.fields?.slice(0, OPERATIONAL_REPORT_MAX_FIELDS).map((field) => {
|
|
597
|
-
const key2 = operationalReportText(
|
|
598
|
-
field.key,
|
|
599
|
-
OPERATIONAL_REPORT_MAX_LABEL_LENGTH
|
|
600
|
-
);
|
|
601
|
-
const label = operationalReportText(
|
|
602
|
-
field.label,
|
|
603
|
-
OPERATIONAL_REPORT_MAX_LABEL_LENGTH
|
|
604
|
-
);
|
|
605
|
-
return key2 && label ? { key: key2, label } : void 0;
|
|
606
|
-
}).filter((field) => Boolean(field));
|
|
607
|
-
const records = recordSet.records?.slice(0, OPERATIONAL_REPORT_MAX_RECORDS).map((record, recordIndex) => {
|
|
608
|
-
const id = operationalReportText(
|
|
609
|
-
record.id,
|
|
610
|
-
OPERATIONAL_REPORT_MAX_LABEL_LENGTH
|
|
611
|
-
) ?? `${recordSetIndex}:${recordIndex}`;
|
|
612
|
-
const values = Object.fromEntries(
|
|
613
|
-
(fields ?? []).map((field) => [
|
|
614
|
-
field.key,
|
|
615
|
-
operationalReportText(
|
|
616
|
-
record.values[field.key],
|
|
617
|
-
OPERATIONAL_REPORT_MAX_VALUE_LENGTH
|
|
618
|
-
) ?? ""
|
|
619
|
-
])
|
|
620
|
-
);
|
|
621
|
-
const sanitizedRecord = {
|
|
622
|
-
id,
|
|
623
|
-
values
|
|
624
|
-
};
|
|
625
|
-
const tone = operationalReportTone(record.tone);
|
|
626
|
-
if (tone) {
|
|
627
|
-
sanitizedRecord.tone = tone;
|
|
628
|
-
}
|
|
629
|
-
return sanitizedRecord;
|
|
630
|
-
});
|
|
631
|
-
const sanitizedRecordSet = { title: title2 };
|
|
632
|
-
if (fields?.length) {
|
|
633
|
-
sanitizedRecordSet.fields = fields;
|
|
634
|
-
}
|
|
635
|
-
const emptyText = operationalReportText(
|
|
636
|
-
recordSet.emptyText,
|
|
637
|
-
OPERATIONAL_REPORT_MAX_VALUE_LENGTH
|
|
638
|
-
);
|
|
639
|
-
if (emptyText) {
|
|
640
|
-
sanitizedRecordSet.emptyText = emptyText;
|
|
641
|
-
}
|
|
642
|
-
if (records?.length) {
|
|
643
|
-
sanitizedRecordSet.records = records;
|
|
644
|
-
}
|
|
645
|
-
return sanitizedRecordSet;
|
|
646
|
-
}).filter(
|
|
647
|
-
(recordSet) => Boolean(recordSet)
|
|
648
|
-
);
|
|
649
|
-
const sanitized = {
|
|
650
|
-
pluginName: args.pluginName
|
|
651
|
-
};
|
|
652
|
-
const generatedAt = operationalReportText(
|
|
653
|
-
args.report.generatedAt,
|
|
654
|
-
OPERATIONAL_REPORT_MAX_VALUE_LENGTH
|
|
655
|
-
);
|
|
656
|
-
if (generatedAt) {
|
|
657
|
-
sanitized.generatedAt = generatedAt;
|
|
658
|
-
}
|
|
659
|
-
if (recordSets?.length) {
|
|
660
|
-
sanitized.recordSets = recordSets;
|
|
661
|
-
}
|
|
662
|
-
if (metrics?.length) {
|
|
663
|
-
sanitized.metrics = metrics;
|
|
664
|
-
}
|
|
665
|
-
const title = operationalReportText(
|
|
666
|
-
args.report.title,
|
|
667
|
-
OPERATIONAL_REPORT_MAX_LABEL_LENGTH
|
|
668
|
-
);
|
|
669
|
-
if (title) {
|
|
670
|
-
sanitized.title = title;
|
|
671
|
-
}
|
|
672
|
-
return sanitized;
|
|
673
|
-
}
|
|
674
|
-
function failedOperationalReport(args) {
|
|
675
|
-
return {
|
|
676
|
-
generatedAt: new Date(args.nowMs).toISOString(),
|
|
677
|
-
pluginName: args.pluginName,
|
|
678
|
-
metrics: [{ label: "report", tone: "danger", value: "failed" }],
|
|
679
|
-
title: args.pluginName,
|
|
680
|
-
recordSets: [
|
|
681
|
-
{
|
|
682
|
-
emptyText: "This plugin report failed to load.",
|
|
683
|
-
title: "Error"
|
|
684
|
-
}
|
|
685
|
-
]
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
async function getAgentPluginOperationalReports(nowMs, conversations) {
|
|
689
|
-
const reports = [];
|
|
690
|
-
for (const plugin of getAgentPlugins()) {
|
|
691
|
-
const hook = plugin.hooks?.operationalReport;
|
|
692
|
-
if (!hook) {
|
|
693
|
-
continue;
|
|
694
|
-
}
|
|
695
|
-
const log = createAgentPluginLogger(plugin.name);
|
|
696
|
-
try {
|
|
697
|
-
const state = createPluginState(plugin.name, {
|
|
698
|
-
legacyStatePrefixes: plugin.legacyStatePrefixes
|
|
699
|
-
});
|
|
700
|
-
const report = await hook({
|
|
701
|
-
plugin: { name: plugin.name },
|
|
702
|
-
log,
|
|
703
|
-
conversations,
|
|
704
|
-
nowMs,
|
|
705
|
-
state: pluginReadState(state)
|
|
706
|
-
});
|
|
707
|
-
if (!report) {
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
reports.push(
|
|
711
|
-
sanitizeOperationalReport({
|
|
712
|
-
pluginName: plugin.name,
|
|
713
|
-
report
|
|
714
|
-
})
|
|
715
|
-
);
|
|
716
|
-
} catch (error) {
|
|
717
|
-
log.error("Plugin operational report failed", {
|
|
718
|
-
error: error instanceof Error ? error.message : String(error)
|
|
719
|
-
});
|
|
720
|
-
reports.push(failedOperationalReport({ nowMs, pluginName: plugin.name }));
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
return reports;
|
|
724
|
-
}
|
|
725
|
-
function normalizeEnv(value) {
|
|
726
|
-
if (!isRecord2(value)) {
|
|
727
|
-
return {};
|
|
728
|
-
}
|
|
729
|
-
const env = {};
|
|
730
|
-
for (const [key2, rawValue] of Object.entries(value)) {
|
|
731
|
-
if (typeof rawValue === "string") {
|
|
732
|
-
env[key2] = rawValue;
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
return env;
|
|
736
|
-
}
|
|
737
|
-
function createSandboxCapability(sandbox) {
|
|
738
|
-
return {
|
|
739
|
-
root: SANDBOX_WORKSPACE_ROOT,
|
|
740
|
-
juniorRoot: `${SANDBOX_WORKSPACE_ROOT}/.junior`,
|
|
741
|
-
async readFile(filePath) {
|
|
742
|
-
return await sandbox.readFileToBuffer({ path: filePath }) ?? null;
|
|
743
|
-
},
|
|
744
|
-
async run(input) {
|
|
745
|
-
const result = await sandbox.runCommand(input);
|
|
746
|
-
const [stdout, stderr] = await Promise.all([
|
|
747
|
-
result.stdout(),
|
|
748
|
-
result.stderr()
|
|
749
|
-
]);
|
|
750
|
-
return {
|
|
751
|
-
exitCode: result.exitCode,
|
|
752
|
-
stdout,
|
|
753
|
-
stderr
|
|
754
|
-
};
|
|
755
|
-
},
|
|
756
|
-
async writeFile(input) {
|
|
757
|
-
await sandbox.writeFiles([
|
|
758
|
-
{
|
|
759
|
-
path: input.path,
|
|
760
|
-
content: input.content,
|
|
761
|
-
...input.mode !== void 0 ? { mode: input.mode } : {}
|
|
762
|
-
}
|
|
763
|
-
]);
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
function createAgentPluginHookRunner(input = {}) {
|
|
768
|
-
const loaded = getAgentPlugins();
|
|
769
|
-
return {
|
|
770
|
-
async prepareSandbox(sandbox) {
|
|
771
|
-
const sandboxCapability = createSandboxCapability(sandbox);
|
|
772
|
-
for (const plugin of loaded) {
|
|
773
|
-
const hook = plugin.hooks?.sandboxPrepare;
|
|
774
|
-
if (!hook) {
|
|
775
|
-
continue;
|
|
776
|
-
}
|
|
777
|
-
logInfo(
|
|
778
|
-
"agent_plugin_hook_sandbox_prepare",
|
|
779
|
-
{},
|
|
780
|
-
{ "app.plugin.name": plugin.name },
|
|
781
|
-
"Running agent plugin sandbox prepare hook"
|
|
782
|
-
);
|
|
783
|
-
await hook({
|
|
784
|
-
plugin: { name: plugin.name },
|
|
785
|
-
log: createAgentPluginLogger(plugin.name),
|
|
786
|
-
requester: input.requester,
|
|
787
|
-
sandbox: sandboxCapability
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
},
|
|
791
|
-
async beforeToolExecute(tool) {
|
|
792
|
-
let nextInput = { ...tool.input };
|
|
793
|
-
const env = normalizeEnv(nextInput.env);
|
|
794
|
-
for (const plugin of loaded) {
|
|
795
|
-
const hook = plugin.hooks?.beforeToolExecute;
|
|
796
|
-
if (!hook) {
|
|
797
|
-
continue;
|
|
798
|
-
}
|
|
799
|
-
let replacement;
|
|
800
|
-
let denied;
|
|
801
|
-
await hook({
|
|
802
|
-
plugin: { name: plugin.name },
|
|
803
|
-
log: createAgentPluginLogger(plugin.name),
|
|
804
|
-
requester: input.requester,
|
|
805
|
-
tool: {
|
|
806
|
-
name: tool.name,
|
|
807
|
-
input: nextInput
|
|
808
|
-
},
|
|
809
|
-
env: {
|
|
810
|
-
get(key2) {
|
|
811
|
-
return env[key2];
|
|
812
|
-
},
|
|
813
|
-
set(key2, value) {
|
|
814
|
-
env[key2] = value;
|
|
815
|
-
}
|
|
816
|
-
},
|
|
817
|
-
decision: {
|
|
818
|
-
deny(message) {
|
|
819
|
-
denied = message;
|
|
820
|
-
},
|
|
821
|
-
replaceInput(input2) {
|
|
822
|
-
replacement = input2;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
});
|
|
826
|
-
if (denied) {
|
|
827
|
-
throw new AgentPluginHookDeniedError(denied);
|
|
828
|
-
}
|
|
829
|
-
if (replacement !== void 0) {
|
|
830
|
-
if (!isRecord2(replacement)) {
|
|
831
|
-
throw new Error(
|
|
832
|
-
`Plugin "${plugin.name}" replaced tool input with a non-object value`
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
nextInput = { ...replacement };
|
|
836
|
-
Object.assign(env, normalizeEnv(nextInput.env));
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
return {
|
|
840
|
-
input: {
|
|
841
|
-
...nextInput,
|
|
842
|
-
...Object.keys(env).length > 0 ? { env } : {}
|
|
843
|
-
},
|
|
844
|
-
env
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
};
|
|
848
|
-
}
|
|
38
|
+
} from "./chunk-OK4KKR7B.js";
|
|
849
39
|
|
|
850
40
|
// src/chat/state/session-log.ts
|
|
851
41
|
import { isDeepStrictEqual } from "util";
|
|
@@ -1297,15 +487,21 @@ async function commitMessages(args) {
|
|
|
1297
487
|
var configuredStore;
|
|
1298
488
|
function getConfiguredConversationStore() {
|
|
1299
489
|
const databaseUrl = getChatConfig().sql.databaseUrl;
|
|
490
|
+
const driver = getChatConfig().sql.driver;
|
|
1300
491
|
if (!databaseUrl) {
|
|
1301
492
|
return createStateConversationStore();
|
|
1302
493
|
}
|
|
1303
|
-
if (configuredStore?.databaseUrl !== databaseUrl) {
|
|
494
|
+
if (configuredStore?.databaseUrl !== databaseUrl || configuredStore.driver !== driver) {
|
|
495
|
+
void configuredStore?.executor.close().catch(() => void 0);
|
|
496
|
+
const executor = createJuniorSqlExecutor({
|
|
497
|
+
connectionString: databaseUrl,
|
|
498
|
+
driver
|
|
499
|
+
});
|
|
1304
500
|
configuredStore = {
|
|
1305
501
|
databaseUrl,
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
)
|
|
502
|
+
driver,
|
|
503
|
+
executor,
|
|
504
|
+
store: createSqlStore(executor)
|
|
1309
505
|
};
|
|
1310
506
|
}
|
|
1311
507
|
return configuredStore.store;
|
|
@@ -1991,6 +1187,7 @@ var TOOL_POLICY_RULES = [
|
|
|
1991
1187
|
`- Sandbox-backed file and shell tools operate in an isolated workspace rooted at ${SANDBOX_WORKSPACE_ROOT}; readFile/writeFile paths are sandbox-workspace paths, bash runs inside that workspace, and attachFile accepts absolute or workspace-relative sandbox paths.`,
|
|
1992
1188
|
"- If a sandbox-backed tool reports that sandbox execution is unavailable, treat that as a blocker for local file/shell inspection; do not pretend host files were inspected.",
|
|
1993
1189
|
"- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
|
|
1190
|
+
"- Run `jr-rpc config get|set|unset|list` for provider defaults and `jr-rpc plugins list` for installed plugin introspection as standalone bash commands; do not chain them with `cd`, `&&`, pipes, or provider commands.",
|
|
1994
1191
|
"- If the first result is empty, stale, ambiguous, or incomplete, try a focused alternate query, path, command, or source before concluding the answer cannot be verified."
|
|
1995
1192
|
];
|
|
1996
1193
|
var TOOL_CALL_STYLE_RULES = [
|
|
@@ -2101,6 +1298,55 @@ function buildRuntimeSection(params) {
|
|
|
2101
1298
|
}
|
|
2102
1299
|
return renderTagBlock("runtime", lines.join("\n"));
|
|
2103
1300
|
}
|
|
1301
|
+
function formatSourceLines(source) {
|
|
1302
|
+
if (source.platform === "local") {
|
|
1303
|
+
return [
|
|
1304
|
+
"- source.platform: local",
|
|
1305
|
+
`- source.conversation_id: ${escapeXml(source.conversationId)}`
|
|
1306
|
+
];
|
|
1307
|
+
}
|
|
1308
|
+
return [
|
|
1309
|
+
"- source.platform: slack",
|
|
1310
|
+
`- source.team_id: ${escapeXml(source.teamId)}`,
|
|
1311
|
+
`- source.channel_id: ${escapeXml(source.channelId)}`,
|
|
1312
|
+
...source.messageTs ? [`- source.message_ts: ${escapeXml(source.messageTs)}`] : [],
|
|
1313
|
+
...source.threadTs ? [`- source.thread_ts: ${escapeXml(source.threadTs)}`] : []
|
|
1314
|
+
];
|
|
1315
|
+
}
|
|
1316
|
+
function formatDestinationLines(destination) {
|
|
1317
|
+
if (destination.platform === "local") {
|
|
1318
|
+
return [
|
|
1319
|
+
"- destination.platform: local",
|
|
1320
|
+
`- destination.conversation_id: ${escapeXml(destination.conversationId)}`
|
|
1321
|
+
];
|
|
1322
|
+
}
|
|
1323
|
+
return [
|
|
1324
|
+
"- destination.platform: slack",
|
|
1325
|
+
`- destination.team_id: ${escapeXml(destination.teamId)}`,
|
|
1326
|
+
`- destination.channel_id: ${escapeXml(destination.channelId)}`
|
|
1327
|
+
];
|
|
1328
|
+
}
|
|
1329
|
+
function buildDispatchSection(params) {
|
|
1330
|
+
if (!params) {
|
|
1331
|
+
return null;
|
|
1332
|
+
}
|
|
1333
|
+
const metadataLines = Object.entries(params.metadata ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(
|
|
1334
|
+
([key2, value]) => `- dispatch.metadata.${escapeXml(key2)}: ${escapeXml(value)}`
|
|
1335
|
+
);
|
|
1336
|
+
return renderTag("dispatch", [
|
|
1337
|
+
"- dispatch.execution: execute the dispatched input now",
|
|
1338
|
+
"- dispatch.delivery: the runtime delivers the final answer to the destination",
|
|
1339
|
+
"- dispatch.delivery_rule: do not request or require a separate posting tool just to deliver the final answer",
|
|
1340
|
+
...params.actor ? [
|
|
1341
|
+
`- dispatch.actor.type: ${escapeXml(params.actor.type)}`,
|
|
1342
|
+
`- dispatch.actor.id: ${escapeXml(params.actor.id)}`
|
|
1343
|
+
] : [],
|
|
1344
|
+
...params.plugin ? [`- dispatch.plugin: ${escapeXml(params.plugin)}`] : [],
|
|
1345
|
+
...formatSourceLines(params.source),
|
|
1346
|
+
...formatDestinationLines(params.destination),
|
|
1347
|
+
...metadataLines
|
|
1348
|
+
]);
|
|
1349
|
+
}
|
|
2104
1350
|
function buildContextSection(params) {
|
|
2105
1351
|
const blocks = [];
|
|
2106
1352
|
const referenceLines = formatReferenceFilesLines();
|
|
@@ -2120,6 +1366,10 @@ function buildContextSection(params) {
|
|
|
2120
1366
|
if (requesterLines) {
|
|
2121
1367
|
blocks.push(requesterLines);
|
|
2122
1368
|
}
|
|
1369
|
+
const dispatchLines = buildDispatchSection(params.dispatch);
|
|
1370
|
+
if (dispatchLines) {
|
|
1371
|
+
blocks.push(dispatchLines);
|
|
1372
|
+
}
|
|
2123
1373
|
const artifactLines = formatArtifactsLines(params.artifactState);
|
|
2124
1374
|
if (artifactLines) {
|
|
2125
1375
|
blocks.push(renderTag("artifacts", artifactLines));
|
|
@@ -2128,7 +1378,7 @@ function buildContextSection(params) {
|
|
|
2128
1378
|
if (configLines) {
|
|
2129
1379
|
blocks.push(
|
|
2130
1380
|
renderTag("configuration", [
|
|
2131
|
-
"Ambient provider defaults; explicit targets win.
|
|
1381
|
+
"Ambient provider defaults; explicit targets win.",
|
|
2132
1382
|
...configLines
|
|
2133
1383
|
])
|
|
2134
1384
|
);
|
|
@@ -2204,6 +1454,7 @@ function buildTurnContextPrompt(params) {
|
|
|
2204
1454
|
requester: params.requester,
|
|
2205
1455
|
artifactState: params.artifactState,
|
|
2206
1456
|
configuration: params.configuration,
|
|
1457
|
+
dispatch: params.dispatch,
|
|
2207
1458
|
invocation: params.invocation
|
|
2208
1459
|
}),
|
|
2209
1460
|
buildRuntimeSection(params.runtime ?? {})
|
|
@@ -2729,20 +1980,6 @@ export {
|
|
|
2729
1980
|
JUNIOR_PERSONALITY,
|
|
2730
1981
|
buildSystemPrompt,
|
|
2731
1982
|
buildTurnContextPrompt,
|
|
2732
|
-
createAgentPluginLogger,
|
|
2733
|
-
createPluginState,
|
|
2734
|
-
getSlackToolContext,
|
|
2735
|
-
bindSlackDirectCredentialSubject,
|
|
2736
|
-
verifySlackDirectCredentialSubject,
|
|
2737
|
-
resolveChannelCapabilities,
|
|
2738
|
-
validateAgentPlugins,
|
|
2739
|
-
setAgentPlugins,
|
|
2740
|
-
getAgentPlugins,
|
|
2741
|
-
getAgentPluginTools,
|
|
2742
|
-
getAgentPluginRoutes,
|
|
2743
|
-
getAgentPluginSlackConversationLink,
|
|
2744
|
-
getAgentPluginOperationalReports,
|
|
2745
|
-
createAgentPluginHookRunner,
|
|
2746
1983
|
loadProjection,
|
|
2747
1984
|
loadConnectedMcpProviders,
|
|
2748
1985
|
recordMcpProviderConnected,
|