@sentry/junior 0.72.0 → 0.74.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/bin/junior.mjs +12 -10
- package/dist/api-reference.d.ts +1 -1
- package/dist/app.d.ts +11 -2
- package/dist/app.js +1233 -13143
- package/dist/chat/agent-dispatch/runner.d.ts +2 -0
- package/dist/chat/agent-dispatch/store.d.ts +2 -2
- package/dist/chat/agent-dispatch/types.d.ts +6 -3
- package/dist/chat/agent-dispatch/validation.d.ts +2 -3
- package/dist/chat/app/production.d.ts +11 -1
- package/dist/chat/app/services.d.ts +7 -0
- package/dist/chat/config.d.ts +3 -0
- package/dist/chat/conversations/configured.d.ts +5 -0
- package/dist/chat/conversations/sql/migrations.d.ts +11 -0
- package/dist/chat/conversations/sql/schema/conversations.d.ts +435 -0
- package/dist/chat/conversations/sql/schema/destinations.d.ts +200 -0
- package/dist/chat/conversations/sql/schema/identities.d.ts +214 -0
- package/dist/chat/conversations/sql/schema/migrations.d.ts +58 -0
- package/dist/chat/conversations/sql/schema/timestamps.d.ts +1 -0
- package/dist/chat/conversations/sql/schema.d.ts +910 -0
- package/dist/chat/conversations/sql/store.d.ts +52 -0
- package/dist/chat/conversations/state.d.ts +4 -0
- package/dist/chat/conversations/store.d.ts +57 -0
- package/dist/chat/destination.d.ts +3 -1
- package/dist/chat/ingress/slack-webhook.d.ts +2 -0
- package/dist/chat/logging.d.ts +3 -0
- package/dist/chat/oauth-flow.d.ts +1 -1
- package/dist/chat/plugins/agent-hooks.d.ts +2 -2
- package/dist/chat/plugins/registry.d.ts +2 -0
- package/dist/chat/plugins/types.d.ts +2 -0
- package/dist/chat/prompt.d.ts +4 -1
- package/dist/chat/requester.d.ts +19 -12
- package/dist/chat/respond.d.ts +7 -3
- package/dist/chat/runtime/agent-continue-runner.d.ts +1 -1
- package/dist/chat/runtime/reply-executor.d.ts +3 -1
- package/dist/chat/runtime/slack-resume.d.ts +3 -3
- package/dist/chat/runtime/slack-runtime.d.ts +13 -3
- package/dist/chat/runtime/turn.d.ts +15 -1
- package/dist/chat/sandbox/egress-credentials.d.ts +5 -2
- package/dist/chat/sandbox/egress-policy.d.ts +5 -1
- package/dist/chat/sandbox/egress-proxy.d.ts +2 -0
- package/dist/chat/sandbox/egress-schemas.d.ts +4 -0
- package/dist/chat/sandbox/egress-session.d.ts +3 -1
- package/dist/chat/sandbox/egress-tracing.d.ts +7 -0
- package/dist/chat/sandbox/sandbox.d.ts +2 -0
- package/dist/chat/sandbox/session.d.ts +3 -2
- package/dist/chat/services/auth-pause-response.d.ts +1 -1
- package/dist/chat/services/auth-pause.d.ts +2 -1
- package/dist/chat/services/mcp-auth-orchestration.d.ts +7 -6
- package/dist/chat/services/message-actor-identity.d.ts +2 -2
- package/dist/chat/services/pending-auth.d.ts +2 -0
- package/dist/chat/services/plugin-auth-orchestration.d.ts +14 -12
- package/dist/chat/services/turn-result.d.ts +3 -0
- package/dist/chat/slack/user.d.ts +2 -2
- package/dist/chat/sql/db.d.ts +20 -0
- package/dist/chat/sql/neon.d.ts +9 -0
- package/dist/chat/sql/schema.d.ts +906 -0
- package/dist/chat/state/turn-session.d.ts +3 -0
- package/dist/chat/task-execution/slack-work.d.ts +2 -0
- package/dist/chat/task-execution/state.d.ts +209 -0
- package/dist/chat/task-execution/store.d.ts +30 -114
- package/dist/chat/task-execution/vercel-callback.d.ts +2 -0
- package/dist/chat/task-execution/worker.d.ts +2 -0
- package/dist/chat/tools/slack/canvas-tools.d.ts +3 -2
- package/dist/chat/tools/slack/channel-list-messages.d.ts +2 -2
- package/dist/chat/tools/slack/channel-post-message.d.ts +3 -2
- package/dist/chat/tools/slack/context.d.ts +15 -2
- package/dist/chat/tools/slack/message-add-reaction.d.ts +3 -2
- package/dist/chat/tools/slack/thread-read.d.ts +2 -2
- package/dist/chat/tools/types.d.ts +19 -19
- package/dist/chunk-2LUZA3LY.js +275 -0
- package/dist/{chunk-6GEYPE6T.js → chunk-3BYAPS6B.js} +30 -6
- package/dist/{chunk-VLIO6RQR.js → chunk-6UP2Z2RZ.js} +4 -4
- package/dist/{chunk-GB3AL54K.js → chunk-7Q5YOUUT.js} +10 -2
- package/dist/{chunk-PP7AGSBU.js → chunk-CYUI7JU5.js} +18 -8
- package/dist/{chunk-3FYPXHPL.js → chunk-F6HWCPOC.js} +1 -1
- package/dist/{chunk-ZJQPA67D.js → chunk-GM7HTXYC.js} +230 -224
- package/dist/{chunk-VSNA5KAB.js → chunk-HYHKTFG2.js} +1338 -1499
- package/dist/chunk-JL2SLRAT.js +1970 -0
- package/dist/{chunk-4JXCSGSA.js → chunk-M4FLLXXD.js} +1 -1
- package/dist/{chunk-55XEZFGD.js → chunk-OR6NQJ5E.js} +2 -2
- package/dist/chunk-SJHUF3DP.js +43 -0
- package/dist/chunk-SQGMG7OD.js +12801 -0
- package/dist/{chunk-QUXPUKBH.js → chunk-Y7X25LFY.js} +1 -1
- package/dist/{chunk-ICKIDP7G.js → chunk-YOHFWWBV.js} +1 -1
- package/dist/{chunk-XC33FJZN.js → chunk-YRDS7VKO.js} +25 -4
- package/dist/cli/chat.js +205 -0
- package/dist/cli/check.js +9 -10
- package/dist/cli/init.js +1 -1
- package/dist/cli/run.js +10 -2
- package/dist/cli/snapshot-warmup.js +7 -7
- package/dist/cli/upgrade.js +81 -11
- package/dist/deployment.d.ts +4 -0
- package/dist/handlers/agent-dispatch.d.ts +6 -1
- package/dist/handlers/mcp-oauth-callback.d.ts +6 -1
- package/dist/handlers/oauth-callback.d.ts +6 -1
- package/dist/handlers/sandbox-egress-proxy.d.ts +2 -0
- package/dist/handlers/webhooks.d.ts +4 -2
- package/dist/instrumentation.js +17 -3
- package/dist/nitro.js +9 -10
- package/dist/reporting/conversations.d.ts +13 -3
- package/dist/reporting.d.ts +9 -2
- package/dist/reporting.js +114 -48
- package/dist/runner-27NP2TEO.js +259 -0
- package/dist/vercel.d.ts +6 -1
- package/dist/vercel.js +1 -1
- package/package.json +9 -4
- package/dist/chunk-6YY4Q3D4.js +0 -12
- package/dist/chunk-HNMUVGSR.js +0 -1119
- package/dist/chunk-Z3YD6NHK.js +0 -12
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
createNeonJuniorSqlExecutor,
|
|
3
|
+
createSqlStore,
|
|
4
|
+
createStateConversationStore
|
|
5
|
+
} from "./chunk-JL2SLRAT.js";
|
|
4
6
|
import {
|
|
5
7
|
isConversationChannel,
|
|
6
8
|
isConversationScopedChannel,
|
|
7
9
|
isDmChannel,
|
|
8
10
|
normalizeSlackConversationId,
|
|
9
11
|
parseDestination
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YRDS7VKO.js";
|
|
11
13
|
import {
|
|
12
14
|
SANDBOX_DATA_ROOT,
|
|
13
15
|
SANDBOX_WORKSPACE_ROOT,
|
|
@@ -21,29 +23,24 @@ import {
|
|
|
21
23
|
import {
|
|
22
24
|
getConnectedStateContext,
|
|
23
25
|
getStateAdapter
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-F6HWCPOC.js";
|
|
25
27
|
import {
|
|
26
28
|
TURN_CONTEXT_TAG,
|
|
27
29
|
botConfig,
|
|
28
|
-
getChatConfig
|
|
29
|
-
|
|
30
|
-
} from "./chunk-ZJQPA67D.js";
|
|
30
|
+
getChatConfig
|
|
31
|
+
} from "./chunk-GM7HTXYC.js";
|
|
31
32
|
import {
|
|
32
33
|
isActorUserId,
|
|
33
34
|
parseActorUserId,
|
|
34
35
|
parseStoredSlackRequester,
|
|
35
36
|
storedSlackRequesterSchema
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-CYUI7JU5.js";
|
|
37
38
|
import {
|
|
38
39
|
isRecord,
|
|
39
40
|
logException,
|
|
40
41
|
logInfo,
|
|
41
|
-
logWarn
|
|
42
|
-
|
|
43
|
-
} from "./chunk-6GEYPE6T.js";
|
|
44
|
-
import {
|
|
45
|
-
sentry_exports
|
|
46
|
-
} from "./chunk-Z3YD6NHK.js";
|
|
42
|
+
logWarn
|
|
43
|
+
} from "./chunk-3BYAPS6B.js";
|
|
47
44
|
|
|
48
45
|
// src/chat/plugins/logging.ts
|
|
49
46
|
function createAgentPluginLogger(plugin) {
|
|
@@ -169,6 +166,23 @@ function createPluginState(plugin, options) {
|
|
|
169
166
|
};
|
|
170
167
|
}
|
|
171
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
|
+
|
|
172
186
|
// src/chat/credentials/subject.ts
|
|
173
187
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
174
188
|
var CREDENTIAL_SUBJECT_HMAC_CONTEXT = "junior.credential_subject.v1";
|
|
@@ -360,29 +374,43 @@ function getAgentPluginTools(context) {
|
|
|
360
374
|
}
|
|
361
375
|
const log = createAgentPluginLogger(plugin.name);
|
|
362
376
|
const destination = context.destination;
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const
|
|
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" ? {
|
|
370
390
|
plugin: { name: plugin.name },
|
|
371
391
|
log,
|
|
372
|
-
requester: context.requester,
|
|
373
|
-
channelCapabilities: pluginCapabilities,
|
|
374
|
-
channelId: context.channelId,
|
|
392
|
+
requester: context.requester?.platform === "slack" ? context.requester : void 0,
|
|
375
393
|
conversationId: context.conversationId,
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
messageTs: context.messageTs,
|
|
380
|
-
threadTs: context.threadTs,
|
|
394
|
+
destination: destination?.platform === "slack" ? destination : void 0,
|
|
395
|
+
slack: slackContext,
|
|
396
|
+
source: context.source,
|
|
381
397
|
userText: context.userText,
|
|
382
398
|
state: createPluginState(plugin.name, {
|
|
383
399
|
legacyStatePrefixes: plugin.legacyStatePrefixes
|
|
384
400
|
})
|
|
385
|
-
}
|
|
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);
|
|
386
414
|
for (const [name, tool] of Object.entries(pluginTools)) {
|
|
387
415
|
if (!AGENT_PLUGIN_TOOL_NAME_RE.test(name)) {
|
|
388
416
|
throw new Error(
|
|
@@ -657,7 +685,7 @@ function failedOperationalReport(args) {
|
|
|
657
685
|
]
|
|
658
686
|
};
|
|
659
687
|
}
|
|
660
|
-
async function getAgentPluginOperationalReports(nowMs
|
|
688
|
+
async function getAgentPluginOperationalReports(nowMs, conversations) {
|
|
661
689
|
const reports = [];
|
|
662
690
|
for (const plugin of getAgentPlugins()) {
|
|
663
691
|
const hook = plugin.hooks?.operationalReport;
|
|
@@ -672,6 +700,7 @@ async function getAgentPluginOperationalReports(nowMs = Date.now()) {
|
|
|
672
700
|
const report = await hook({
|
|
673
701
|
plugin: { name: plugin.name },
|
|
674
702
|
log,
|
|
703
|
+
conversations,
|
|
675
704
|
nowMs,
|
|
676
705
|
state: pluginReadState(state)
|
|
677
706
|
});
|
|
@@ -818,1345 +847,1378 @@ function createAgentPluginHookRunner(input = {}) {
|
|
|
818
847
|
};
|
|
819
848
|
}
|
|
820
849
|
|
|
821
|
-
// src/
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
850
|
+
// src/chat/state/session-log.ts
|
|
851
|
+
import { isDeepStrictEqual } from "util";
|
|
852
|
+
import { z } from "zod";
|
|
853
|
+
var AGENT_SESSION_LOG_PREFIX = "junior:agent-session-log";
|
|
854
|
+
var AGENT_SESSION_LOG_SCHEMA_VERSION = 1;
|
|
855
|
+
var INITIAL_SESSION_ID = "session_0";
|
|
856
|
+
var SESSION_ID_PREFIX = "session_";
|
|
857
|
+
var piMessageSchema = z.object({
|
|
858
|
+
role: z.string()
|
|
859
|
+
}).passthrough().transform((value) => value);
|
|
860
|
+
var piMessageEntrySchema = z.object({
|
|
861
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
862
|
+
type: z.literal("pi_message"),
|
|
863
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
864
|
+
message: piMessageSchema,
|
|
865
|
+
requester: storedSlackRequesterSchema.optional()
|
|
866
|
+
});
|
|
867
|
+
var projectionResetEntrySchema = z.object({
|
|
868
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
869
|
+
type: z.literal("projection_reset"),
|
|
870
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
871
|
+
messages: z.array(piMessageSchema),
|
|
872
|
+
requester: storedSlackRequesterSchema.optional()
|
|
873
|
+
});
|
|
874
|
+
var requesterRecordedEntrySchema = z.object({
|
|
875
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
876
|
+
type: z.literal("requester_recorded"),
|
|
877
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
878
|
+
requester: storedSlackRequesterSchema
|
|
879
|
+
});
|
|
880
|
+
var mcpProviderConnectedEntrySchema = z.object({
|
|
881
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
882
|
+
type: z.literal("mcp_provider_connected"),
|
|
883
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
884
|
+
provider: z.string().min(1)
|
|
885
|
+
});
|
|
886
|
+
var authorizationKindSchema = z.union([
|
|
887
|
+
z.literal("plugin"),
|
|
888
|
+
z.literal("mcp")
|
|
889
|
+
]);
|
|
890
|
+
var authorizationRequestedEntrySchema = z.object({
|
|
891
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
892
|
+
type: z.literal("authorization_requested"),
|
|
893
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
894
|
+
createdAtMs: z.number().int().nonnegative(),
|
|
895
|
+
kind: authorizationKindSchema,
|
|
896
|
+
provider: z.string().min(1),
|
|
897
|
+
requesterId: z.string().min(1),
|
|
898
|
+
authorizationId: z.string().min(1),
|
|
899
|
+
delivery: z.union([
|
|
900
|
+
z.literal("private_link_sent"),
|
|
901
|
+
z.literal("private_link_reused")
|
|
902
|
+
])
|
|
903
|
+
});
|
|
904
|
+
var authorizationCompletedEntrySchema = z.object({
|
|
905
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
906
|
+
type: z.literal("authorization_completed"),
|
|
907
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
908
|
+
createdAtMs: z.number().int().nonnegative(),
|
|
909
|
+
kind: authorizationKindSchema,
|
|
910
|
+
provider: z.string().min(1),
|
|
911
|
+
requesterId: z.string().min(1),
|
|
912
|
+
authorizationId: z.string().min(1)
|
|
913
|
+
});
|
|
914
|
+
var sessionLogEntrySchema = z.discriminatedUnion("type", [
|
|
915
|
+
piMessageEntrySchema,
|
|
916
|
+
projectionResetEntrySchema,
|
|
917
|
+
requesterRecordedEntrySchema,
|
|
918
|
+
mcpProviderConnectedEntrySchema,
|
|
919
|
+
authorizationRequestedEntrySchema,
|
|
920
|
+
authorizationCompletedEntrySchema
|
|
921
|
+
]);
|
|
922
|
+
function key(scope) {
|
|
923
|
+
const prefix = getChatConfig().state.keyPrefix;
|
|
924
|
+
return [
|
|
925
|
+
...prefix ? [prefix] : [],
|
|
926
|
+
AGENT_SESSION_LOG_PREFIX,
|
|
927
|
+
scope.conversationId
|
|
928
|
+
].join(":");
|
|
833
929
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
import fs from "fs";
|
|
837
|
-
import path from "path";
|
|
838
|
-
|
|
839
|
-
// src/chat/interruption-marker.ts
|
|
840
|
-
var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
|
|
841
|
-
function getInterruptionMarker() {
|
|
842
|
-
return INTERRUPTED_MARKER;
|
|
930
|
+
function rawKey(scope) {
|
|
931
|
+
return [AGENT_SESSION_LOG_PREFIX, scope.conversationId].join(":");
|
|
843
932
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
847
|
-
function truncateStatusText(text) {
|
|
848
|
-
const trimmed = text.trim();
|
|
849
|
-
if (!trimmed) {
|
|
850
|
-
return "";
|
|
851
|
-
}
|
|
852
|
-
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
853
|
-
return trimmed;
|
|
854
|
-
}
|
|
855
|
-
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
933
|
+
function normalizeMessageCount(value) {
|
|
934
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
856
935
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
let n = 1;
|
|
864
|
-
while (line[start + n] === "`") {
|
|
865
|
-
n++;
|
|
866
|
-
}
|
|
867
|
-
const marker = "`".repeat(n);
|
|
868
|
-
let search = start + n;
|
|
869
|
-
while (search < line.length) {
|
|
870
|
-
const close = line.indexOf(marker, search);
|
|
871
|
-
if (close === -1) {
|
|
872
|
-
return void 0;
|
|
873
|
-
}
|
|
874
|
-
const after = close + n;
|
|
875
|
-
if (line[after] !== "`") {
|
|
876
|
-
return { text: line.slice(start, after), end: after };
|
|
936
|
+
function countMatchingPrefix(left, right) {
|
|
937
|
+
const limit = Math.min(left.length, right.length);
|
|
938
|
+
for (let index = 0; index < limit; index += 1) {
|
|
939
|
+
if (!isDeepStrictEqual(left[index], right[index])) {
|
|
940
|
+
return index;
|
|
877
941
|
}
|
|
878
|
-
search = after + 1;
|
|
879
942
|
}
|
|
880
|
-
return
|
|
943
|
+
return limit;
|
|
881
944
|
}
|
|
882
|
-
function
|
|
883
|
-
|
|
884
|
-
return void 0;
|
|
885
|
-
}
|
|
886
|
-
const close = line.indexOf(">", start + 1);
|
|
887
|
-
if (close === -1) {
|
|
888
|
-
return void 0;
|
|
889
|
-
}
|
|
890
|
-
const body = line.slice(start + 1, close);
|
|
891
|
-
if (/^(?:https?:\/\/|@|#|!)/.test(body)) {
|
|
892
|
-
return { text: line.slice(start, close + 1), end: close + 1 };
|
|
893
|
-
}
|
|
894
|
-
return void 0;
|
|
945
|
+
function entrySessionId(entry) {
|
|
946
|
+
return entry.sessionId ?? INITIAL_SESSION_ID;
|
|
895
947
|
}
|
|
896
|
-
function
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
if (labelEnd === -1) {
|
|
902
|
-
return void 0;
|
|
903
|
-
}
|
|
904
|
-
const destStart = labelEnd + 2;
|
|
905
|
-
if (!line.startsWith("http://", destStart) && !line.startsWith("https://", destStart)) {
|
|
906
|
-
return void 0;
|
|
907
|
-
}
|
|
908
|
-
const closeParens = line.indexOf(")", destStart);
|
|
909
|
-
if (closeParens === -1) {
|
|
910
|
-
return void 0;
|
|
948
|
+
function latestProjectionResetIndex(entries) {
|
|
949
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
950
|
+
if (entries[index]?.type === "projection_reset") {
|
|
951
|
+
return index;
|
|
952
|
+
}
|
|
911
953
|
}
|
|
912
|
-
return
|
|
954
|
+
return -1;
|
|
913
955
|
}
|
|
914
|
-
function
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
else if (ch === ")") balance--;
|
|
956
|
+
function currentSessionId(entries) {
|
|
957
|
+
const resetIndex = latestProjectionResetIndex(entries);
|
|
958
|
+
if (resetIndex < 0) {
|
|
959
|
+
return INITIAL_SESSION_ID;
|
|
919
960
|
}
|
|
920
|
-
return
|
|
961
|
+
return entrySessionId(entries[resetIndex]);
|
|
921
962
|
}
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
963
|
+
function nextSessionId(entries) {
|
|
964
|
+
const resetCount = entries.filter(
|
|
965
|
+
(entry) => entry.type === "projection_reset"
|
|
966
|
+
).length;
|
|
967
|
+
return `${SESSION_ID_PREFIX}${resetCount + 1}`;
|
|
968
|
+
}
|
|
969
|
+
function projectionEntries(entries, sessionId) {
|
|
970
|
+
if (sessionId) {
|
|
971
|
+
const sessionEntries = [];
|
|
972
|
+
let started = false;
|
|
973
|
+
for (const entry of entries) {
|
|
974
|
+
const entryId = entrySessionId(entry);
|
|
975
|
+
if (!started) {
|
|
976
|
+
if (entryId !== sessionId) {
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
started = true;
|
|
980
|
+
} else if (entry.type === "projection_reset" && entryId !== sessionId) {
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
if (entryId === sessionId) {
|
|
984
|
+
sessionEntries.push(entry);
|
|
985
|
+
}
|
|
928
986
|
}
|
|
929
|
-
|
|
930
|
-
}
|
|
931
|
-
if (end === start) {
|
|
932
|
-
return void 0;
|
|
933
|
-
}
|
|
934
|
-
let raw = line.slice(start, end);
|
|
935
|
-
let suffix = "";
|
|
936
|
-
const peel = () => {
|
|
937
|
-
suffix = raw.slice(-1) + suffix;
|
|
938
|
-
raw = raw.slice(0, -1);
|
|
939
|
-
};
|
|
940
|
-
const shouldPeel = () => raw.endsWith("_") || /[.,!?;:]$/.test(raw) || raw.endsWith(")") && hasUnmatchedClosingParen(raw);
|
|
941
|
-
while (raw.length > 0 && shouldPeel()) {
|
|
942
|
-
peel();
|
|
943
|
-
}
|
|
944
|
-
if (!/^https?:\/\/.+/.test(raw)) {
|
|
945
|
-
return void 0;
|
|
987
|
+
return sessionEntries;
|
|
946
988
|
}
|
|
947
|
-
|
|
989
|
+
const resetIndex = latestProjectionResetIndex(entries);
|
|
990
|
+
const startIndex = resetIndex < 0 ? 0 : resetIndex;
|
|
991
|
+
const currentId = resetIndex < 0 ? INITIAL_SESSION_ID : entrySessionId(entries[resetIndex]);
|
|
992
|
+
return entries.slice(startIndex).filter((entry) => entrySessionId(entry) === currentId);
|
|
948
993
|
}
|
|
949
|
-
function
|
|
950
|
-
let
|
|
951
|
-
|
|
952
|
-
while (i < line.length) {
|
|
953
|
-
const codeSpan = readInlineCodeSpan(line, i);
|
|
954
|
-
if (codeSpan) {
|
|
955
|
-
result += codeSpan.text;
|
|
956
|
-
i = codeSpan.end;
|
|
957
|
-
continue;
|
|
958
|
-
}
|
|
959
|
-
const angleToken = readExistingSlackAngleToken(line, i);
|
|
960
|
-
if (angleToken) {
|
|
961
|
-
result += angleToken.text;
|
|
962
|
-
i = angleToken.end;
|
|
963
|
-
continue;
|
|
964
|
-
}
|
|
965
|
-
const mdLink = readMarkdownLink(line, i);
|
|
966
|
-
if (mdLink) {
|
|
967
|
-
result += mdLink.text;
|
|
968
|
-
i = mdLink.end;
|
|
969
|
-
continue;
|
|
970
|
-
}
|
|
971
|
-
if (line.startsWith("https://", i) || line.startsWith("http://", i)) {
|
|
972
|
-
const parsed = readBareUrl(line, i);
|
|
973
|
-
if (parsed) {
|
|
974
|
-
result += `<${parsed.url}>${parsed.suffix}`;
|
|
975
|
-
i = parsed.end;
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
result += line[i];
|
|
980
|
-
i++;
|
|
981
|
-
}
|
|
982
|
-
return result;
|
|
983
|
-
}
|
|
984
|
-
function wrapBareUrls(text) {
|
|
985
|
-
const lines = text.split("\n");
|
|
986
|
-
const out = [];
|
|
987
|
-
let inCodeBlock = false;
|
|
988
|
-
for (const line of lines) {
|
|
989
|
-
if (line.trimStart().startsWith("```")) {
|
|
990
|
-
inCodeBlock = !inCodeBlock;
|
|
991
|
-
out.push(line);
|
|
992
|
-
continue;
|
|
993
|
-
}
|
|
994
|
-
out.push(inCodeBlock ? line : wrapBareUrlsOnLine(line));
|
|
995
|
-
}
|
|
996
|
-
return out.join("\n");
|
|
997
|
-
}
|
|
998
|
-
function ensureBlockSpacing(text) {
|
|
999
|
-
const codeBlockPattern = /^```/;
|
|
1000
|
-
const listItemPattern = /^[-*•]\s|^\d+\.\s/;
|
|
1001
|
-
const lines = text.split("\n");
|
|
1002
|
-
const result = [];
|
|
1003
|
-
let inCodeBlock = false;
|
|
1004
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1005
|
-
const line = lines[i];
|
|
1006
|
-
const isCodeFence = codeBlockPattern.test(line.trimStart());
|
|
1007
|
-
if (isCodeFence) {
|
|
1008
|
-
if (!inCodeBlock) {
|
|
1009
|
-
const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
|
|
1010
|
-
if (prev2 !== void 0 && prev2.trim() !== "") {
|
|
1011
|
-
result.push("");
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
inCodeBlock = !inCodeBlock;
|
|
1015
|
-
result.push(line);
|
|
1016
|
-
continue;
|
|
1017
|
-
}
|
|
1018
|
-
if (inCodeBlock) {
|
|
1019
|
-
result.push(line);
|
|
1020
|
-
continue;
|
|
1021
|
-
}
|
|
1022
|
-
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
1023
|
-
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
|
|
1024
|
-
result.push("");
|
|
1025
|
-
}
|
|
1026
|
-
result.push(line);
|
|
994
|
+
function findLastIndex(values, predicate) {
|
|
995
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
996
|
+
if (predicate(values[index])) return index;
|
|
1027
997
|
}
|
|
1028
|
-
return
|
|
1029
|
-
}
|
|
1030
|
-
function normalizeSlackReplyMarkdown(text) {
|
|
1031
|
-
let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
|
|
1032
|
-
normalized = wrapBareUrls(normalized);
|
|
1033
|
-
normalized = ensureBlockSpacing(normalized);
|
|
1034
|
-
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
998
|
+
return -1;
|
|
1035
999
|
}
|
|
1036
|
-
function
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1000
|
+
function piEntry(message, sessionId, requester) {
|
|
1001
|
+
return {
|
|
1002
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1003
|
+
type: "pi_message",
|
|
1004
|
+
sessionId,
|
|
1005
|
+
message,
|
|
1006
|
+
...requester ? { requester } : {}
|
|
1007
|
+
};
|
|
1042
1008
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
}
|
|
1052
|
-
return text.split("\n").length;
|
|
1009
|
+
function resetEntry(messages, sessionId, requester) {
|
|
1010
|
+
return {
|
|
1011
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1012
|
+
type: "projection_reset",
|
|
1013
|
+
sessionId,
|
|
1014
|
+
messages,
|
|
1015
|
+
...requester ? { requester } : {}
|
|
1016
|
+
};
|
|
1053
1017
|
}
|
|
1054
|
-
function
|
|
1055
|
-
return
|
|
1018
|
+
function requesterRecordedEntry(requester, sessionId) {
|
|
1019
|
+
return {
|
|
1020
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1021
|
+
type: "requester_recorded",
|
|
1022
|
+
sessionId,
|
|
1023
|
+
requester
|
|
1024
|
+
};
|
|
1056
1025
|
}
|
|
1057
|
-
function
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
return newlineIndex;
|
|
1065
|
-
}
|
|
1066
|
-
const spaceIndex = bounded.lastIndexOf(" ");
|
|
1067
|
-
if (spaceIndex > 0) {
|
|
1068
|
-
return spaceIndex;
|
|
1069
|
-
}
|
|
1070
|
-
return maxChars;
|
|
1026
|
+
function mcpProviderConnectedEntry(provider, sessionId) {
|
|
1027
|
+
return {
|
|
1028
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1029
|
+
type: "mcp_provider_connected",
|
|
1030
|
+
sessionId,
|
|
1031
|
+
provider
|
|
1032
|
+
};
|
|
1071
1033
|
}
|
|
1072
|
-
function
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1034
|
+
function authorizationObservationMessage(entry) {
|
|
1035
|
+
const label = entry.kind === "mcp" ? "MCP authorization" : "Authorization";
|
|
1036
|
+
return {
|
|
1037
|
+
role: "user",
|
|
1038
|
+
content: [
|
|
1039
|
+
{
|
|
1040
|
+
type: "text",
|
|
1041
|
+
text: `${label} completed for provider "${entry.provider}". Continue the blocked request and retry the provider operation if needed.`
|
|
1042
|
+
}
|
|
1043
|
+
],
|
|
1044
|
+
timestamp: entry.createdAtMs
|
|
1045
|
+
};
|
|
1081
1046
|
}
|
|
1082
|
-
function
|
|
1047
|
+
function authorizationRequestedEntry(args) {
|
|
1083
1048
|
return {
|
|
1084
|
-
|
|
1085
|
-
|
|
1049
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1050
|
+
type: "authorization_requested",
|
|
1051
|
+
sessionId: args.sessionId,
|
|
1052
|
+
createdAtMs: args.createdAtMs,
|
|
1053
|
+
kind: args.kind,
|
|
1054
|
+
provider: args.provider,
|
|
1055
|
+
requesterId: args.requesterId,
|
|
1056
|
+
authorizationId: args.authorizationId,
|
|
1057
|
+
delivery: args.delivery
|
|
1086
1058
|
};
|
|
1087
1059
|
}
|
|
1088
|
-
function
|
|
1089
|
-
const lineCount = countSlackLines(text);
|
|
1060
|
+
function authorizationCompletedEntry(args) {
|
|
1090
1061
|
return {
|
|
1091
|
-
|
|
1092
|
-
|
|
1062
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1063
|
+
type: "authorization_completed",
|
|
1064
|
+
sessionId: args.sessionId,
|
|
1065
|
+
createdAtMs: args.createdAtMs,
|
|
1066
|
+
kind: args.kind,
|
|
1067
|
+
provider: args.provider,
|
|
1068
|
+
requesterId: args.requesterId,
|
|
1069
|
+
authorizationId: args.authorizationId
|
|
1093
1070
|
};
|
|
1094
1071
|
}
|
|
1095
|
-
function
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1072
|
+
function decode(value) {
|
|
1073
|
+
if (typeof value === "string") {
|
|
1074
|
+
return decode(JSON.parse(value));
|
|
1075
|
+
}
|
|
1076
|
+
const parsed = sessionLogEntrySchema.safeParse(value);
|
|
1077
|
+
if (parsed.success) {
|
|
1078
|
+
return parsed.data;
|
|
1079
|
+
}
|
|
1080
|
+
return piEntry(piMessageSchema.parse(value), INITIAL_SESSION_ID);
|
|
1081
|
+
}
|
|
1082
|
+
function project(entries, sessionId) {
|
|
1083
|
+
let messages = [];
|
|
1084
|
+
let requester;
|
|
1085
|
+
for (const entry of projectionEntries(entries, sessionId)) {
|
|
1086
|
+
if (entry.type === "pi_message") {
|
|
1087
|
+
messages.push(entry.message);
|
|
1088
|
+
if (entry.message.role === "user" && entry.requester) {
|
|
1089
|
+
requester = entry.requester;
|
|
1090
|
+
}
|
|
1101
1091
|
continue;
|
|
1102
1092
|
}
|
|
1103
|
-
if (
|
|
1104
|
-
|
|
1105
|
-
fence: openerMatch[1],
|
|
1106
|
-
openerLine: trimmed
|
|
1107
|
-
};
|
|
1093
|
+
if (entry.type === "requester_recorded") {
|
|
1094
|
+
requester = entry.requester;
|
|
1108
1095
|
continue;
|
|
1109
1096
|
}
|
|
1110
|
-
if (
|
|
1111
|
-
|
|
1097
|
+
if (entry.type === "authorization_completed") {
|
|
1098
|
+
messages.push(authorizationObservationMessage(entry));
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (entry.type === "mcp_provider_connected" || entry.type === "authorization_requested") {
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
messages = [...entry.messages];
|
|
1105
|
+
if (entry.requester) {
|
|
1106
|
+
requester = entry.requester;
|
|
1112
1107
|
}
|
|
1113
1108
|
}
|
|
1114
|
-
|
|
1115
|
-
return null;
|
|
1116
|
-
}
|
|
1117
|
-
return {
|
|
1118
|
-
closeSuffix: text.endsWith("\n") ? open.fence : `
|
|
1119
|
-
${open.fence}`,
|
|
1120
|
-
reopenPrefix: `${open.openerLine}
|
|
1121
|
-
`
|
|
1122
|
-
};
|
|
1109
|
+
return { messages, requester };
|
|
1123
1110
|
}
|
|
1124
|
-
function
|
|
1125
|
-
|
|
1126
|
-
return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
|
|
1111
|
+
function projectMessages(entries, sessionId) {
|
|
1112
|
+
return project(entries, sessionId).messages;
|
|
1127
1113
|
}
|
|
1128
|
-
function
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
({ prefix, rest } = takeSlackInlinePrefix(
|
|
1135
|
-
text,
|
|
1136
|
-
forceSplitBudget(text, budget)
|
|
1137
|
-
));
|
|
1138
|
-
}
|
|
1139
|
-
let carryover = rest ? getFenceContinuation(prefix) : null;
|
|
1140
|
-
if (!carryover) {
|
|
1141
|
-
return { prefix, rest };
|
|
1142
|
-
}
|
|
1143
|
-
const carryoverBudget = reserveInlineBudgetForSuffix(
|
|
1144
|
-
`${carryover.closeSuffix}${CONTINUED_MARKER}`
|
|
1145
|
-
);
|
|
1146
|
-
({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
|
|
1147
|
-
if (!rest) {
|
|
1148
|
-
({ prefix, rest } = takeSlackInlinePrefix(
|
|
1149
|
-
text,
|
|
1150
|
-
forceSplitBudget(text, carryoverBudget)
|
|
1151
|
-
));
|
|
1152
|
-
}
|
|
1153
|
-
carryover = rest ? getFenceContinuation(prefix) : null;
|
|
1154
|
-
if (!carryover) {
|
|
1155
|
-
return { prefix, rest };
|
|
1114
|
+
function connectedMcpProviders(entries, sessionId) {
|
|
1115
|
+
const providers = /* @__PURE__ */ new Set();
|
|
1116
|
+
for (const entry of projectionEntries(entries, sessionId)) {
|
|
1117
|
+
if (entry.type === "mcp_provider_connected") {
|
|
1118
|
+
providers.add(entry.provider);
|
|
1119
|
+
}
|
|
1156
1120
|
}
|
|
1157
|
-
return
|
|
1158
|
-
prefix,
|
|
1159
|
-
rest: `${carryover.reopenPrefix}${rest}`
|
|
1160
|
-
};
|
|
1121
|
+
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
1161
1122
|
}
|
|
1162
|
-
function
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
if (options?.forceSplit) {
|
|
1169
|
-
return takeSlackContinuationChunk(text, budget);
|
|
1123
|
+
function commitEntries(existingMessages, nextMessages, sessionId, entries, existingRequester, requester) {
|
|
1124
|
+
const matchingPrefix = countMatchingPrefix(existingMessages, nextMessages);
|
|
1125
|
+
if (matchingPrefix === existingMessages.length) {
|
|
1126
|
+
const newMessages = nextMessages.slice(matchingPrefix);
|
|
1127
|
+
if (newMessages.length === 0 && requester && !isDeepStrictEqual(existingRequester, requester)) {
|
|
1128
|
+
return [requesterRecordedEntry(requester, sessionId)];
|
|
1170
1129
|
}
|
|
1171
|
-
const
|
|
1172
|
-
return
|
|
1173
|
-
|
|
1130
|
+
const requesterIndex = requester ? findLastIndex(newMessages, (m) => m.role === "user") : -1;
|
|
1131
|
+
return newMessages.map(
|
|
1132
|
+
(message, index) => piEntry(
|
|
1133
|
+
message,
|
|
1134
|
+
sessionId,
|
|
1135
|
+
index === requesterIndex ? requester : void 0
|
|
1136
|
+
)
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
return [
|
|
1140
|
+
resetEntry(
|
|
1141
|
+
nextMessages,
|
|
1142
|
+
nextSessionId(entries),
|
|
1143
|
+
requester ?? existingRequester
|
|
1144
|
+
)
|
|
1145
|
+
];
|
|
1146
|
+
}
|
|
1147
|
+
function redisStore(redisStateAdapter) {
|
|
1148
|
+
const client = redisStateAdapter.getClient();
|
|
1174
1149
|
return {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1150
|
+
async append({ entries, scope, ttlMs }) {
|
|
1151
|
+
const listKey = key(scope);
|
|
1152
|
+
if (entries.length > 0) {
|
|
1153
|
+
await client.rPush(
|
|
1154
|
+
listKey,
|
|
1155
|
+
entries.map((entry) => JSON.stringify(entry))
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
await client.pExpire(listKey, Math.max(1, ttlMs));
|
|
1159
|
+
},
|
|
1160
|
+
async read(scope) {
|
|
1161
|
+
const values = await client.lRange(key(scope), 0, -1);
|
|
1162
|
+
return values.map(decode);
|
|
1163
|
+
}
|
|
1178
1164
|
};
|
|
1179
1165
|
}
|
|
1180
|
-
function
|
|
1181
|
-
const
|
|
1182
|
-
const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
|
|
1183
|
-
const normalized = text.replace(/\r\n?/g, "\n");
|
|
1184
|
-
if (!normalized) {
|
|
1185
|
-
return { prefix: "", rest: "" };
|
|
1186
|
-
}
|
|
1187
|
-
if (fitsInlineBudget(normalized, maxChars, maxLines)) {
|
|
1188
|
-
return { prefix: normalized, rest: "" };
|
|
1189
|
-
}
|
|
1190
|
-
const lineBounded = splitByLineBudget(normalized, maxLines);
|
|
1191
|
-
const cutIndex = findSplitIndex(lineBounded, maxChars);
|
|
1192
|
-
const prefix = lineBounded.slice(0, cutIndex).trimEnd();
|
|
1193
|
-
if (prefix) {
|
|
1194
|
-
return {
|
|
1195
|
-
prefix,
|
|
1196
|
-
rest: normalized.slice(prefix.length).trimStart()
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
|
|
1166
|
+
function stateStore() {
|
|
1167
|
+
const stateAdapter = getStateAdapter();
|
|
1200
1168
|
return {
|
|
1201
|
-
|
|
1202
|
-
|
|
1169
|
+
async append({ entries, scope, ttlMs }) {
|
|
1170
|
+
const listKey = rawKey(scope);
|
|
1171
|
+
for (const entry of entries) {
|
|
1172
|
+
await stateAdapter.appendToList(listKey, entry, {
|
|
1173
|
+
ttlMs: Math.max(1, ttlMs)
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
async read(scope) {
|
|
1178
|
+
const values = await stateAdapter.getList(rawKey(scope));
|
|
1179
|
+
return values.map(decode);
|
|
1180
|
+
}
|
|
1203
1181
|
};
|
|
1204
1182
|
}
|
|
1205
|
-
function
|
|
1206
|
-
const
|
|
1207
|
-
if (
|
|
1208
|
-
return
|
|
1209
|
-
}
|
|
1210
|
-
const chunks = [];
|
|
1211
|
-
const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
1212
|
-
let remaining = normalized;
|
|
1213
|
-
while (remaining) {
|
|
1214
|
-
const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
|
|
1215
|
-
if (fitsFinalChunk) {
|
|
1216
|
-
chunks.push(
|
|
1217
|
-
options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
|
|
1218
|
-
);
|
|
1219
|
-
break;
|
|
1220
|
-
}
|
|
1221
|
-
const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
|
|
1222
|
-
...continuationBudget,
|
|
1223
|
-
forceSplit: true
|
|
1224
|
-
});
|
|
1225
|
-
chunks.push(renderedPrefix);
|
|
1226
|
-
remaining = rest;
|
|
1227
|
-
}
|
|
1228
|
-
if (chunks.length === 2) {
|
|
1229
|
-
chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
|
|
1183
|
+
async function defaultStore() {
|
|
1184
|
+
const { redisStateAdapter, stateAdapter } = await getConnectedStateContext();
|
|
1185
|
+
if (redisStateAdapter) {
|
|
1186
|
+
return redisStore(redisStateAdapter);
|
|
1230
1187
|
}
|
|
1231
|
-
|
|
1188
|
+
await stateAdapter.connect();
|
|
1189
|
+
return stateStore();
|
|
1232
1190
|
}
|
|
1233
|
-
function
|
|
1234
|
-
|
|
1191
|
+
async function loadEntries(args) {
|
|
1192
|
+
const store = args.store ?? await defaultStore();
|
|
1193
|
+
return await store.read(args);
|
|
1235
1194
|
}
|
|
1236
|
-
function
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
if (fileCount > 0) {
|
|
1241
|
-
return {
|
|
1242
|
-
raw: "",
|
|
1243
|
-
files
|
|
1244
|
-
};
|
|
1245
|
-
}
|
|
1246
|
-
throw new Error(
|
|
1247
|
-
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
1248
|
-
);
|
|
1195
|
+
async function loadMessages(args) {
|
|
1196
|
+
const messageCount = normalizeMessageCount(args.messageCount);
|
|
1197
|
+
if (messageCount === 0) {
|
|
1198
|
+
return [];
|
|
1249
1199
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
};
|
|
1200
|
+
const store = args.store ?? await defaultStore();
|
|
1201
|
+
const messages = projectMessages(await store.read(args), args.sessionId);
|
|
1202
|
+
return messages.length >= messageCount ? messages.slice(0, messageCount) : void 0;
|
|
1254
1203
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
};
|
|
1259
|
-
|
|
1260
|
-
// src/chat/prompt.ts
|
|
1261
|
-
var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
|
|
1262
|
-
function getLoggedMarkdownFiles() {
|
|
1263
|
-
const globalState = globalThis;
|
|
1264
|
-
globalState.__juniorLoggedMarkdownFiles ??= /* @__PURE__ */ new Set();
|
|
1265
|
-
return globalState.__juniorLoggedMarkdownFiles;
|
|
1204
|
+
async function loadProjection(args) {
|
|
1205
|
+
const store = args.store ?? await defaultStore();
|
|
1206
|
+
return project(await store.read(args), args.sessionId).messages;
|
|
1266
1207
|
}
|
|
1267
|
-
function
|
|
1268
|
-
|
|
1269
|
-
try {
|
|
1270
|
-
const raw = fs.readFileSync(resolved, "utf8").trim();
|
|
1271
|
-
if (raw.length > 0) {
|
|
1272
|
-
const loggedMarkdownFiles = getLoggedMarkdownFiles();
|
|
1273
|
-
const logKey = `${fileName}:${resolved}`;
|
|
1274
|
-
if (!loggedMarkdownFiles.has(logKey)) {
|
|
1275
|
-
loggedMarkdownFiles.add(logKey);
|
|
1276
|
-
logInfo(
|
|
1277
|
-
`${fileName.toLowerCase()}_loaded`,
|
|
1278
|
-
{},
|
|
1279
|
-
{
|
|
1280
|
-
"file.path": resolved
|
|
1281
|
-
},
|
|
1282
|
-
`Loaded ${fileName}`
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
return raw;
|
|
1286
|
-
}
|
|
1287
|
-
} catch {
|
|
1288
|
-
continue;
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
return null;
|
|
1208
|
+
async function loadConnectedMcpProviders(args) {
|
|
1209
|
+
return connectedMcpProviders(await loadEntries(args));
|
|
1292
1210
|
}
|
|
1293
|
-
function
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1296
|
-
|
|
1211
|
+
async function recordMcpProviderConnected(args) {
|
|
1212
|
+
const store = args.store ?? await defaultStore();
|
|
1213
|
+
const entries = await store.read(args);
|
|
1214
|
+
const sessionId = currentSessionId(entries);
|
|
1215
|
+
if (connectedMcpProviders(entries).includes(args.provider)) {
|
|
1216
|
+
return;
|
|
1297
1217
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
},
|
|
1304
|
-
"SOUL.md not found; using built-in default personality"
|
|
1305
|
-
);
|
|
1306
|
-
return DEFAULT_SOUL;
|
|
1218
|
+
await store.append({
|
|
1219
|
+
scope: args,
|
|
1220
|
+
entries: [mcpProviderConnectedEntry(args.provider, sessionId)],
|
|
1221
|
+
ttlMs: args.ttlMs
|
|
1222
|
+
});
|
|
1307
1223
|
}
|
|
1308
|
-
function
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
"soul_load_failed",
|
|
1317
|
-
{},
|
|
1318
|
-
{
|
|
1319
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1320
|
-
},
|
|
1321
|
-
"Failed to load SOUL.md; using built-in default personality"
|
|
1322
|
-
);
|
|
1323
|
-
return DEFAULT_SOUL;
|
|
1324
|
-
}
|
|
1325
|
-
})();
|
|
1326
|
-
var JUNIOR_WORLD = (() => {
|
|
1327
|
-
try {
|
|
1328
|
-
return loadWorld();
|
|
1329
|
-
} catch (error) {
|
|
1330
|
-
logWarn(
|
|
1331
|
-
"world_load_failed",
|
|
1332
|
-
{},
|
|
1333
|
-
{
|
|
1334
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1335
|
-
},
|
|
1336
|
-
"Failed to load WORLD.md; omitting world prompt context"
|
|
1337
|
-
);
|
|
1338
|
-
return null;
|
|
1224
|
+
async function recordAuthorizationRequested(args) {
|
|
1225
|
+
const store = args.store ?? await defaultStore();
|
|
1226
|
+
const entries = await store.read(args);
|
|
1227
|
+
const sessionId = currentSessionId(entries);
|
|
1228
|
+
if (projectionEntries(entries).some(
|
|
1229
|
+
(entry) => entry.type === "authorization_requested" && entry.authorizationId === args.authorizationId
|
|
1230
|
+
)) {
|
|
1231
|
+
return;
|
|
1339
1232
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1233
|
+
await store.append({
|
|
1234
|
+
scope: args,
|
|
1235
|
+
entries: [
|
|
1236
|
+
authorizationRequestedEntry({
|
|
1237
|
+
createdAtMs: Date.now(),
|
|
1238
|
+
kind: args.kind,
|
|
1239
|
+
sessionId,
|
|
1240
|
+
provider: args.provider,
|
|
1241
|
+
requesterId: args.requesterId,
|
|
1242
|
+
authorizationId: args.authorizationId,
|
|
1243
|
+
delivery: args.delivery
|
|
1244
|
+
})
|
|
1245
|
+
],
|
|
1246
|
+
ttlMs: args.ttlMs
|
|
1247
|
+
});
|
|
1343
1248
|
}
|
|
1344
|
-
function
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
return
|
|
1249
|
+
async function recordAuthorizationCompleted(args) {
|
|
1250
|
+
const store = args.store ?? await defaultStore();
|
|
1251
|
+
const entries = await store.read(args);
|
|
1252
|
+
const sessionId = currentSessionId(entries);
|
|
1253
|
+
if (projectionEntries(entries).some(
|
|
1254
|
+
(entry) => entry.type === "authorization_completed" && entry.authorizationId === args.authorizationId
|
|
1255
|
+
)) {
|
|
1256
|
+
return;
|
|
1352
1257
|
}
|
|
1258
|
+
await store.append({
|
|
1259
|
+
scope: args,
|
|
1260
|
+
entries: [
|
|
1261
|
+
authorizationCompletedEntry({
|
|
1262
|
+
createdAtMs: Date.now(),
|
|
1263
|
+
kind: args.kind,
|
|
1264
|
+
sessionId,
|
|
1265
|
+
provider: args.provider,
|
|
1266
|
+
requesterId: args.requesterId,
|
|
1267
|
+
authorizationId: args.authorizationId
|
|
1268
|
+
})
|
|
1269
|
+
],
|
|
1270
|
+
ttlMs: args.ttlMs
|
|
1271
|
+
});
|
|
1353
1272
|
}
|
|
1354
|
-
function
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1273
|
+
async function commitMessages(args) {
|
|
1274
|
+
const store = args.store ?? await defaultStore();
|
|
1275
|
+
const entries = await store.read(args);
|
|
1276
|
+
const existingProjection = project(entries);
|
|
1277
|
+
const currentId = currentSessionId(entries);
|
|
1278
|
+
const nextEntries = commitEntries(
|
|
1279
|
+
existingProjection.messages,
|
|
1280
|
+
args.messages,
|
|
1281
|
+
currentId,
|
|
1282
|
+
entries,
|
|
1283
|
+
existingProjection.requester,
|
|
1284
|
+
args.requester
|
|
1285
|
+
);
|
|
1286
|
+
await store.append({
|
|
1287
|
+
scope: args,
|
|
1288
|
+
entries: nextEntries,
|
|
1289
|
+
ttlMs: args.ttlMs
|
|
1290
|
+
});
|
|
1291
|
+
return {
|
|
1292
|
+
sessionId: nextEntries.find((entry) => entry.type === "projection_reset")?.sessionId ?? currentId
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// src/chat/conversations/configured.ts
|
|
1297
|
+
var configuredStore;
|
|
1298
|
+
function getConfiguredConversationStore() {
|
|
1299
|
+
const databaseUrl = getChatConfig().sql.databaseUrl;
|
|
1300
|
+
if (!databaseUrl) {
|
|
1301
|
+
return createStateConversationStore();
|
|
1302
|
+
}
|
|
1303
|
+
if (configuredStore?.databaseUrl !== databaseUrl) {
|
|
1304
|
+
configuredStore = {
|
|
1305
|
+
databaseUrl,
|
|
1306
|
+
store: createSqlStore(
|
|
1307
|
+
createNeonJuniorSqlExecutor({ connectionString: databaseUrl })
|
|
1308
|
+
)
|
|
1309
|
+
};
|
|
1358
1310
|
}
|
|
1359
|
-
return
|
|
1311
|
+
return configuredStore.store;
|
|
1360
1312
|
}
|
|
1361
|
-
function
|
|
1362
|
-
return
|
|
1313
|
+
function hasConfiguredSqlConversationStore() {
|
|
1314
|
+
return Boolean(getChatConfig().sql.databaseUrl);
|
|
1363
1315
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1316
|
+
|
|
1317
|
+
// src/chat/xml.ts
|
|
1318
|
+
function escapeXml(value) {
|
|
1319
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1366
1320
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
return
|
|
1321
|
+
|
|
1322
|
+
// src/chat/prompt.ts
|
|
1323
|
+
import fs from "fs";
|
|
1324
|
+
import path from "path";
|
|
1325
|
+
|
|
1326
|
+
// src/chat/interruption-marker.ts
|
|
1327
|
+
var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
|
|
1328
|
+
function getInterruptionMarker() {
|
|
1329
|
+
return INTERRUPTED_MARKER;
|
|
1376
1330
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
const sections = [];
|
|
1385
|
-
if (autoSelectable.length > 0) {
|
|
1386
|
-
const available = [
|
|
1387
|
-
"<available-skills>",
|
|
1388
|
-
"Scan before answering. Load the most specific matching skill; do not answer from memory when a skill fits. A request that names a skill, plugin, provider, or account matching a skill name is a skill match. If none fits, do not load a skill."
|
|
1389
|
-
];
|
|
1390
|
-
for (const skill of autoSelectable) {
|
|
1391
|
-
available.push(...formatSkillEntry(skill));
|
|
1392
|
-
}
|
|
1393
|
-
available.push("</available-skills>");
|
|
1394
|
-
sections.push(available.join("\n"));
|
|
1331
|
+
|
|
1332
|
+
// src/chat/slack/status-format.ts
|
|
1333
|
+
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
1334
|
+
function truncateStatusText(text) {
|
|
1335
|
+
const trimmed = text.trim();
|
|
1336
|
+
if (!trimmed) {
|
|
1337
|
+
return "";
|
|
1395
1338
|
}
|
|
1396
|
-
if (
|
|
1397
|
-
|
|
1398
|
-
"<user-callable-skills>",
|
|
1399
|
-
"The user's current message explicitly references this skill by name. Load it when relevant to the request."
|
|
1400
|
-
];
|
|
1401
|
-
for (const skill of invokedExplicitOnly) {
|
|
1402
|
-
userCallable.push(...formatSkillEntry(skill));
|
|
1403
|
-
}
|
|
1404
|
-
userCallable.push("</user-callable-skills>");
|
|
1405
|
-
sections.push(userCallable.join("\n"));
|
|
1339
|
+
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
1340
|
+
return trimmed;
|
|
1406
1341
|
}
|
|
1407
|
-
return
|
|
1342
|
+
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
1408
1343
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
"Active MCP provider catalogs are available through `searchMcpTools`. Call it with provider to list descriptors or with query to narrow results, then pass the exact returned `tool_name` to `callMcpTool`. Put provider fields inside `arguments`."
|
|
1415
|
-
];
|
|
1416
|
-
for (const catalog of catalogs) {
|
|
1417
|
-
lines.push(" <catalog>");
|
|
1418
|
-
lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
|
|
1419
|
-
lines.push(
|
|
1420
|
-
` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
|
|
1421
|
-
);
|
|
1422
|
-
lines.push(" </catalog>");
|
|
1344
|
+
|
|
1345
|
+
// src/chat/slack/mrkdwn.ts
|
|
1346
|
+
function readInlineCodeSpan(line, start) {
|
|
1347
|
+
if (line[start] !== "`") {
|
|
1348
|
+
return void 0;
|
|
1423
1349
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
const guidedTools = tools.filter(
|
|
1428
|
-
(tool) => Boolean(tool.promptSnippet?.trim()) || (tool.promptGuidelines?.length ?? 0) > 0
|
|
1429
|
-
);
|
|
1430
|
-
if (guidedTools.length === 0) {
|
|
1431
|
-
return null;
|
|
1350
|
+
let n = 1;
|
|
1351
|
+
while (line[start + n] === "`") {
|
|
1352
|
+
n++;
|
|
1432
1353
|
}
|
|
1433
|
-
const
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1354
|
+
const marker = "`".repeat(n);
|
|
1355
|
+
let search = start + n;
|
|
1356
|
+
while (search < line.length) {
|
|
1357
|
+
const close = line.indexOf(marker, search);
|
|
1358
|
+
if (close === -1) {
|
|
1359
|
+
return void 0;
|
|
1438
1360
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1361
|
+
const after = close + n;
|
|
1362
|
+
if (line[after] !== "`") {
|
|
1363
|
+
return { text: line.slice(start, after), end: after };
|
|
1443
1364
|
}
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1446
|
-
return lines.join("\n");
|
|
1447
|
-
}
|
|
1448
|
-
function formatReferenceFilesLines() {
|
|
1449
|
-
const files = listReferenceFiles();
|
|
1450
|
-
if (files.length === 0) {
|
|
1451
|
-
return null;
|
|
1365
|
+
search = after + 1;
|
|
1452
1366
|
}
|
|
1453
|
-
return
|
|
1454
|
-
const name = path.basename(filePath);
|
|
1455
|
-
return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
|
|
1456
|
-
});
|
|
1367
|
+
return void 0;
|
|
1457
1368
|
}
|
|
1458
|
-
function
|
|
1459
|
-
if (
|
|
1460
|
-
|
|
1461
|
-
if (artifactState.lastCanvasId) {
|
|
1462
|
-
lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
|
|
1369
|
+
function readExistingSlackAngleToken(line, start) {
|
|
1370
|
+
if (line[start] !== "<") {
|
|
1371
|
+
return void 0;
|
|
1463
1372
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1373
|
+
const close = line.indexOf(">", start + 1);
|
|
1374
|
+
if (close === -1) {
|
|
1375
|
+
return void 0;
|
|
1466
1376
|
}
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
lines.push(` - id: ${escapeXml(canvas.id)}`);
|
|
1471
|
-
if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
|
|
1472
|
-
if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
|
|
1473
|
-
if (canvas.createdAt) {
|
|
1474
|
-
lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1377
|
+
const body = line.slice(start + 1, close);
|
|
1378
|
+
if (/^(?:https?:\/\/|@|#|!)/.test(body)) {
|
|
1379
|
+
return { text: line.slice(start, close + 1), end: close + 1 };
|
|
1477
1380
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1381
|
+
return void 0;
|
|
1382
|
+
}
|
|
1383
|
+
function readMarkdownLink(line, start) {
|
|
1384
|
+
if (line[start] !== "[") {
|
|
1385
|
+
return void 0;
|
|
1480
1386
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1387
|
+
const labelEnd = line.indexOf("](", start + 1);
|
|
1388
|
+
if (labelEnd === -1) {
|
|
1389
|
+
return void 0;
|
|
1483
1390
|
}
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
const keys = Object.keys(configuration ?? {}).sort(
|
|
1488
|
-
(a, b) => a.localeCompare(b)
|
|
1489
|
-
);
|
|
1490
|
-
if (keys.length === 0) return null;
|
|
1491
|
-
return keys.map(
|
|
1492
|
-
(key2) => `- ${escapeXml(key2)}: ${formatConfigurationValue(configuration?.[key2])}`
|
|
1493
|
-
);
|
|
1494
|
-
}
|
|
1495
|
-
var HEADER = "You are a Slack-based helper assistant. Follow the personality section for voice and tone in every reply. Platform mechanics and output rules override personality and world context when they conflict.";
|
|
1496
|
-
var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
|
|
1497
|
-
var TOOL_POLICY_RULES = [
|
|
1498
|
-
"- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
|
|
1499
|
-
"- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
|
|
1500
|
-
"- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
|
|
1501
|
-
"- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
|
|
1502
|
-
"- For repository or implementation questions, inspect the target repository first: local checkout when present, otherwise the configured GitHub/source provider. Do not treat loaded skill files as repo source unless the user asks about the skill. Cite file paths, symbols, PRs/issues, commits, or URLs that support the answer.",
|
|
1503
|
-
`- 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.`,
|
|
1504
|
-
"- 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.",
|
|
1505
|
-
"- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
|
|
1506
|
-
"- 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."
|
|
1507
|
-
];
|
|
1508
|
-
var TOOL_CALL_STYLE_RULES = [
|
|
1509
|
-
"- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
|
|
1510
|
-
"- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
|
|
1511
|
-
"- When a first-class tool exists for an action, use it directly instead of asking the user to run an equivalent command, slash command, or manual lookup.",
|
|
1512
|
-
"- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
|
|
1513
|
-
];
|
|
1514
|
-
var SKILL_POLICY_RULES = [
|
|
1515
|
-
"- Only load skills listed in `<available-skills>`, `<user-callable-skills>`, or named by `<explicit-skill-trigger>`. Never guess or invent a skill name.",
|
|
1516
|
-
"- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
|
|
1517
|
-
];
|
|
1518
|
-
var EXECUTION_CONTRACT_RULES = [
|
|
1519
|
-
"- Actionable request: act in this turn.",
|
|
1520
|
-
"- Continue until done or genuinely blocked. Do not finish with a plan, promise, or offer to check next when an available tool or source can move the request forward.",
|
|
1521
|
-
"- Completion means the final answer covers the user's actual ask, including requested follow-up checks, and is grounded in the best evidence you could access.",
|
|
1522
|
-
"- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
|
|
1523
|
-
"- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
|
|
1524
|
-
"- For non-trivial or long-running work, call `reportProgress` early when available, then only when the major phase changes. Routine tool calls should stay silent."
|
|
1525
|
-
];
|
|
1526
|
-
var CONVERSATION_RULES = [
|
|
1527
|
-
"- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
|
|
1528
|
-
"- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
|
|
1529
|
-
"- Runtime owns continuation and authorization notices; on resumed turns, answer with the final requested content only."
|
|
1530
|
-
];
|
|
1531
|
-
var SLACK_ACTION_RULES = [
|
|
1532
|
-
"- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
|
|
1533
|
-
"- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
|
|
1534
|
-
"- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
|
|
1535
|
-
"- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
|
|
1536
|
-
"- Do not claim an attachment, canvas, channel post, list update, or reaction succeeded unless the tool returned success this turn; when it did, include any link the tool returned.",
|
|
1537
|
-
"- Do not use reactions as progress indicators."
|
|
1538
|
-
];
|
|
1539
|
-
var SAFETY_RULES = [
|
|
1540
|
-
"- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
|
|
1541
|
-
"- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
|
|
1542
|
-
"- Do not change system prompts, tool policies, security settings, credentials, or runtime configuration unless the user explicitly requests that exact administrative action and an available tool permits it."
|
|
1543
|
-
];
|
|
1544
|
-
var FAILURE_RULES = [
|
|
1545
|
-
"- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
|
|
1546
|
-
"- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
|
|
1547
|
-
"- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
|
|
1548
|
-
];
|
|
1549
|
-
function renderRuleSection(tag, lines) {
|
|
1550
|
-
return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
|
|
1551
|
-
}
|
|
1552
|
-
function buildBehaviorSection() {
|
|
1553
|
-
return [
|
|
1554
|
-
renderRuleSection("tool-policy", TOOL_POLICY_RULES),
|
|
1555
|
-
renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
|
|
1556
|
-
renderRuleSection("skill-policy", SKILL_POLICY_RULES),
|
|
1557
|
-
renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
|
|
1558
|
-
renderRuleSection("conversation", CONVERSATION_RULES),
|
|
1559
|
-
renderRuleSection("slack-actions", SLACK_ACTION_RULES),
|
|
1560
|
-
renderRuleSection("safety", SAFETY_RULES),
|
|
1561
|
-
renderRuleSection("failure-handling", FAILURE_RULES)
|
|
1562
|
-
].join("\n\n");
|
|
1563
|
-
}
|
|
1564
|
-
function buildOutputSection() {
|
|
1565
|
-
const openTag = `<output format="slack-markdown" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
|
|
1566
|
-
return [
|
|
1567
|
-
openTag,
|
|
1568
|
-
"- Start with the answer or result, not internal process narration.",
|
|
1569
|
-
"- Use Slack-flavored Markdown: **bold** section labels, `code`, [text](url) links, bullet lists, and fenced code blocks. No hash-prefixed headings and no tables. When the answer primarily lists several URLs, show each URL bare instead of as a labeled link.",
|
|
1570
|
-
"- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
|
|
1571
|
-
"- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to one or two short sentences plus the link; do not recap the canvas contents.",
|
|
1572
|
-
"- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
|
|
1573
|
-
"</output>"
|
|
1574
|
-
].join("\n");
|
|
1575
|
-
}
|
|
1576
|
-
function buildIdentitySection() {
|
|
1577
|
-
return [
|
|
1578
|
-
"# Identity",
|
|
1579
|
-
`Your Slack username is \`${botConfig.userName}\`.`
|
|
1580
|
-
].join("\n");
|
|
1581
|
-
}
|
|
1582
|
-
function buildPersonalitySection() {
|
|
1583
|
-
return ["# Personality", JUNIOR_PERSONALITY.trim()].join("\n");
|
|
1584
|
-
}
|
|
1585
|
-
function buildWorldSection() {
|
|
1586
|
-
if (!JUNIOR_WORLD) {
|
|
1587
|
-
return null;
|
|
1391
|
+
const destStart = labelEnd + 2;
|
|
1392
|
+
if (!line.startsWith("http://", destStart) && !line.startsWith("https://", destStart)) {
|
|
1393
|
+
return void 0;
|
|
1588
1394
|
}
|
|
1589
|
-
|
|
1395
|
+
const closeParens = line.indexOf(")", destStart);
|
|
1396
|
+
if (closeParens === -1) {
|
|
1397
|
+
return void 0;
|
|
1398
|
+
}
|
|
1399
|
+
return { text: line.slice(start, closeParens + 1), end: closeParens + 1 };
|
|
1590
1400
|
}
|
|
1591
|
-
function
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
].filter(Boolean);
|
|
1597
|
-
if (lines.length === 0) {
|
|
1598
|
-
return null;
|
|
1401
|
+
function hasUnmatchedClosingParen(text) {
|
|
1402
|
+
let balance = 0;
|
|
1403
|
+
for (const ch of text) {
|
|
1404
|
+
if (ch === "(") balance++;
|
|
1405
|
+
else if (ch === ")") balance--;
|
|
1599
1406
|
}
|
|
1600
|
-
return
|
|
1407
|
+
return balance < 0;
|
|
1601
1408
|
}
|
|
1602
|
-
function
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
])
|
|
1611
|
-
);
|
|
1409
|
+
function readBareUrl(line, start) {
|
|
1410
|
+
let end = start;
|
|
1411
|
+
while (end < line.length) {
|
|
1412
|
+
const ch = line[end];
|
|
1413
|
+
if (/\s/.test(ch) || ch === "<" || ch === ">" || ch === '"' || ch === "`" || ch === "|" || ch === "*") {
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
end++;
|
|
1612
1417
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
user_name: params.requester?.userName,
|
|
1616
|
-
user_id: params.requester?.userId
|
|
1617
|
-
});
|
|
1618
|
-
if (requesterLines) {
|
|
1619
|
-
blocks.push(requesterLines);
|
|
1418
|
+
if (end === start) {
|
|
1419
|
+
return void 0;
|
|
1620
1420
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1421
|
+
let raw = line.slice(start, end);
|
|
1422
|
+
let suffix = "";
|
|
1423
|
+
const peel = () => {
|
|
1424
|
+
suffix = raw.slice(-1) + suffix;
|
|
1425
|
+
raw = raw.slice(0, -1);
|
|
1426
|
+
};
|
|
1427
|
+
const shouldPeel = () => raw.endsWith("_") || /[.,!?;:]$/.test(raw) || raw.endsWith(")") && hasUnmatchedClosingParen(raw);
|
|
1428
|
+
while (raw.length > 0 && shouldPeel()) {
|
|
1429
|
+
peel();
|
|
1624
1430
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
blocks.push(
|
|
1628
|
-
renderTag("configuration", [
|
|
1629
|
-
"Ambient provider defaults; explicit targets win. Run `jr-rpc config get|set|unset|list` as standalone bash commands; do not chain with `cd`, `&&`, pipes, or provider commands.",
|
|
1630
|
-
...configLines
|
|
1631
|
-
])
|
|
1632
|
-
);
|
|
1431
|
+
if (!/^https?:\/\/.+/.test(raw)) {
|
|
1432
|
+
return void 0;
|
|
1633
1433
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
);
|
|
1434
|
+
return { url: raw, suffix, end };
|
|
1435
|
+
}
|
|
1436
|
+
function wrapBareUrlsOnLine(line) {
|
|
1437
|
+
let result = "";
|
|
1438
|
+
let i = 0;
|
|
1439
|
+
while (i < line.length) {
|
|
1440
|
+
const codeSpan = readInlineCodeSpan(line, i);
|
|
1441
|
+
if (codeSpan) {
|
|
1442
|
+
result += codeSpan.text;
|
|
1443
|
+
i = codeSpan.end;
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
const angleToken = readExistingSlackAngleToken(line, i);
|
|
1447
|
+
if (angleToken) {
|
|
1448
|
+
result += angleToken.text;
|
|
1449
|
+
i = angleToken.end;
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
const mdLink = readMarkdownLink(line, i);
|
|
1453
|
+
if (mdLink) {
|
|
1454
|
+
result += mdLink.text;
|
|
1455
|
+
i = mdLink.end;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
if (line.startsWith("https://", i) || line.startsWith("http://", i)) {
|
|
1459
|
+
const parsed = readBareUrl(line, i);
|
|
1460
|
+
if (parsed) {
|
|
1461
|
+
result += `<${parsed.url}>${parsed.suffix}`;
|
|
1462
|
+
i = parsed.end;
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
result += line[i];
|
|
1467
|
+
i++;
|
|
1641
1468
|
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1469
|
+
return result;
|
|
1470
|
+
}
|
|
1471
|
+
function wrapBareUrls(text) {
|
|
1472
|
+
const lines = text.split("\n");
|
|
1473
|
+
const out = [];
|
|
1474
|
+
let inCodeBlock = false;
|
|
1475
|
+
for (const line of lines) {
|
|
1476
|
+
if (line.trimStart().startsWith("```")) {
|
|
1477
|
+
inCodeBlock = !inCodeBlock;
|
|
1478
|
+
out.push(line);
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
out.push(inCodeBlock ? line : wrapBareUrlsOnLine(line));
|
|
1645
1482
|
}
|
|
1646
|
-
return
|
|
1483
|
+
return out.join("\n");
|
|
1647
1484
|
}
|
|
1648
|
-
function
|
|
1649
|
-
const
|
|
1650
|
-
const
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1485
|
+
function ensureBlockSpacing(text) {
|
|
1486
|
+
const codeBlockPattern = /^```/;
|
|
1487
|
+
const listItemPattern = /^[-*•]\s|^\d+\.\s/;
|
|
1488
|
+
const lines = text.split("\n");
|
|
1489
|
+
const result = [];
|
|
1490
|
+
let inCodeBlock = false;
|
|
1491
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1492
|
+
const line = lines[i];
|
|
1493
|
+
const isCodeFence = codeBlockPattern.test(line.trimStart());
|
|
1494
|
+
if (isCodeFence) {
|
|
1495
|
+
if (!inCodeBlock) {
|
|
1496
|
+
const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
|
|
1497
|
+
if (prev2 !== void 0 && prev2.trim() !== "") {
|
|
1498
|
+
result.push("");
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
inCodeBlock = !inCodeBlock;
|
|
1502
|
+
result.push(line);
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
if (inCodeBlock) {
|
|
1506
|
+
result.push(line);
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
1510
|
+
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
|
|
1511
|
+
result.push("");
|
|
1512
|
+
}
|
|
1513
|
+
result.push(line);
|
|
1656
1514
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1515
|
+
return result.join("\n");
|
|
1516
|
+
}
|
|
1517
|
+
function normalizeSlackReplyMarkdown(text) {
|
|
1518
|
+
let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
|
|
1519
|
+
normalized = wrapBareUrls(normalized);
|
|
1520
|
+
normalized = ensureBlockSpacing(normalized);
|
|
1521
|
+
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
1522
|
+
}
|
|
1523
|
+
function normalizeSlackStatusText(text) {
|
|
1524
|
+
const trimmed = text.trim();
|
|
1525
|
+
if (!trimmed) {
|
|
1526
|
+
return "";
|
|
1662
1527
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1528
|
+
return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// src/chat/slack/output.ts
|
|
1532
|
+
var MAX_INLINE_CHARS = 2200;
|
|
1533
|
+
var MAX_INLINE_LINES = 45;
|
|
1534
|
+
var CONTINUED_MARKER = "\n\n[Continued below]";
|
|
1535
|
+
function countSlackLines(text) {
|
|
1536
|
+
if (!text) {
|
|
1537
|
+
return 0;
|
|
1666
1538
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1539
|
+
return text.split("\n").length;
|
|
1540
|
+
}
|
|
1541
|
+
function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
1542
|
+
return text.length <= maxChars && countSlackLines(text) <= maxLines;
|
|
1543
|
+
}
|
|
1544
|
+
function findSplitIndex(text, maxChars) {
|
|
1545
|
+
if (text.length <= maxChars) {
|
|
1546
|
+
return text.length;
|
|
1669
1547
|
}
|
|
1670
|
-
|
|
1548
|
+
const bounded = text.slice(0, maxChars);
|
|
1549
|
+
const newlineIndex = bounded.lastIndexOf("\n");
|
|
1550
|
+
if (newlineIndex > 0) {
|
|
1551
|
+
return newlineIndex;
|
|
1552
|
+
}
|
|
1553
|
+
const spaceIndex = bounded.lastIndexOf(" ");
|
|
1554
|
+
if (spaceIndex > 0) {
|
|
1555
|
+
return spaceIndex;
|
|
1556
|
+
}
|
|
1557
|
+
return maxChars;
|
|
1671
1558
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
return STATIC_SYSTEM_PROMPT;
|
|
1559
|
+
function splitByLineBudget(text, maxLines) {
|
|
1560
|
+
if (maxLines <= 0) {
|
|
1561
|
+
return "";
|
|
1562
|
+
}
|
|
1563
|
+
const lines = text.split("\n");
|
|
1564
|
+
if (lines.length <= maxLines) {
|
|
1565
|
+
return text;
|
|
1566
|
+
}
|
|
1567
|
+
return lines.slice(0, maxLines).join("\n");
|
|
1682
1568
|
}
|
|
1683
|
-
function
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1569
|
+
function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
1570
|
+
return {
|
|
1571
|
+
maxChars: Math.max(1, maxChars - suffix.length),
|
|
1572
|
+
maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
function forceSplitBudget(text, budget) {
|
|
1576
|
+
const lineCount = countSlackLines(text);
|
|
1577
|
+
return {
|
|
1578
|
+
maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
|
|
1579
|
+
maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
function getFenceContinuation(text) {
|
|
1583
|
+
let open;
|
|
1584
|
+
for (const line of text.split("\n")) {
|
|
1585
|
+
const trimmed = line.trimStart();
|
|
1586
|
+
const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
1587
|
+
if (!openerMatch) {
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
if (!open) {
|
|
1591
|
+
open = {
|
|
1592
|
+
fence: openerMatch[1],
|
|
1593
|
+
openerLine: trimmed
|
|
1594
|
+
};
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1597
|
+
if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
|
|
1598
|
+
open = void 0;
|
|
1599
|
+
}
|
|
1687
1600
|
}
|
|
1688
|
-
|
|
1689
|
-
buildCapabilitiesSection({
|
|
1690
|
-
availableSkills: params.availableSkills,
|
|
1691
|
-
activeMcpCatalogs: params.activeMcpCatalogs ?? [],
|
|
1692
|
-
invocation: params.invocation,
|
|
1693
|
-
toolGuidance: params.toolGuidance ?? []
|
|
1694
|
-
}),
|
|
1695
|
-
buildContextSection({
|
|
1696
|
-
requester: params.requester,
|
|
1697
|
-
artifactState: params.artifactState,
|
|
1698
|
-
configuration: params.configuration,
|
|
1699
|
-
invocation: params.invocation
|
|
1700
|
-
}),
|
|
1701
|
-
buildRuntimeSection(params.runtime ?? {})
|
|
1702
|
-
].filter((section) => Boolean(section));
|
|
1703
|
-
if (runtimeSections.length === 0) {
|
|
1601
|
+
if (!open) {
|
|
1704
1602
|
return null;
|
|
1705
1603
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1604
|
+
return {
|
|
1605
|
+
closeSuffix: text.endsWith("\n") ? open.fence : `
|
|
1606
|
+
${open.fence}`,
|
|
1607
|
+
reopenPrefix: `${open.openerLine}
|
|
1608
|
+
`
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
function appendSlackSuffix(text, marker) {
|
|
1612
|
+
const carryover = getFenceContinuation(text);
|
|
1613
|
+
return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
|
|
1614
|
+
}
|
|
1615
|
+
function stripTrailingContinuationMarker(text) {
|
|
1616
|
+
return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
|
|
1714
1617
|
}
|
|
1618
|
+
function takeSlackContinuationChunk(text, budget) {
|
|
1619
|
+
let { prefix, rest } = takeSlackInlinePrefix(text, budget);
|
|
1620
|
+
if (!rest) {
|
|
1621
|
+
({ prefix, rest } = takeSlackInlinePrefix(
|
|
1622
|
+
text,
|
|
1623
|
+
forceSplitBudget(text, budget)
|
|
1624
|
+
));
|
|
1625
|
+
}
|
|
1626
|
+
let carryover = rest ? getFenceContinuation(prefix) : null;
|
|
1627
|
+
if (!carryover) {
|
|
1628
|
+
return { prefix, rest };
|
|
1629
|
+
}
|
|
1630
|
+
const carryoverBudget = reserveInlineBudgetForSuffix(
|
|
1631
|
+
`${carryover.closeSuffix}${CONTINUED_MARKER}`
|
|
1632
|
+
);
|
|
1633
|
+
({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
|
|
1634
|
+
if (!rest) {
|
|
1635
|
+
({ prefix, rest } = takeSlackInlinePrefix(
|
|
1636
|
+
text,
|
|
1637
|
+
forceSplitBudget(text, carryoverBudget)
|
|
1638
|
+
));
|
|
1639
|
+
}
|
|
1640
|
+
carryover = rest ? getFenceContinuation(prefix) : null;
|
|
1641
|
+
if (!carryover) {
|
|
1642
|
+
return { prefix, rest };
|
|
1643
|
+
}
|
|
1644
|
+
return {
|
|
1645
|
+
prefix,
|
|
1646
|
+
rest: `${carryover.reopenPrefix}${rest}`
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
function takeSlackContinuationPrefix(text, options) {
|
|
1650
|
+
const budget = {
|
|
1651
|
+
maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
|
|
1652
|
+
maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
|
|
1653
|
+
};
|
|
1654
|
+
const { prefix, rest } = (() => {
|
|
1655
|
+
if (options?.forceSplit) {
|
|
1656
|
+
return takeSlackContinuationChunk(text, budget);
|
|
1657
|
+
}
|
|
1658
|
+
const initial = takeSlackInlinePrefix(text, budget);
|
|
1659
|
+
return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
|
|
1660
|
+
})();
|
|
1661
|
+
return {
|
|
1662
|
+
prefix,
|
|
1663
|
+
renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
|
|
1664
|
+
rest
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
function takeSlackInlinePrefix(text, options) {
|
|
1668
|
+
const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
|
|
1669
|
+
const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
|
|
1670
|
+
const normalized = text.replace(/\r\n?/g, "\n");
|
|
1671
|
+
if (!normalized) {
|
|
1672
|
+
return { prefix: "", rest: "" };
|
|
1673
|
+
}
|
|
1674
|
+
if (fitsInlineBudget(normalized, maxChars, maxLines)) {
|
|
1675
|
+
return { prefix: normalized, rest: "" };
|
|
1676
|
+
}
|
|
1677
|
+
const lineBounded = splitByLineBudget(normalized, maxLines);
|
|
1678
|
+
const cutIndex = findSplitIndex(lineBounded, maxChars);
|
|
1679
|
+
const prefix = lineBounded.slice(0, cutIndex).trimEnd();
|
|
1680
|
+
if (prefix) {
|
|
1681
|
+
return {
|
|
1682
|
+
prefix,
|
|
1683
|
+
rest: normalized.slice(prefix.length).trimStart()
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
|
|
1687
|
+
return {
|
|
1688
|
+
prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
|
|
1689
|
+
rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
function splitSlackReplyText(text, options) {
|
|
1693
|
+
const normalized = normalizeSlackReplyMarkdown(text);
|
|
1694
|
+
if (!normalized) {
|
|
1695
|
+
return [];
|
|
1696
|
+
}
|
|
1697
|
+
const chunks = [];
|
|
1698
|
+
const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
1699
|
+
let remaining = normalized;
|
|
1700
|
+
while (remaining) {
|
|
1701
|
+
const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
|
|
1702
|
+
if (fitsFinalChunk) {
|
|
1703
|
+
chunks.push(
|
|
1704
|
+
options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
|
|
1705
|
+
);
|
|
1706
|
+
break;
|
|
1707
|
+
}
|
|
1708
|
+
const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
|
|
1709
|
+
...continuationBudget,
|
|
1710
|
+
forceSplit: true
|
|
1711
|
+
});
|
|
1712
|
+
chunks.push(renderedPrefix);
|
|
1713
|
+
remaining = rest;
|
|
1714
|
+
}
|
|
1715
|
+
if (chunks.length === 2) {
|
|
1716
|
+
chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
|
|
1717
|
+
}
|
|
1718
|
+
return chunks;
|
|
1719
|
+
}
|
|
1720
|
+
function getSlackContinuationBudget() {
|
|
1721
|
+
return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
1722
|
+
}
|
|
1723
|
+
function buildSlackOutputMessage(text, files) {
|
|
1724
|
+
const normalized = normalizeSlackReplyMarkdown(text);
|
|
1725
|
+
const fileCount = files?.length ?? 0;
|
|
1726
|
+
if (!normalized) {
|
|
1727
|
+
if (fileCount > 0) {
|
|
1728
|
+
return {
|
|
1729
|
+
raw: "",
|
|
1730
|
+
files
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
throw new Error(
|
|
1734
|
+
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
return {
|
|
1738
|
+
markdown: normalized,
|
|
1739
|
+
files
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
var slackOutputPolicy = {
|
|
1743
|
+
maxInlineChars: MAX_INLINE_CHARS,
|
|
1744
|
+
maxInlineLines: MAX_INLINE_LINES
|
|
1745
|
+
};
|
|
1715
1746
|
|
|
1716
|
-
// src/chat/
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1747
|
+
// src/chat/prompt.ts
|
|
1748
|
+
var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
|
|
1749
|
+
function getLoggedMarkdownFiles() {
|
|
1750
|
+
const globalState = globalThis;
|
|
1751
|
+
globalState.__juniorLoggedMarkdownFiles ??= /* @__PURE__ */ new Set();
|
|
1752
|
+
return globalState.__juniorLoggedMarkdownFiles;
|
|
1753
|
+
}
|
|
1754
|
+
function loadOptionalMarkdownFile(candidates, fileName) {
|
|
1755
|
+
for (const resolved of candidates) {
|
|
1756
|
+
try {
|
|
1757
|
+
const raw = fs.readFileSync(resolved, "utf8").trim();
|
|
1758
|
+
if (raw.length > 0) {
|
|
1759
|
+
const loggedMarkdownFiles = getLoggedMarkdownFiles();
|
|
1760
|
+
const logKey = `${fileName}:${resolved}`;
|
|
1761
|
+
if (!loggedMarkdownFiles.has(logKey)) {
|
|
1762
|
+
loggedMarkdownFiles.add(logKey);
|
|
1763
|
+
logInfo(
|
|
1764
|
+
`${fileName.toLowerCase()}_loaded`,
|
|
1765
|
+
{},
|
|
1766
|
+
{
|
|
1767
|
+
"file.path": resolved
|
|
1768
|
+
},
|
|
1769
|
+
`Loaded ${fileName}`
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
return raw;
|
|
1773
|
+
}
|
|
1774
|
+
} catch {
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
return null;
|
|
1779
|
+
}
|
|
1780
|
+
function loadSoul() {
|
|
1781
|
+
const soul = loadOptionalMarkdownFile(soulPathCandidates(), "SOUL.md");
|
|
1782
|
+
if (soul) {
|
|
1783
|
+
return soul;
|
|
1784
|
+
}
|
|
1785
|
+
logWarn(
|
|
1786
|
+
"soul_load_fallback",
|
|
1787
|
+
{},
|
|
1788
|
+
{
|
|
1789
|
+
"app.file.candidates": soulPathCandidates()
|
|
1790
|
+
},
|
|
1791
|
+
"SOUL.md not found; using built-in default personality"
|
|
1792
|
+
);
|
|
1793
|
+
return DEFAULT_SOUL;
|
|
1794
|
+
}
|
|
1795
|
+
function loadWorld() {
|
|
1796
|
+
return loadOptionalMarkdownFile(worldPathCandidates(), "WORLD.md");
|
|
1797
|
+
}
|
|
1798
|
+
var JUNIOR_PERSONALITY = (() => {
|
|
1799
|
+
try {
|
|
1800
|
+
return loadSoul();
|
|
1801
|
+
} catch (error) {
|
|
1802
|
+
logWarn(
|
|
1803
|
+
"soul_load_failed",
|
|
1804
|
+
{},
|
|
1805
|
+
{
|
|
1806
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1807
|
+
},
|
|
1808
|
+
"Failed to load SOUL.md; using built-in default personality"
|
|
1809
|
+
);
|
|
1810
|
+
return DEFAULT_SOUL;
|
|
1811
|
+
}
|
|
1812
|
+
})();
|
|
1813
|
+
var JUNIOR_WORLD = (() => {
|
|
1814
|
+
try {
|
|
1815
|
+
return loadWorld();
|
|
1816
|
+
} catch (error) {
|
|
1817
|
+
logWarn(
|
|
1818
|
+
"world_load_failed",
|
|
1819
|
+
{},
|
|
1820
|
+
{
|
|
1821
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1822
|
+
},
|
|
1823
|
+
"Failed to load WORLD.md; omitting world prompt context"
|
|
1824
|
+
);
|
|
1825
|
+
return null;
|
|
1826
|
+
}
|
|
1827
|
+
})();
|
|
1828
|
+
function workspaceSkillDir(skillName) {
|
|
1829
|
+
return sandboxSkillDir(skillName);
|
|
1795
1830
|
}
|
|
1796
|
-
function
|
|
1797
|
-
|
|
1831
|
+
function formatConfigurationValue(value) {
|
|
1832
|
+
if (typeof value === "string") {
|
|
1833
|
+
return escapeXml(value);
|
|
1834
|
+
}
|
|
1835
|
+
try {
|
|
1836
|
+
return escapeXml(JSON.stringify(value));
|
|
1837
|
+
} catch {
|
|
1838
|
+
return escapeXml(String(value));
|
|
1839
|
+
}
|
|
1798
1840
|
}
|
|
1799
|
-
function
|
|
1800
|
-
|
|
1841
|
+
function renderRequesterBlock(fields) {
|
|
1842
|
+
const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key2, value]) => `- ${key2}: ${escapeXml(value)}`);
|
|
1843
|
+
if (lines.length === 0) {
|
|
1844
|
+
return null;
|
|
1845
|
+
}
|
|
1846
|
+
return ["<requester>", ...lines, "</requester>"];
|
|
1801
1847
|
}
|
|
1802
|
-
function
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1848
|
+
function renderTag(tag, lines) {
|
|
1849
|
+
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
1850
|
+
}
|
|
1851
|
+
function renderTagBlock(tag, content) {
|
|
1852
|
+
return [`<${tag}>`, content, `</${tag}>`].join("\n");
|
|
1853
|
+
}
|
|
1854
|
+
function formatSkillEntry(skill) {
|
|
1855
|
+
const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
|
|
1856
|
+
const lines = [];
|
|
1857
|
+
lines.push(" <skill>");
|
|
1858
|
+
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
1859
|
+
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
1860
|
+
lines.push(` <location>${escapeXml(skillLocation)}</location>`);
|
|
1861
|
+
lines.push(" </skill>");
|
|
1862
|
+
return lines;
|
|
1863
|
+
}
|
|
1864
|
+
function formatAvailableSkillsForPrompt(skills, invocation) {
|
|
1865
|
+
const autoSelectable = skills.filter(
|
|
1866
|
+
(s) => s.disableModelInvocation !== true
|
|
1867
|
+
);
|
|
1868
|
+
const invokedExplicitOnly = invocation ? skills.filter(
|
|
1869
|
+
(s) => s.disableModelInvocation === true && s.name === invocation.skillName
|
|
1870
|
+
) : [];
|
|
1871
|
+
const sections = [];
|
|
1872
|
+
if (autoSelectable.length > 0) {
|
|
1873
|
+
const available = [
|
|
1874
|
+
"<available-skills>",
|
|
1875
|
+
"Scan before answering. Load the most specific matching skill; do not answer from memory when a skill fits. A request that names a skill, plugin, provider, or account matching a skill name is a skill match. If none fits, do not load a skill."
|
|
1876
|
+
];
|
|
1877
|
+
for (const skill of autoSelectable) {
|
|
1878
|
+
available.push(...formatSkillEntry(skill));
|
|
1807
1879
|
}
|
|
1880
|
+
available.push("</available-skills>");
|
|
1881
|
+
sections.push(available.join("\n"));
|
|
1808
1882
|
}
|
|
1809
|
-
|
|
1883
|
+
if (invokedExplicitOnly.length > 0) {
|
|
1884
|
+
const userCallable = [
|
|
1885
|
+
"<user-callable-skills>",
|
|
1886
|
+
"The user's current message explicitly references this skill by name. Load it when relevant to the request."
|
|
1887
|
+
];
|
|
1888
|
+
for (const skill of invokedExplicitOnly) {
|
|
1889
|
+
userCallable.push(...formatSkillEntry(skill));
|
|
1890
|
+
}
|
|
1891
|
+
userCallable.push("</user-callable-skills>");
|
|
1892
|
+
sections.push(userCallable.join("\n"));
|
|
1893
|
+
}
|
|
1894
|
+
return sections.length > 0 ? sections.join("\n") : null;
|
|
1810
1895
|
}
|
|
1811
|
-
function
|
|
1812
|
-
|
|
1896
|
+
function formatActiveMcpCatalogsForPrompt(catalogs) {
|
|
1897
|
+
if (catalogs.length === 0) {
|
|
1898
|
+
return null;
|
|
1899
|
+
}
|
|
1900
|
+
const lines = [
|
|
1901
|
+
"Active MCP provider catalogs are available through `searchMcpTools`. Call it with provider to list descriptors or with query to narrow results, then pass the exact returned `tool_name` to `callMcpTool`. Put provider fields inside `arguments`."
|
|
1902
|
+
];
|
|
1903
|
+
for (const catalog of catalogs) {
|
|
1904
|
+
lines.push(" <catalog>");
|
|
1905
|
+
lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
|
|
1906
|
+
lines.push(
|
|
1907
|
+
` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
|
|
1908
|
+
);
|
|
1909
|
+
lines.push(" </catalog>");
|
|
1910
|
+
}
|
|
1911
|
+
return lines.join("\n");
|
|
1813
1912
|
}
|
|
1814
|
-
function
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1913
|
+
function formatToolGuidanceForPrompt(tools) {
|
|
1914
|
+
const guidedTools = tools.filter(
|
|
1915
|
+
(tool) => Boolean(tool.promptSnippet?.trim()) || (tool.promptGuidelines?.length ?? 0) > 0
|
|
1916
|
+
);
|
|
1917
|
+
if (guidedTools.length === 0) {
|
|
1918
|
+
return null;
|
|
1919
|
+
}
|
|
1920
|
+
const lines = [];
|
|
1921
|
+
for (const tool of guidedTools) {
|
|
1922
|
+
lines.push(` <tool name="${escapeXml(tool.name)}">`);
|
|
1923
|
+
if (tool.promptSnippet?.trim()) {
|
|
1924
|
+
lines.push(` - ${escapeXml(tool.promptSnippet.trim())}`);
|
|
1925
|
+
}
|
|
1926
|
+
if (tool.promptGuidelines && tool.promptGuidelines.length > 0) {
|
|
1927
|
+
for (const guideline of tool.promptGuidelines) {
|
|
1928
|
+
lines.push(` - ${escapeXml(guideline)}`);
|
|
1929
|
+
}
|
|
1818
1930
|
}
|
|
1931
|
+
lines.push(" </tool>");
|
|
1819
1932
|
}
|
|
1820
|
-
return
|
|
1933
|
+
return lines.join("\n");
|
|
1821
1934
|
}
|
|
1822
|
-
function
|
|
1823
|
-
const
|
|
1824
|
-
if (
|
|
1825
|
-
return
|
|
1935
|
+
function formatReferenceFilesLines() {
|
|
1936
|
+
const files = listReferenceFiles();
|
|
1937
|
+
if (files.length === 0) {
|
|
1938
|
+
return null;
|
|
1826
1939
|
}
|
|
1827
|
-
return
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
(entry) => entry.type === "projection_reset"
|
|
1832
|
-
).length;
|
|
1833
|
-
return `${SESSION_ID_PREFIX}${resetCount + 1}`;
|
|
1940
|
+
return files.map((filePath) => {
|
|
1941
|
+
const name = path.basename(filePath);
|
|
1942
|
+
return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
|
|
1943
|
+
});
|
|
1834
1944
|
}
|
|
1835
|
-
function
|
|
1836
|
-
if (
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
}
|
|
1849
|
-
if (
|
|
1850
|
-
|
|
1945
|
+
function formatArtifactsLines(artifactState) {
|
|
1946
|
+
if (!artifactState) return null;
|
|
1947
|
+
const lines = [];
|
|
1948
|
+
if (artifactState.lastCanvasId) {
|
|
1949
|
+
lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
|
|
1950
|
+
}
|
|
1951
|
+
if (artifactState.lastCanvasUrl) {
|
|
1952
|
+
lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
|
|
1953
|
+
}
|
|
1954
|
+
if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
|
|
1955
|
+
lines.push("- recent_canvases:");
|
|
1956
|
+
for (const canvas of artifactState.recentCanvases) {
|
|
1957
|
+
lines.push(` - id: ${escapeXml(canvas.id)}`);
|
|
1958
|
+
if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
|
|
1959
|
+
if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
|
|
1960
|
+
if (canvas.createdAt) {
|
|
1961
|
+
lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
|
|
1851
1962
|
}
|
|
1852
1963
|
}
|
|
1853
|
-
return sessionEntries;
|
|
1854
1964
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1965
|
+
if (artifactState.lastListId) {
|
|
1966
|
+
lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
|
|
1967
|
+
}
|
|
1968
|
+
if (artifactState.lastListUrl) {
|
|
1969
|
+
lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
|
|
1970
|
+
}
|
|
1971
|
+
return lines.length > 0 ? lines : null;
|
|
1972
|
+
}
|
|
1973
|
+
function formatConfigurationLines(configuration) {
|
|
1974
|
+
const keys = Object.keys(configuration ?? {}).sort(
|
|
1975
|
+
(a, b) => a.localeCompare(b)
|
|
1976
|
+
);
|
|
1977
|
+
if (keys.length === 0) return null;
|
|
1978
|
+
return keys.map(
|
|
1979
|
+
(key2) => `- ${escapeXml(key2)}: ${formatConfigurationValue(configuration?.[key2])}`
|
|
1980
|
+
);
|
|
1981
|
+
}
|
|
1982
|
+
var SLACK_HEADER = "You are a Slack-based helper assistant. Follow the personality section for voice and tone in every reply. Platform mechanics and output rules override personality and world context when they conflict.";
|
|
1983
|
+
var LOCAL_HEADER = "You are a helper assistant. Follow the personality section for voice and tone in every reply. Platform mechanics and output rules override personality and world context when they conflict.";
|
|
1984
|
+
var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
|
|
1985
|
+
var TOOL_POLICY_RULES = [
|
|
1986
|
+
"- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
|
|
1987
|
+
"- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
|
|
1988
|
+
"- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
|
|
1989
|
+
"- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
|
|
1990
|
+
"- For repository or implementation questions, inspect the target repository first: local checkout when present, otherwise the configured GitHub/source provider. Do not treat loaded skill files as repo source unless the user asks about the skill. Cite file paths, symbols, PRs/issues, commits, or URLs that support the answer.",
|
|
1991
|
+
`- 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
|
+
"- 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
|
+
"- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
|
|
1994
|
+
"- 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
|
+
];
|
|
1996
|
+
var TOOL_CALL_STYLE_RULES = [
|
|
1997
|
+
"- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
|
|
1998
|
+
"- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
|
|
1999
|
+
"- When a first-class tool exists for an action, use it directly instead of asking the user to run an equivalent command, slash command, or manual lookup.",
|
|
2000
|
+
"- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
|
|
2001
|
+
];
|
|
2002
|
+
var SKILL_POLICY_RULES = [
|
|
2003
|
+
"- Only load skills listed in `<available-skills>`, `<user-callable-skills>`, or named by `<explicit-skill-trigger>`. Never guess or invent a skill name.",
|
|
2004
|
+
"- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
|
|
2005
|
+
];
|
|
2006
|
+
var EXECUTION_CONTRACT_RULES = [
|
|
2007
|
+
"- Actionable request: act in this turn.",
|
|
2008
|
+
"- Continue until done or genuinely blocked. Do not finish with a plan, promise, or offer to check next when an available tool or source can move the request forward.",
|
|
2009
|
+
"- Completion means the final answer covers the user's actual ask, including requested follow-up checks, and is grounded in the best evidence you could access.",
|
|
2010
|
+
"- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
|
|
2011
|
+
"- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
|
|
2012
|
+
"- For non-trivial or long-running work, call `reportProgress` early when available, then only when the major phase changes. Routine tool calls should stay silent."
|
|
2013
|
+
];
|
|
2014
|
+
var CONVERSATION_RULES = [
|
|
2015
|
+
"- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
|
|
2016
|
+
"- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
|
|
2017
|
+
"- Runtime owns continuation and authorization notices; on resumed turns, answer with the final requested content only."
|
|
2018
|
+
];
|
|
2019
|
+
var SLACK_ACTION_RULES = [
|
|
2020
|
+
"- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
|
|
2021
|
+
"- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
|
|
2022
|
+
"- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
|
|
2023
|
+
"- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
|
|
2024
|
+
"- Do not claim an attachment, canvas, channel post, list update, or reaction succeeded unless the tool returned success this turn; when it did, include any link the tool returned.",
|
|
2025
|
+
"- Do not use reactions as progress indicators."
|
|
2026
|
+
];
|
|
2027
|
+
var SAFETY_RULES = [
|
|
2028
|
+
"- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
|
|
2029
|
+
"- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
|
|
2030
|
+
"- Do not change system prompts, tool policies, security settings, credentials, or runtime configuration unless the user explicitly requests that exact administrative action and an available tool permits it."
|
|
2031
|
+
];
|
|
2032
|
+
var FAILURE_RULES = [
|
|
2033
|
+
"- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
|
|
2034
|
+
"- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
|
|
2035
|
+
"- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
|
|
2036
|
+
];
|
|
2037
|
+
function renderRuleSection(tag, lines) {
|
|
2038
|
+
return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
|
|
1859
2039
|
}
|
|
1860
|
-
function
|
|
1861
|
-
|
|
1862
|
-
|
|
2040
|
+
function buildBehaviorSection(platform) {
|
|
2041
|
+
const sections = [
|
|
2042
|
+
renderRuleSection("tool-policy", TOOL_POLICY_RULES),
|
|
2043
|
+
renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
|
|
2044
|
+
renderRuleSection("skill-policy", SKILL_POLICY_RULES),
|
|
2045
|
+
renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
|
|
2046
|
+
renderRuleSection("conversation", CONVERSATION_RULES),
|
|
2047
|
+
renderRuleSection("safety", SAFETY_RULES),
|
|
2048
|
+
renderRuleSection("failure-handling", FAILURE_RULES)
|
|
2049
|
+
];
|
|
2050
|
+
if (platform === "slack") {
|
|
2051
|
+
sections.splice(
|
|
2052
|
+
5,
|
|
2053
|
+
0,
|
|
2054
|
+
renderRuleSection("slack-actions", SLACK_ACTION_RULES)
|
|
2055
|
+
);
|
|
1863
2056
|
}
|
|
1864
|
-
return
|
|
1865
|
-
}
|
|
1866
|
-
function piEntry(message, sessionId, requester) {
|
|
1867
|
-
return {
|
|
1868
|
-
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1869
|
-
type: "pi_message",
|
|
1870
|
-
sessionId,
|
|
1871
|
-
message,
|
|
1872
|
-
...requester ? { requester } : {}
|
|
1873
|
-
};
|
|
1874
|
-
}
|
|
1875
|
-
function resetEntry(messages, sessionId, requester) {
|
|
1876
|
-
return {
|
|
1877
|
-
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1878
|
-
type: "projection_reset",
|
|
1879
|
-
sessionId,
|
|
1880
|
-
messages,
|
|
1881
|
-
...requester ? { requester } : {}
|
|
1882
|
-
};
|
|
2057
|
+
return sections.join("\n\n");
|
|
1883
2058
|
}
|
|
1884
|
-
function
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2059
|
+
function buildOutputSection(platform) {
|
|
2060
|
+
if (platform === "local") {
|
|
2061
|
+
return [
|
|
2062
|
+
`<output format="markdown">`,
|
|
2063
|
+
"- Start with the answer or result, not internal process narration.",
|
|
2064
|
+
"- Use concise Markdown suitable for terminal output: short paragraphs, bullets, links, and fenced code blocks when helpful.",
|
|
2065
|
+
"- End every turn with a final user-facing response.",
|
|
2066
|
+
"</output>"
|
|
2067
|
+
].join("\n");
|
|
2068
|
+
}
|
|
2069
|
+
const openTag = `<output format="slack-markdown" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
|
|
2070
|
+
return [
|
|
2071
|
+
openTag,
|
|
2072
|
+
"- Start with the answer or result, not internal process narration.",
|
|
2073
|
+
"- Use Slack-flavored Markdown: **bold** section labels, `code`, [text](url) links, bullet lists, and fenced code blocks. No hash-prefixed headings and no tables. When the answer primarily lists several URLs, show each URL bare instead of as a labeled link.",
|
|
2074
|
+
"- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
|
|
2075
|
+
"- When a research or document-style answer would benefit from continuation, multiple sections, or future reference value, create a Slack canvas and keep the thread reply to one or two short sentences plus the link; do not recap the canvas contents.",
|
|
2076
|
+
"- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
|
|
2077
|
+
"</output>"
|
|
2078
|
+
].join("\n");
|
|
1891
2079
|
}
|
|
1892
|
-
function
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
type: "mcp_provider_connected",
|
|
1896
|
-
sessionId,
|
|
1897
|
-
provider
|
|
1898
|
-
};
|
|
2080
|
+
function buildIdentitySection(platform) {
|
|
2081
|
+
const name = platform === "slack" ? `Your Slack username is \`${botConfig.userName}\`.` : `Your assistant name is \`${botConfig.userName}\`.`;
|
|
2082
|
+
return ["# Identity", name].join("\n");
|
|
1899
2083
|
}
|
|
1900
|
-
function
|
|
1901
|
-
|
|
1902
|
-
return {
|
|
1903
|
-
role: "user",
|
|
1904
|
-
content: [
|
|
1905
|
-
{
|
|
1906
|
-
type: "text",
|
|
1907
|
-
text: `${label} completed for provider "${entry.provider}". Continue the blocked request and retry the provider operation if needed.`
|
|
1908
|
-
}
|
|
1909
|
-
],
|
|
1910
|
-
timestamp: entry.createdAtMs
|
|
1911
|
-
};
|
|
2084
|
+
function buildPersonalitySection() {
|
|
2085
|
+
return ["# Personality", JUNIOR_PERSONALITY.trim()].join("\n");
|
|
1912
2086
|
}
|
|
1913
|
-
function
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
createdAtMs: args.createdAtMs,
|
|
1919
|
-
kind: args.kind,
|
|
1920
|
-
provider: args.provider,
|
|
1921
|
-
requesterId: args.requesterId,
|
|
1922
|
-
authorizationId: args.authorizationId,
|
|
1923
|
-
delivery: args.delivery
|
|
1924
|
-
};
|
|
2087
|
+
function buildWorldSection() {
|
|
2088
|
+
if (!JUNIOR_WORLD) {
|
|
2089
|
+
return null;
|
|
2090
|
+
}
|
|
2091
|
+
return ["# World", JUNIOR_WORLD.trim()].join("\n");
|
|
1925
2092
|
}
|
|
1926
|
-
function
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
type: "
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
};
|
|
2093
|
+
function buildRuntimeSection(params) {
|
|
2094
|
+
const lines = [
|
|
2095
|
+
params.conversationId ? `- gen_ai.conversation.id: ${escapeXml(params.conversationId)}` : "",
|
|
2096
|
+
params.slackConversation?.type ? `- slack.conversation.type: ${escapeXml(params.slackConversation.type)}` : "",
|
|
2097
|
+
params.slackConversation?.name ? `- slack.conversation.name: ${escapeXml(params.slackConversation.name)}` : ""
|
|
2098
|
+
].filter(Boolean);
|
|
2099
|
+
if (lines.length === 0) {
|
|
2100
|
+
return null;
|
|
2101
|
+
}
|
|
2102
|
+
return renderTagBlock("runtime", lines.join("\n"));
|
|
1937
2103
|
}
|
|
1938
|
-
function
|
|
1939
|
-
|
|
1940
|
-
|
|
2104
|
+
function buildContextSection(params) {
|
|
2105
|
+
const blocks = [];
|
|
2106
|
+
const referenceLines = formatReferenceFilesLines();
|
|
2107
|
+
if (referenceLines) {
|
|
2108
|
+
blocks.push(
|
|
2109
|
+
renderTag("reference-files", [
|
|
2110
|
+
"Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
|
|
2111
|
+
...referenceLines
|
|
2112
|
+
])
|
|
2113
|
+
);
|
|
1941
2114
|
}
|
|
1942
|
-
const
|
|
1943
|
-
|
|
1944
|
-
|
|
2115
|
+
const requesterLines = renderRequesterBlock({
|
|
2116
|
+
full_name: params.requester?.fullName,
|
|
2117
|
+
user_name: params.requester?.userName,
|
|
2118
|
+
user_id: params.requester?.userId
|
|
2119
|
+
});
|
|
2120
|
+
if (requesterLines) {
|
|
2121
|
+
blocks.push(requesterLines);
|
|
1945
2122
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
let messages = [];
|
|
1950
|
-
let requester;
|
|
1951
|
-
for (const entry of projectionEntries(entries, sessionId)) {
|
|
1952
|
-
if (entry.type === "pi_message") {
|
|
1953
|
-
messages.push(entry.message);
|
|
1954
|
-
if (entry.message.role === "user" && entry.requester) {
|
|
1955
|
-
requester = entry.requester;
|
|
1956
|
-
}
|
|
1957
|
-
continue;
|
|
1958
|
-
}
|
|
1959
|
-
if (entry.type === "requester_recorded") {
|
|
1960
|
-
requester = entry.requester;
|
|
1961
|
-
continue;
|
|
1962
|
-
}
|
|
1963
|
-
if (entry.type === "authorization_completed") {
|
|
1964
|
-
messages.push(authorizationObservationMessage(entry));
|
|
1965
|
-
continue;
|
|
1966
|
-
}
|
|
1967
|
-
if (entry.type === "mcp_provider_connected" || entry.type === "authorization_requested") {
|
|
1968
|
-
continue;
|
|
1969
|
-
}
|
|
1970
|
-
messages = [...entry.messages];
|
|
1971
|
-
if (entry.requester) {
|
|
1972
|
-
requester = entry.requester;
|
|
1973
|
-
}
|
|
2123
|
+
const artifactLines = formatArtifactsLines(params.artifactState);
|
|
2124
|
+
if (artifactLines) {
|
|
2125
|
+
blocks.push(renderTag("artifacts", artifactLines));
|
|
1974
2126
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
if (entry.type === "mcp_provider_connected") {
|
|
1984
|
-
providers.add(entry.provider);
|
|
1985
|
-
}
|
|
2127
|
+
const configLines = formatConfigurationLines(params.configuration);
|
|
2128
|
+
if (configLines) {
|
|
2129
|
+
blocks.push(
|
|
2130
|
+
renderTag("configuration", [
|
|
2131
|
+
"Ambient provider defaults; explicit targets win. Run `jr-rpc config get|set|unset|list` as standalone bash commands; do not chain with `cd`, `&&`, pipes, or provider commands.",
|
|
2132
|
+
...configLines
|
|
2133
|
+
])
|
|
2134
|
+
);
|
|
1986
2135
|
}
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
if (newMessages.length === 0 && requester && !isDeepStrictEqual(existingRequester, requester)) {
|
|
1994
|
-
return [requesterRecordedEntry(requester, sessionId)];
|
|
1995
|
-
}
|
|
1996
|
-
const requesterIndex = requester ? findLastIndex(newMessages, (m) => m.role === "user") : -1;
|
|
1997
|
-
return newMessages.map(
|
|
1998
|
-
(message, index) => piEntry(
|
|
1999
|
-
message,
|
|
2000
|
-
sessionId,
|
|
2001
|
-
index === requesterIndex ? requester : void 0
|
|
2002
|
-
)
|
|
2136
|
+
if (params.invocation) {
|
|
2137
|
+
blocks.push(
|
|
2138
|
+
renderTag("explicit-skill-trigger", [
|
|
2139
|
+
"Treat this skill as selected. Load it unless the tool says it is unavailable.",
|
|
2140
|
+
`/${escapeXml(params.invocation.skillName)}`
|
|
2141
|
+
])
|
|
2003
2142
|
);
|
|
2004
2143
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
nextSessionId(entries),
|
|
2009
|
-
requester ?? existingRequester
|
|
2010
|
-
)
|
|
2011
|
-
];
|
|
2012
|
-
}
|
|
2013
|
-
function redisStore(redisStateAdapter) {
|
|
2014
|
-
const client = redisStateAdapter.getClient();
|
|
2015
|
-
return {
|
|
2016
|
-
async append({ entries, scope, ttlMs }) {
|
|
2017
|
-
const listKey = key(scope);
|
|
2018
|
-
if (entries.length > 0) {
|
|
2019
|
-
await client.rPush(
|
|
2020
|
-
listKey,
|
|
2021
|
-
entries.map((entry) => JSON.stringify(entry))
|
|
2022
|
-
);
|
|
2023
|
-
}
|
|
2024
|
-
await client.pExpire(listKey, Math.max(1, ttlMs));
|
|
2025
|
-
},
|
|
2026
|
-
async read(scope) {
|
|
2027
|
-
const values = await client.lRange(key(scope), 0, -1);
|
|
2028
|
-
return values.map(decode);
|
|
2029
|
-
}
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
function stateStore() {
|
|
2033
|
-
const stateAdapter = getStateAdapter();
|
|
2034
|
-
return {
|
|
2035
|
-
async append({ entries, scope, ttlMs }) {
|
|
2036
|
-
const listKey = rawKey(scope);
|
|
2037
|
-
for (const entry of entries) {
|
|
2038
|
-
await stateAdapter.appendToList(listKey, entry, {
|
|
2039
|
-
ttlMs: Math.max(1, ttlMs)
|
|
2040
|
-
});
|
|
2041
|
-
}
|
|
2042
|
-
},
|
|
2043
|
-
async read(scope) {
|
|
2044
|
-
const values = await stateAdapter.getList(rawKey(scope));
|
|
2045
|
-
return values.map(decode);
|
|
2046
|
-
}
|
|
2047
|
-
};
|
|
2048
|
-
}
|
|
2049
|
-
async function defaultStore() {
|
|
2050
|
-
const { redisStateAdapter, stateAdapter } = await getConnectedStateContext();
|
|
2051
|
-
if (redisStateAdapter) {
|
|
2052
|
-
return redisStore(redisStateAdapter);
|
|
2144
|
+
const body = blocks.map((block) => block.join("\n")).join("\n\n");
|
|
2145
|
+
if (!body) {
|
|
2146
|
+
return null;
|
|
2053
2147
|
}
|
|
2054
|
-
|
|
2055
|
-
return stateStore();
|
|
2056
|
-
}
|
|
2057
|
-
async function loadEntries(args) {
|
|
2058
|
-
const store = args.store ?? await defaultStore();
|
|
2059
|
-
return await store.read(args);
|
|
2148
|
+
return renderTagBlock("context", body);
|
|
2060
2149
|
}
|
|
2061
|
-
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
|
|
2150
|
+
function buildCapabilitiesSection(params) {
|
|
2151
|
+
const blocks = [];
|
|
2152
|
+
const availableSkills = formatAvailableSkillsForPrompt(
|
|
2153
|
+
params.availableSkills,
|
|
2154
|
+
params.invocation
|
|
2155
|
+
);
|
|
2156
|
+
if (availableSkills) {
|
|
2157
|
+
blocks.push(availableSkills);
|
|
2065
2158
|
}
|
|
2066
|
-
const
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
const store = args.store ?? await defaultStore();
|
|
2072
|
-
return project(await store.read(args), args.sessionId).messages;
|
|
2073
|
-
}
|
|
2074
|
-
async function loadConnectedMcpProviders(args) {
|
|
2075
|
-
return connectedMcpProviders(await loadEntries(args));
|
|
2076
|
-
}
|
|
2077
|
-
async function recordMcpProviderConnected(args) {
|
|
2078
|
-
const store = args.store ?? await defaultStore();
|
|
2079
|
-
const entries = await store.read(args);
|
|
2080
|
-
const sessionId = currentSessionId(entries);
|
|
2081
|
-
if (connectedMcpProviders(entries).includes(args.provider)) {
|
|
2082
|
-
return;
|
|
2159
|
+
const activeCatalogs = formatActiveMcpCatalogsForPrompt(
|
|
2160
|
+
params.activeMcpCatalogs
|
|
2161
|
+
);
|
|
2162
|
+
if (activeCatalogs) {
|
|
2163
|
+
blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
|
|
2083
2164
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
ttlMs: args.ttlMs
|
|
2088
|
-
});
|
|
2089
|
-
}
|
|
2090
|
-
async function recordAuthorizationRequested(args) {
|
|
2091
|
-
const store = args.store ?? await defaultStore();
|
|
2092
|
-
const entries = await store.read(args);
|
|
2093
|
-
const sessionId = currentSessionId(entries);
|
|
2094
|
-
if (projectionEntries(entries).some(
|
|
2095
|
-
(entry) => entry.type === "authorization_requested" && entry.authorizationId === args.authorizationId
|
|
2096
|
-
)) {
|
|
2097
|
-
return;
|
|
2165
|
+
const toolGuidance = formatToolGuidanceForPrompt(params.toolGuidance ?? []);
|
|
2166
|
+
if (toolGuidance) {
|
|
2167
|
+
blocks.push(renderTagBlock("tool-guidance", toolGuidance));
|
|
2098
2168
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
entries: [
|
|
2102
|
-
authorizationRequestedEntry({
|
|
2103
|
-
createdAtMs: Date.now(),
|
|
2104
|
-
kind: args.kind,
|
|
2105
|
-
sessionId,
|
|
2106
|
-
provider: args.provider,
|
|
2107
|
-
requesterId: args.requesterId,
|
|
2108
|
-
authorizationId: args.authorizationId,
|
|
2109
|
-
delivery: args.delivery
|
|
2110
|
-
})
|
|
2111
|
-
],
|
|
2112
|
-
ttlMs: args.ttlMs
|
|
2113
|
-
});
|
|
2114
|
-
}
|
|
2115
|
-
async function recordAuthorizationCompleted(args) {
|
|
2116
|
-
const store = args.store ?? await defaultStore();
|
|
2117
|
-
const entries = await store.read(args);
|
|
2118
|
-
const sessionId = currentSessionId(entries);
|
|
2119
|
-
if (projectionEntries(entries).some(
|
|
2120
|
-
(entry) => entry.type === "authorization_completed" && entry.authorizationId === args.authorizationId
|
|
2121
|
-
)) {
|
|
2122
|
-
return;
|
|
2169
|
+
if (blocks.length === 0) {
|
|
2170
|
+
return null;
|
|
2123
2171
|
}
|
|
2124
|
-
|
|
2125
|
-
scope: args,
|
|
2126
|
-
entries: [
|
|
2127
|
-
authorizationCompletedEntry({
|
|
2128
|
-
createdAtMs: Date.now(),
|
|
2129
|
-
kind: args.kind,
|
|
2130
|
-
sessionId,
|
|
2131
|
-
provider: args.provider,
|
|
2132
|
-
requesterId: args.requesterId,
|
|
2133
|
-
authorizationId: args.authorizationId
|
|
2134
|
-
})
|
|
2135
|
-
],
|
|
2136
|
-
ttlMs: args.ttlMs
|
|
2137
|
-
});
|
|
2172
|
+
return blocks.join("\n\n");
|
|
2138
2173
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
)
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2174
|
+
function buildStaticSystemPrompt(platform) {
|
|
2175
|
+
return [
|
|
2176
|
+
platform === "slack" ? SLACK_HEADER : LOCAL_HEADER,
|
|
2177
|
+
buildIdentitySection(platform),
|
|
2178
|
+
buildPersonalitySection(),
|
|
2179
|
+
buildWorldSection(),
|
|
2180
|
+
buildBehaviorSection(platform),
|
|
2181
|
+
buildOutputSection(platform)
|
|
2182
|
+
].filter((section) => Boolean(section)).join("\n\n");
|
|
2183
|
+
}
|
|
2184
|
+
var STATIC_SYSTEM_PROMPTS = {
|
|
2185
|
+
local: buildStaticSystemPrompt("local"),
|
|
2186
|
+
slack: buildStaticSystemPrompt("slack")
|
|
2187
|
+
};
|
|
2188
|
+
function buildSystemPrompt(params) {
|
|
2189
|
+
return STATIC_SYSTEM_PROMPTS[params.source.platform];
|
|
2190
|
+
}
|
|
2191
|
+
function buildTurnContextPrompt(params) {
|
|
2192
|
+
const includeSessionContext = params.includeSessionContext ?? true;
|
|
2193
|
+
if (!includeSessionContext) {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
const runtimeSections = [
|
|
2197
|
+
buildCapabilitiesSection({
|
|
2198
|
+
availableSkills: params.availableSkills,
|
|
2199
|
+
activeMcpCatalogs: params.activeMcpCatalogs ?? [],
|
|
2200
|
+
invocation: params.invocation,
|
|
2201
|
+
toolGuidance: params.toolGuidance ?? []
|
|
2202
|
+
}),
|
|
2203
|
+
buildContextSection({
|
|
2204
|
+
requester: params.requester,
|
|
2205
|
+
artifactState: params.artifactState,
|
|
2206
|
+
configuration: params.configuration,
|
|
2207
|
+
invocation: params.invocation
|
|
2208
|
+
}),
|
|
2209
|
+
buildRuntimeSection(params.runtime ?? {})
|
|
2210
|
+
].filter((section) => Boolean(section));
|
|
2211
|
+
if (runtimeSections.length === 0) {
|
|
2212
|
+
return null;
|
|
2213
|
+
}
|
|
2214
|
+
const sections = [
|
|
2215
|
+
`<${TURN_CONTEXT_TAG}>`,
|
|
2216
|
+
TURN_CONTEXT_HEADER,
|
|
2217
|
+
"The current user instruction appears after this block in the same message.",
|
|
2218
|
+
...runtimeSections,
|
|
2219
|
+
`</${TURN_CONTEXT_TAG}>`
|
|
2220
|
+
].filter((section) => Boolean(section));
|
|
2221
|
+
return sections.join("\n\n");
|
|
2160
2222
|
}
|
|
2161
2223
|
|
|
2162
2224
|
// src/chat/state/turn-session.ts
|
|
@@ -2321,24 +2383,36 @@ async function appendAgentTurnSessionSummary(summary, ttlMs) {
|
|
|
2321
2383
|
)
|
|
2322
2384
|
]);
|
|
2323
2385
|
}
|
|
2324
|
-
async function
|
|
2386
|
+
async function recordConversationActivityMetadata(args) {
|
|
2387
|
+
const conversationStore = args.conversationStore ?? getConfiguredConversationStore();
|
|
2388
|
+
const source = args.summary.destination?.platform === "local" ? "local" : args.summary.surface;
|
|
2389
|
+
const shouldRequireExistingStateConversation = !args.conversationStore && args.summary.destination?.platform === "slack" && !hasConfiguredSqlConversationStore();
|
|
2325
2390
|
try {
|
|
2326
|
-
|
|
2391
|
+
if (shouldRequireExistingStateConversation) {
|
|
2392
|
+
const existing = await conversationStore.get({
|
|
2393
|
+
conversationId: args.summary.conversationId
|
|
2394
|
+
});
|
|
2395
|
+
if (!existing) {
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
await conversationStore.recordActivity({
|
|
2327
2400
|
activityAtMs: args.summary.updatedAtMs,
|
|
2328
2401
|
channelName: args.summary.channelName,
|
|
2329
2402
|
conversationId: args.summary.conversationId,
|
|
2330
2403
|
destination: args.summary.destination,
|
|
2331
2404
|
nowMs: args.nowMs,
|
|
2332
2405
|
requester: args.summary.requester,
|
|
2333
|
-
source
|
|
2406
|
+
source
|
|
2334
2407
|
});
|
|
2335
2408
|
} catch (error) {
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
"conversation_activity_record_failed",
|
|
2409
|
+
logWarn(
|
|
2410
|
+
"conversation_activity_metadata_update_failed",
|
|
2339
2411
|
{ conversationId: args.summary.conversationId },
|
|
2340
|
-
{
|
|
2341
|
-
|
|
2412
|
+
{
|
|
2413
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
2414
|
+
},
|
|
2415
|
+
"Failed to update conversation activity metadata"
|
|
2342
2416
|
);
|
|
2343
2417
|
}
|
|
2344
2418
|
}
|
|
@@ -2465,6 +2539,11 @@ async function setStoredRecord(args) {
|
|
|
2465
2539
|
...summary
|
|
2466
2540
|
} = args.record;
|
|
2467
2541
|
await appendAgentTurnSessionSummary(summary, args.ttlMs);
|
|
2542
|
+
await recordConversationActivityMetadata({
|
|
2543
|
+
conversationStore: args.conversationStore,
|
|
2544
|
+
nowMs: Date.now(),
|
|
2545
|
+
summary
|
|
2546
|
+
});
|
|
2468
2547
|
return materializeAgentTurnSessionRecord(args.record, [...args.piMessages]);
|
|
2469
2548
|
}
|
|
2470
2549
|
async function updateAgentTurnSessionState(args) {
|
|
@@ -2516,6 +2595,7 @@ async function upsertAgentTurnSessionRecord(args) {
|
|
|
2516
2595
|
ttlMs
|
|
2517
2596
|
});
|
|
2518
2597
|
return await setStoredRecord({
|
|
2598
|
+
conversationStore: args.conversationStore,
|
|
2519
2599
|
piMessages: args.piMessages,
|
|
2520
2600
|
ttlMs,
|
|
2521
2601
|
record: buildStoredRecord({
|
|
@@ -2576,7 +2656,8 @@ async function recordAgentTurnSessionSummary(args) {
|
|
|
2576
2656
|
...args.traceId ?? existing?.traceId ? { traceId: args.traceId ?? existing?.traceId } : {}
|
|
2577
2657
|
};
|
|
2578
2658
|
await appendAgentTurnSessionSummary(summary, ttlMs);
|
|
2579
|
-
await
|
|
2659
|
+
await recordConversationActivityMetadata({
|
|
2660
|
+
conversationStore: args.conversationStore,
|
|
2580
2661
|
nowMs,
|
|
2581
2662
|
summary
|
|
2582
2663
|
});
|
|
@@ -2638,242 +2719,19 @@ async function failAgentTurnSessionRecord(args) {
|
|
|
2638
2719
|
});
|
|
2639
2720
|
}
|
|
2640
2721
|
|
|
2641
|
-
// src/chat/sentry-links.ts
|
|
2642
|
-
function getSentryOrgSlug() {
|
|
2643
|
-
const slug = process.env.SENTRY_ORG_SLUG?.trim();
|
|
2644
|
-
return slug || void 0;
|
|
2645
|
-
}
|
|
2646
|
-
function isSentrySaasDsnHost(host) {
|
|
2647
|
-
return host === "sentry.io" || host.endsWith(".sentry.io");
|
|
2648
|
-
}
|
|
2649
|
-
function buildSentryWebBaseUrl(dsn) {
|
|
2650
|
-
if (isSentrySaasDsnHost(dsn.host)) {
|
|
2651
|
-
return "https://sentry.io";
|
|
2652
|
-
}
|
|
2653
|
-
const port = dsn.port ? `:${dsn.port}` : "";
|
|
2654
|
-
const path2 = dsn.path ? `/${dsn.path}` : "";
|
|
2655
|
-
return `${dsn.protocol}://${dsn.host}${port}${path2}`;
|
|
2656
|
-
}
|
|
2657
|
-
function buildSentryConversationUrl(conversationId) {
|
|
2658
|
-
const client = sentry_exports.getClient();
|
|
2659
|
-
const dsn = client?.getDsn();
|
|
2660
|
-
if (!dsn?.host || !dsn.projectId) {
|
|
2661
|
-
return void 0;
|
|
2662
|
-
}
|
|
2663
|
-
const orgSlug = getSentryOrgSlug();
|
|
2664
|
-
if (!orgSlug) {
|
|
2665
|
-
return void 0;
|
|
2666
|
-
}
|
|
2667
|
-
const encodedId = encodeURIComponent(conversationId);
|
|
2668
|
-
const params = new URLSearchParams();
|
|
2669
|
-
params.set("project", dsn.projectId);
|
|
2670
|
-
const path2 = `explore/conversations/${encodedId}/?${params.toString()}`;
|
|
2671
|
-
if (isSentrySaasDsnHost(dsn.host)) {
|
|
2672
|
-
return `https://${orgSlug}.sentry.io/${path2}`;
|
|
2673
|
-
}
|
|
2674
|
-
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path2}`;
|
|
2675
|
-
}
|
|
2676
|
-
function buildSentryTraceUrl(traceId) {
|
|
2677
|
-
const client = sentry_exports.getClient();
|
|
2678
|
-
const dsn = client?.getDsn();
|
|
2679
|
-
if (!dsn?.host || !dsn.projectId) {
|
|
2680
|
-
return void 0;
|
|
2681
|
-
}
|
|
2682
|
-
const orgSlug = getSentryOrgSlug();
|
|
2683
|
-
if (!orgSlug) {
|
|
2684
|
-
return void 0;
|
|
2685
|
-
}
|
|
2686
|
-
const encodedTraceId = encodeURIComponent(traceId);
|
|
2687
|
-
const params = new URLSearchParams();
|
|
2688
|
-
params.set("project", dsn.projectId);
|
|
2689
|
-
const path2 = `performance/trace/${encodedTraceId}/?${params.toString()}`;
|
|
2690
|
-
if (isSentrySaasDsnHost(dsn.host)) {
|
|
2691
|
-
return `https://${orgSlug}.sentry.io/${path2}`;
|
|
2692
|
-
}
|
|
2693
|
-
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path2}`;
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
// src/chat/slack/conversation-context.ts
|
|
2697
|
-
function normalizeConversationName(type, channelName) {
|
|
2698
|
-
const trimmed = channelName?.trim();
|
|
2699
|
-
if (!trimmed) return void 0;
|
|
2700
|
-
if (type === "public_channel" || type === "private_channel" || type === "private_channel_or_group_dm") {
|
|
2701
|
-
return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
|
|
2702
|
-
}
|
|
2703
|
-
return trimmed;
|
|
2704
|
-
}
|
|
2705
|
-
function typeFromSlackChannelType(channelType) {
|
|
2706
|
-
if (channelType === "channel") return "public_channel";
|
|
2707
|
-
if (channelType === "group") return "private_channel";
|
|
2708
|
-
if (channelType === "mpim") return "group_dm";
|
|
2709
|
-
if (channelType === "im") return "direct_message";
|
|
2710
|
-
return void 0;
|
|
2711
|
-
}
|
|
2712
|
-
function typeFromChannelId(channelId, channelName) {
|
|
2713
|
-
const normalized = normalizeSlackConversationId(channelId);
|
|
2714
|
-
if (!normalized) return void 0;
|
|
2715
|
-
if (normalized.startsWith("C")) return "public_channel";
|
|
2716
|
-
if (normalized.startsWith("D")) return "direct_message";
|
|
2717
|
-
if (normalized.startsWith("G")) {
|
|
2718
|
-
return channelName?.trim().startsWith("mpdm-") ? "group_dm" : "private_channel_or_group_dm";
|
|
2719
|
-
}
|
|
2720
|
-
return void 0;
|
|
2721
|
-
}
|
|
2722
|
-
function toSlackEventChannelType(channelType) {
|
|
2723
|
-
if (channelType === "channel" || channelType === "group" || channelType === "mpim" || channelType === "im") {
|
|
2724
|
-
return channelType;
|
|
2725
|
-
}
|
|
2726
|
-
return void 0;
|
|
2727
|
-
}
|
|
2728
|
-
function resolveSlackChannelTypeFromMessage(message) {
|
|
2729
|
-
const raw = message.raw;
|
|
2730
|
-
if (!raw || typeof raw !== "object") {
|
|
2731
|
-
return void 0;
|
|
2732
|
-
}
|
|
2733
|
-
const channelType = raw.channel_type;
|
|
2734
|
-
return typeof channelType === "string" ? toSlackEventChannelType(channelType.trim()) : void 0;
|
|
2735
|
-
}
|
|
2736
|
-
function resolveSlackConversationContext(input) {
|
|
2737
|
-
const type = typeFromSlackChannelType(input.channelType) ?? typeFromChannelId(input.channelId, input.channelName);
|
|
2738
|
-
if (!type) return void 0;
|
|
2739
|
-
const name = normalizeConversationName(type, input.channelName);
|
|
2740
|
-
return {
|
|
2741
|
-
type,
|
|
2742
|
-
...name ? { name } : {}
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
function resolveSlackConversationContextFromThreadId(input) {
|
|
2746
|
-
const slackThread = parseSlackThreadId(input.threadId);
|
|
2747
|
-
return resolveSlackConversationContext({
|
|
2748
|
-
channelId: slackThread?.channelId,
|
|
2749
|
-
channelName: input.channelName
|
|
2750
|
-
});
|
|
2751
|
-
}
|
|
2752
|
-
function formatSlackConversationTypeLabel(type) {
|
|
2753
|
-
if (type === "public_channel") return "Public Channel";
|
|
2754
|
-
if (type === "private_channel") return "Private Channel";
|
|
2755
|
-
if (type === "group_dm") return "Group DM";
|
|
2756
|
-
if (type === "direct_message") return "Direct Message";
|
|
2757
|
-
return "Private Channel or Group DM";
|
|
2758
|
-
}
|
|
2759
|
-
function formatSlackConversationRedactedLabel(context) {
|
|
2760
|
-
if (!context) return void 0;
|
|
2761
|
-
return formatSlackConversationTypeLabel(context.type);
|
|
2762
|
-
}
|
|
2763
|
-
|
|
2764
|
-
// src/chat/state/conversation-details.ts
|
|
2765
|
-
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
|
|
2766
|
-
var CONVERSATION_PREFIX = "junior:conversation";
|
|
2767
|
-
var CONVERSATION_DETAILS_TTL_MS = THREAD_STATE_TTL_MS2;
|
|
2768
|
-
function conversationContextKey(conversationId) {
|
|
2769
|
-
return `${CONVERSATION_PREFIX}:${conversationId}:context`;
|
|
2770
|
-
}
|
|
2771
|
-
function conversationTitleKey(conversationId) {
|
|
2772
|
-
return `${CONVERSATION_PREFIX}:${conversationId}:title`;
|
|
2773
|
-
}
|
|
2774
|
-
function parseOriginRequester(value) {
|
|
2775
|
-
return parseStoredSlackRequester(value);
|
|
2776
|
-
}
|
|
2777
|
-
function parseOriginSurface(value) {
|
|
2778
|
-
if (value === "slack" || value === "api" || value === "scheduler" || value === "internal") {
|
|
2779
|
-
return value;
|
|
2780
|
-
}
|
|
2781
|
-
return void 0;
|
|
2782
|
-
}
|
|
2783
|
-
function storedContextFromInput(context) {
|
|
2784
|
-
return {
|
|
2785
|
-
...context.channelName ? { channelName: context.channelName } : {},
|
|
2786
|
-
...context.originSurface ? { originSurface: context.originSurface } : {},
|
|
2787
|
-
...context.originRequester ? { originRequester: context.originRequester } : {},
|
|
2788
|
-
startedAtMs: context.startedAtMs
|
|
2789
|
-
};
|
|
2790
|
-
}
|
|
2791
|
-
function parseContext(value) {
|
|
2792
|
-
if (!isRecord(value)) return void 0;
|
|
2793
|
-
const startedAtMs = toOptionalNumber(value.startedAtMs);
|
|
2794
|
-
if (startedAtMs === void 0) return void 0;
|
|
2795
|
-
return {
|
|
2796
|
-
...typeof value.channelName === "string" && value.channelName.trim() ? { channelName: value.channelName.trim() } : {},
|
|
2797
|
-
...parseOriginSurface(value.originSurface) ? { originSurface: parseOriginSurface(value.originSurface) } : {},
|
|
2798
|
-
...parseOriginRequester(value.originRequester) ? { originRequester: parseOriginRequester(value.originRequester) } : {},
|
|
2799
|
-
startedAtMs
|
|
2800
|
-
};
|
|
2801
|
-
}
|
|
2802
|
-
function parseTitle(value) {
|
|
2803
|
-
if (!isRecord(value)) return void 0;
|
|
2804
|
-
const displayTitle = typeof value.displayTitle === "string" && value.displayTitle.trim() ? value.displayTitle.trim() : void 0;
|
|
2805
|
-
if (!displayTitle) return void 0;
|
|
2806
|
-
return {
|
|
2807
|
-
displayTitle,
|
|
2808
|
-
...typeof value.titleSourceMessageId === "string" ? { titleSourceMessageId: value.titleSourceMessageId } : {}
|
|
2809
|
-
};
|
|
2810
|
-
}
|
|
2811
|
-
async function initConversationContext(conversationId, context) {
|
|
2812
|
-
const stateAdapter = getStateAdapter();
|
|
2813
|
-
await stateAdapter.connect();
|
|
2814
|
-
const key2 = conversationContextKey(conversationId);
|
|
2815
|
-
const inserted = await stateAdapter.setIfNotExists(
|
|
2816
|
-
key2,
|
|
2817
|
-
storedContextFromInput(context),
|
|
2818
|
-
CONVERSATION_DETAILS_TTL_MS
|
|
2819
|
-
);
|
|
2820
|
-
if (inserted) return;
|
|
2821
|
-
const existing = parseContext(await stateAdapter.get(key2));
|
|
2822
|
-
if (!existing) {
|
|
2823
|
-
return;
|
|
2824
|
-
}
|
|
2825
|
-
await stateAdapter.set(key2, existing, CONVERSATION_DETAILS_TTL_MS);
|
|
2826
|
-
}
|
|
2827
|
-
async function setConversationTitle(conversationId, title) {
|
|
2828
|
-
const stateAdapter = getStateAdapter();
|
|
2829
|
-
await stateAdapter.connect();
|
|
2830
|
-
await stateAdapter.set(
|
|
2831
|
-
conversationTitleKey(conversationId),
|
|
2832
|
-
{
|
|
2833
|
-
displayTitle: title.displayTitle,
|
|
2834
|
-
...title.titleSourceMessageId ? { titleSourceMessageId: title.titleSourceMessageId } : {}
|
|
2835
|
-
},
|
|
2836
|
-
CONVERSATION_DETAILS_TTL_MS
|
|
2837
|
-
);
|
|
2838
|
-
}
|
|
2839
|
-
async function getConversationDetails(conversationId) {
|
|
2840
|
-
const stateAdapter = getStateAdapter();
|
|
2841
|
-
await stateAdapter.connect();
|
|
2842
|
-
const [rawContext, rawTitle] = await Promise.all([
|
|
2843
|
-
stateAdapter.get(conversationContextKey(conversationId)),
|
|
2844
|
-
stateAdapter.get(conversationTitleKey(conversationId))
|
|
2845
|
-
]);
|
|
2846
|
-
const context = parseContext(rawContext);
|
|
2847
|
-
const title = parseTitle(rawTitle);
|
|
2848
|
-
if (!context && !title) return void 0;
|
|
2849
|
-
return {
|
|
2850
|
-
conversationId,
|
|
2851
|
-
...title?.displayTitle ? { displayTitle: title.displayTitle } : {},
|
|
2852
|
-
...title?.titleSourceMessageId ? { titleSourceMessageId: title.titleSourceMessageId } : {},
|
|
2853
|
-
...context?.channelName ? { channelName: context.channelName } : {},
|
|
2854
|
-
...context?.originSurface ? { originSurface: context.originSurface } : {},
|
|
2855
|
-
...context?.originRequester ? { originRequester: context.originRequester } : {},
|
|
2856
|
-
...context?.startedAtMs !== void 0 ? { startedAtMs: context.startedAtMs } : {}
|
|
2857
|
-
};
|
|
2858
|
-
}
|
|
2859
|
-
async function getConversationDetailsForIds(conversationIds) {
|
|
2860
|
-
const uniqueIds = [...new Set(conversationIds)].filter(Boolean);
|
|
2861
|
-
const entries = await Promise.all(
|
|
2862
|
-
uniqueIds.map(async (id) => {
|
|
2863
|
-
const details = await getConversationDetails(id);
|
|
2864
|
-
return details ? [id, details] : void 0;
|
|
2865
|
-
})
|
|
2866
|
-
);
|
|
2867
|
-
const result = /* @__PURE__ */ new Map();
|
|
2868
|
-
for (const entry of entries) {
|
|
2869
|
-
if (entry) result.set(entry[0], entry[1]);
|
|
2870
|
-
}
|
|
2871
|
-
return result;
|
|
2872
|
-
}
|
|
2873
|
-
|
|
2874
2722
|
export {
|
|
2723
|
+
getInterruptionMarker,
|
|
2724
|
+
truncateStatusText,
|
|
2725
|
+
normalizeSlackStatusText,
|
|
2726
|
+
splitSlackReplyText,
|
|
2727
|
+
buildSlackOutputMessage,
|
|
2728
|
+
escapeXml,
|
|
2729
|
+
JUNIOR_PERSONALITY,
|
|
2730
|
+
buildSystemPrompt,
|
|
2731
|
+
buildTurnContextPrompt,
|
|
2875
2732
|
createAgentPluginLogger,
|
|
2876
2733
|
createPluginState,
|
|
2734
|
+
getSlackToolContext,
|
|
2877
2735
|
bindSlackDirectCredentialSubject,
|
|
2878
2736
|
verifySlackDirectCredentialSubject,
|
|
2879
2737
|
resolveChannelCapabilities,
|
|
@@ -2885,36 +2743,17 @@ export {
|
|
|
2885
2743
|
getAgentPluginSlackConversationLink,
|
|
2886
2744
|
getAgentPluginOperationalReports,
|
|
2887
2745
|
createAgentPluginHookRunner,
|
|
2888
|
-
GET,
|
|
2889
|
-
getInterruptionMarker,
|
|
2890
|
-
truncateStatusText,
|
|
2891
|
-
normalizeSlackStatusText,
|
|
2892
|
-
splitSlackReplyText,
|
|
2893
|
-
buildSlackOutputMessage,
|
|
2894
|
-
escapeXml,
|
|
2895
|
-
JUNIOR_PERSONALITY,
|
|
2896
|
-
buildSystemPrompt,
|
|
2897
|
-
buildTurnContextPrompt,
|
|
2898
2746
|
loadProjection,
|
|
2899
2747
|
loadConnectedMcpProviders,
|
|
2900
2748
|
recordMcpProviderConnected,
|
|
2901
2749
|
recordAuthorizationRequested,
|
|
2902
2750
|
recordAuthorizationCompleted,
|
|
2903
2751
|
commitMessages,
|
|
2752
|
+
getConfiguredConversationStore,
|
|
2904
2753
|
getAgentTurnSessionRecord,
|
|
2905
2754
|
upsertAgentTurnSessionRecord,
|
|
2906
2755
|
recordAgentTurnSessionSummary,
|
|
2907
2756
|
listAgentTurnSessionSummariesForConversation,
|
|
2908
2757
|
abandonAgentTurnSessionRecord,
|
|
2909
|
-
failAgentTurnSessionRecord
|
|
2910
|
-
buildSentryConversationUrl,
|
|
2911
|
-
buildSentryTraceUrl,
|
|
2912
|
-
resolveSlackChannelTypeFromMessage,
|
|
2913
|
-
resolveSlackConversationContext,
|
|
2914
|
-
resolveSlackConversationContextFromThreadId,
|
|
2915
|
-
formatSlackConversationRedactedLabel,
|
|
2916
|
-
initConversationContext,
|
|
2917
|
-
setConversationTitle,
|
|
2918
|
-
getConversationDetails,
|
|
2919
|
-
getConversationDetailsForIds
|
|
2758
|
+
failAgentTurnSessionRecord
|
|
2920
2759
|
};
|