@sentry/junior 0.71.3 → 0.73.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 +22 -10
- package/dist/api-reference.d.ts +2 -0
- package/dist/app.d.ts +12 -3
- package/dist/app.js +2522 -15560
- package/dist/chat/agent-dispatch/heartbeat.d.ts +0 -6
- 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 +8 -1
- package/dist/chat/app/services.d.ts +7 -0
- package/dist/chat/destination.d.ts +3 -1
- package/dist/chat/logging.d.ts +3 -0
- package/dist/chat/mcp/errors.d.ts +3 -0
- package/dist/chat/mcp/tool-manager.d.ts +5 -1
- package/dist/chat/oauth-flow.d.ts +1 -1
- package/dist/chat/plugins/agent-hooks.d.ts +3 -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 +67 -0
- package/dist/chat/respond.d.ts +8 -8
- package/dist/chat/runtime/agent-continue-runner.d.ts +25 -0
- package/dist/chat/runtime/reply-executor.d.ts +7 -5
- 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 +17 -3
- 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/agent-continue.d.ts +27 -0
- 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 +1 -1
- package/dist/chat/services/message-actor-identity.d.ts +12 -4
- package/dist/chat/services/plugin-auth-orchestration.d.ts +9 -8
- package/dist/chat/services/turn-result.d.ts +3 -0
- package/dist/chat/services/turn-session-record.d.ts +10 -7
- package/dist/chat/slack/user.d.ts +4 -4
- package/dist/chat/state/adapter.d.ts +2 -0
- package/dist/chat/state/conversation-details.d.ts +4 -3
- package/dist/chat/state/session-log.d.ts +43 -0
- package/dist/chat/state/turn-session.d.ts +7 -10
- package/dist/chat/task-execution/slack-work.d.ts +5 -5
- package/dist/chat/task-execution/store.d.ts +83 -48
- package/dist/chat/task-execution/worker.d.ts +3 -3
- package/dist/chat/tools/definition.d.ts +3 -0
- package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -1
- 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 +20 -23
- package/dist/{chunk-BBXYXOJW.js → chunk-3BYAPS6B.js} +48 -529
- package/dist/{chunk-UXG6TU2U.js → chunk-7Q5YOUUT.js} +16 -93
- package/dist/chunk-AL5T52ZD.js +1119 -0
- package/dist/chunk-CYUI7JU5.js +195 -0
- package/dist/{chunk-R62YWUNO.js → chunk-DIMX5F3T.js} +10 -28
- package/dist/chunk-G3E7SCME.js +28 -0
- package/dist/chunk-KVZL5NZS.js +519 -0
- package/dist/chunk-M4FLLXXD.js +212 -0
- package/dist/chunk-OQSYYOLM.js +12787 -0
- package/dist/{chunk-GT67ZWZQ.js → chunk-OR6NQJ5E.js} +5 -3
- package/dist/{chunk-B5HKWWQB.js → chunk-RY6AL5C7.js} +8 -6
- package/dist/chunk-SJHUF3DP.js +43 -0
- package/dist/{chunk-XE2VFQQN.js → chunk-UOTZ3EEQ.js} +1 -1
- package/dist/{chunk-HOGQL2H6.js → chunk-UZVHXZ7V.js} +1357 -1486
- package/dist/{chunk-76YMBKW7.js → chunk-V4VYUY4A.js} +27 -14
- package/dist/{chunk-JS4HURDT.js → chunk-WS2EG3GW.js} +224 -224
- package/dist/chunk-ZDA2HYX5.js +275 -0
- package/dist/cli/chat.js +205 -0
- package/dist/cli/check.js +6 -5
- package/dist/cli/run.js +18 -2
- package/dist/cli/snapshot-warmup.js +11 -8
- package/dist/cli/upgrade.js +599 -0
- 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 -2
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +10 -10
- package/dist/plugins.d.ts +1 -1
- package/dist/reporting/conversations.d.ts +116 -0
- package/dist/reporting.d.ts +24 -129
- package/dist/reporting.js +320 -166
- package/dist/runner-LMAM4OGD.js +259 -0
- package/package.json +3 -3
- package/dist/chat/runtime/timeout-resume-runner.d.ts +0 -19
- package/dist/chat/services/requester-identity.d.ts +0 -19
- package/dist/chat/services/timeout-resume.d.ts +0 -23
- package/dist/chunk-6YY4Q3D4.js +0 -12
- package/dist/chunk-Z3YD6NHK.js +0 -12
- package/dist/handlers/turn-resume.d.ts +0 -4
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
getConnectedStateContext,
|
|
5
|
-
getStateAdapter,
|
|
6
|
-
sandboxSkillDir
|
|
7
|
-
} from "./chunk-R62YWUNO.js";
|
|
8
|
-
import {
|
|
9
|
-
isActorUserId,
|
|
10
|
-
parseActorUserId
|
|
11
|
-
} from "./chunk-UXG6TU2U.js";
|
|
2
|
+
recordConversationActivity
|
|
3
|
+
} from "./chunk-AL5T52ZD.js";
|
|
12
4
|
import {
|
|
13
5
|
isConversationChannel,
|
|
14
6
|
isConversationScopedChannel,
|
|
15
7
|
isDmChannel,
|
|
16
8
|
normalizeSlackConversationId,
|
|
17
9
|
parseDestination
|
|
18
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-V4VYUY4A.js";
|
|
11
|
+
import {
|
|
12
|
+
SANDBOX_DATA_ROOT,
|
|
13
|
+
SANDBOX_WORKSPACE_ROOT,
|
|
14
|
+
sandboxSkillDir
|
|
15
|
+
} from "./chunk-G3E7SCME.js";
|
|
16
|
+
import {
|
|
17
|
+
listReferenceFiles,
|
|
18
|
+
soulPathCandidates,
|
|
19
|
+
worldPathCandidates
|
|
20
|
+
} from "./chunk-KVZL5NZS.js";
|
|
21
|
+
import {
|
|
22
|
+
getConnectedStateContext,
|
|
23
|
+
getStateAdapter
|
|
24
|
+
} from "./chunk-DIMX5F3T.js";
|
|
19
25
|
import {
|
|
20
26
|
TURN_CONTEXT_TAG,
|
|
21
27
|
botConfig,
|
|
22
|
-
getChatConfig
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
getChatConfig
|
|
29
|
+
} from "./chunk-WS2EG3GW.js";
|
|
30
|
+
import {
|
|
31
|
+
isActorUserId,
|
|
32
|
+
parseActorUserId,
|
|
33
|
+
parseStoredSlackRequester,
|
|
34
|
+
storedSlackRequesterSchema
|
|
35
|
+
} from "./chunk-CYUI7JU5.js";
|
|
25
36
|
import {
|
|
26
37
|
isRecord,
|
|
27
|
-
listReferenceFiles,
|
|
28
38
|
logException,
|
|
29
39
|
logInfo,
|
|
30
|
-
logWarn
|
|
31
|
-
|
|
32
|
-
toOptionalNumber,
|
|
33
|
-
worldPathCandidates
|
|
34
|
-
} from "./chunk-BBXYXOJW.js";
|
|
35
|
-
import {
|
|
36
|
-
sentry_exports
|
|
37
|
-
} from "./chunk-Z3YD6NHK.js";
|
|
40
|
+
logWarn
|
|
41
|
+
} from "./chunk-3BYAPS6B.js";
|
|
38
42
|
|
|
39
43
|
// src/chat/plugins/logging.ts
|
|
40
44
|
function createAgentPluginLogger(plugin) {
|
|
@@ -160,6 +164,23 @@ function createPluginState(plugin, options) {
|
|
|
160
164
|
};
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
// src/chat/tools/slack/context.ts
|
|
168
|
+
function getSlackToolContext(context) {
|
|
169
|
+
if (context.source.platform !== "slack") {
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
destination: context.destination?.platform === "slack" ? context.destination : void 0,
|
|
174
|
+
source: context.source,
|
|
175
|
+
requester: context.requester?.platform === "slack" ? context.requester : void 0,
|
|
176
|
+
destinationChannelId: context.destination?.platform === "slack" ? context.destination.channelId : void 0,
|
|
177
|
+
messageTs: context.source.messageTs,
|
|
178
|
+
sourceChannelId: context.source.channelId,
|
|
179
|
+
teamId: context.source.teamId,
|
|
180
|
+
threadTs: context.source.threadTs
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
163
184
|
// src/chat/credentials/subject.ts
|
|
164
185
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
165
186
|
var CREDENTIAL_SUBJECT_HMAC_CONTEXT = "junior.credential_subject.v1";
|
|
@@ -351,29 +372,43 @@ function getAgentPluginTools(context) {
|
|
|
351
372
|
}
|
|
352
373
|
const log = createAgentPluginLogger(plugin.name);
|
|
353
374
|
const destination = context.destination;
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const
|
|
375
|
+
const slackToolContext = getSlackToolContext(context);
|
|
376
|
+
const credentialSubject = slackToolContext ? createSlackDirectCredentialSubject({
|
|
377
|
+
channelId: slackToolContext.sourceChannelId,
|
|
378
|
+
teamId: slackToolContext.teamId,
|
|
379
|
+
userId: slackToolContext.requester?.userId
|
|
380
|
+
}) : void 0;
|
|
381
|
+
const slackContext = slackToolContext ? {
|
|
382
|
+
channelCapabilities: resolveChannelCapabilities(
|
|
383
|
+
slackToolContext.sourceChannelId
|
|
384
|
+
),
|
|
385
|
+
...credentialSubject ? { credentialSubject } : {}
|
|
386
|
+
} : void 0;
|
|
387
|
+
const pluginContext = context.source.platform === "slack" ? {
|
|
361
388
|
plugin: { name: plugin.name },
|
|
362
389
|
log,
|
|
363
|
-
requester: context.requester,
|
|
364
|
-
channelCapabilities: pluginCapabilities,
|
|
365
|
-
channelId: context.channelId,
|
|
390
|
+
requester: context.requester?.platform === "slack" ? context.requester : void 0,
|
|
366
391
|
conversationId: context.conversationId,
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
messageTs: context.messageTs,
|
|
371
|
-
threadTs: context.threadTs,
|
|
392
|
+
destination: destination?.platform === "slack" ? destination : void 0,
|
|
393
|
+
slack: slackContext,
|
|
394
|
+
source: context.source,
|
|
372
395
|
userText: context.userText,
|
|
373
396
|
state: createPluginState(plugin.name, {
|
|
374
397
|
legacyStatePrefixes: plugin.legacyStatePrefixes
|
|
375
398
|
})
|
|
376
|
-
}
|
|
399
|
+
} : {
|
|
400
|
+
plugin: { name: plugin.name },
|
|
401
|
+
log,
|
|
402
|
+
requester: context.requester?.platform === "local" ? context.requester : void 0,
|
|
403
|
+
conversationId: context.conversationId,
|
|
404
|
+
destination: destination?.platform === "local" ? destination : void 0,
|
|
405
|
+
source: context.source,
|
|
406
|
+
userText: context.userText,
|
|
407
|
+
state: createPluginState(plugin.name, {
|
|
408
|
+
legacyStatePrefixes: plugin.legacyStatePrefixes
|
|
409
|
+
})
|
|
410
|
+
};
|
|
411
|
+
const pluginTools = hook(pluginContext);
|
|
377
412
|
for (const [name, tool] of Object.entries(pluginTools)) {
|
|
378
413
|
if (!AGENT_PLUGIN_TOOL_NAME_RE.test(name)) {
|
|
379
414
|
throw new Error(
|
|
@@ -809,1293 +844,1361 @@ function createAgentPluginHookRunner(input = {}) {
|
|
|
809
844
|
};
|
|
810
845
|
}
|
|
811
846
|
|
|
812
|
-
// src/
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
847
|
+
// src/chat/state/session-log.ts
|
|
848
|
+
import { isDeepStrictEqual } from "util";
|
|
849
|
+
import { z } from "zod";
|
|
850
|
+
var AGENT_SESSION_LOG_PREFIX = "junior:agent-session-log";
|
|
851
|
+
var AGENT_SESSION_LOG_SCHEMA_VERSION = 1;
|
|
852
|
+
var INITIAL_SESSION_ID = "session_0";
|
|
853
|
+
var SESSION_ID_PREFIX = "session_";
|
|
854
|
+
var piMessageSchema = z.object({
|
|
855
|
+
role: z.string()
|
|
856
|
+
}).passthrough().transform((value) => value);
|
|
857
|
+
var piMessageEntrySchema = z.object({
|
|
858
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
859
|
+
type: z.literal("pi_message"),
|
|
860
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
861
|
+
message: piMessageSchema,
|
|
862
|
+
requester: storedSlackRequesterSchema.optional()
|
|
863
|
+
});
|
|
864
|
+
var projectionResetEntrySchema = z.object({
|
|
865
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
866
|
+
type: z.literal("projection_reset"),
|
|
867
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
868
|
+
messages: z.array(piMessageSchema),
|
|
869
|
+
requester: storedSlackRequesterSchema.optional()
|
|
870
|
+
});
|
|
871
|
+
var requesterRecordedEntrySchema = z.object({
|
|
872
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
873
|
+
type: z.literal("requester_recorded"),
|
|
874
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
875
|
+
requester: storedSlackRequesterSchema
|
|
876
|
+
});
|
|
877
|
+
var mcpProviderConnectedEntrySchema = z.object({
|
|
878
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
879
|
+
type: z.literal("mcp_provider_connected"),
|
|
880
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
881
|
+
provider: z.string().min(1)
|
|
882
|
+
});
|
|
883
|
+
var authorizationKindSchema = z.union([
|
|
884
|
+
z.literal("plugin"),
|
|
885
|
+
z.literal("mcp")
|
|
886
|
+
]);
|
|
887
|
+
var authorizationRequestedEntrySchema = z.object({
|
|
888
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
889
|
+
type: z.literal("authorization_requested"),
|
|
890
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
891
|
+
createdAtMs: z.number().int().nonnegative(),
|
|
892
|
+
kind: authorizationKindSchema,
|
|
893
|
+
provider: z.string().min(1),
|
|
894
|
+
requesterId: z.string().min(1),
|
|
895
|
+
authorizationId: z.string().min(1),
|
|
896
|
+
delivery: z.union([
|
|
897
|
+
z.literal("private_link_sent"),
|
|
898
|
+
z.literal("private_link_reused")
|
|
899
|
+
])
|
|
900
|
+
});
|
|
901
|
+
var authorizationCompletedEntrySchema = z.object({
|
|
902
|
+
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
903
|
+
type: z.literal("authorization_completed"),
|
|
904
|
+
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
905
|
+
createdAtMs: z.number().int().nonnegative(),
|
|
906
|
+
kind: authorizationKindSchema,
|
|
907
|
+
provider: z.string().min(1),
|
|
908
|
+
requesterId: z.string().min(1),
|
|
909
|
+
authorizationId: z.string().min(1)
|
|
910
|
+
});
|
|
911
|
+
var sessionLogEntrySchema = z.discriminatedUnion("type", [
|
|
912
|
+
piMessageEntrySchema,
|
|
913
|
+
projectionResetEntrySchema,
|
|
914
|
+
requesterRecordedEntrySchema,
|
|
915
|
+
mcpProviderConnectedEntrySchema,
|
|
916
|
+
authorizationRequestedEntrySchema,
|
|
917
|
+
authorizationCompletedEntrySchema
|
|
918
|
+
]);
|
|
919
|
+
function key(scope) {
|
|
920
|
+
const prefix = getChatConfig().state.keyPrefix;
|
|
921
|
+
return [
|
|
922
|
+
...prefix ? [prefix] : [],
|
|
923
|
+
AGENT_SESSION_LOG_PREFIX,
|
|
924
|
+
scope.conversationId
|
|
925
|
+
].join(":");
|
|
819
926
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
import fs from "fs";
|
|
823
|
-
import path from "path";
|
|
824
|
-
|
|
825
|
-
// src/chat/interruption-marker.ts
|
|
826
|
-
var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
|
|
827
|
-
function getInterruptionMarker() {
|
|
828
|
-
return INTERRUPTED_MARKER;
|
|
927
|
+
function rawKey(scope) {
|
|
928
|
+
return [AGENT_SESSION_LOG_PREFIX, scope.conversationId].join(":");
|
|
829
929
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
833
|
-
function truncateStatusText(text) {
|
|
834
|
-
const trimmed = text.trim();
|
|
835
|
-
if (!trimmed) {
|
|
836
|
-
return "";
|
|
837
|
-
}
|
|
838
|
-
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
839
|
-
return trimmed;
|
|
840
|
-
}
|
|
841
|
-
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
930
|
+
function normalizeMessageCount(value) {
|
|
931
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
842
932
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
let n = 1;
|
|
850
|
-
while (line[start + n] === "`") {
|
|
851
|
-
n++;
|
|
852
|
-
}
|
|
853
|
-
const marker = "`".repeat(n);
|
|
854
|
-
let search = start + n;
|
|
855
|
-
while (search < line.length) {
|
|
856
|
-
const close = line.indexOf(marker, search);
|
|
857
|
-
if (close === -1) {
|
|
858
|
-
return void 0;
|
|
859
|
-
}
|
|
860
|
-
const after = close + n;
|
|
861
|
-
if (line[after] !== "`") {
|
|
862
|
-
return { text: line.slice(start, after), end: after };
|
|
933
|
+
function countMatchingPrefix(left, right) {
|
|
934
|
+
const limit = Math.min(left.length, right.length);
|
|
935
|
+
for (let index = 0; index < limit; index += 1) {
|
|
936
|
+
if (!isDeepStrictEqual(left[index], right[index])) {
|
|
937
|
+
return index;
|
|
863
938
|
}
|
|
864
|
-
search = after + 1;
|
|
865
939
|
}
|
|
866
|
-
return
|
|
940
|
+
return limit;
|
|
867
941
|
}
|
|
868
|
-
function
|
|
869
|
-
|
|
870
|
-
return void 0;
|
|
871
|
-
}
|
|
872
|
-
const close = line.indexOf(">", start + 1);
|
|
873
|
-
if (close === -1) {
|
|
874
|
-
return void 0;
|
|
875
|
-
}
|
|
876
|
-
const body = line.slice(start + 1, close);
|
|
877
|
-
if (/^(?:https?:\/\/|@|#|!)/.test(body)) {
|
|
878
|
-
return { text: line.slice(start, close + 1), end: close + 1 };
|
|
879
|
-
}
|
|
880
|
-
return void 0;
|
|
942
|
+
function entrySessionId(entry) {
|
|
943
|
+
return entry.sessionId ?? INITIAL_SESSION_ID;
|
|
881
944
|
}
|
|
882
|
-
function
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
if (labelEnd === -1) {
|
|
888
|
-
return void 0;
|
|
889
|
-
}
|
|
890
|
-
const destStart = labelEnd + 2;
|
|
891
|
-
if (!line.startsWith("http://", destStart) && !line.startsWith("https://", destStart)) {
|
|
892
|
-
return void 0;
|
|
893
|
-
}
|
|
894
|
-
const closeParens = line.indexOf(")", destStart);
|
|
895
|
-
if (closeParens === -1) {
|
|
896
|
-
return void 0;
|
|
945
|
+
function latestProjectionResetIndex(entries) {
|
|
946
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
947
|
+
if (entries[index]?.type === "projection_reset") {
|
|
948
|
+
return index;
|
|
949
|
+
}
|
|
897
950
|
}
|
|
898
|
-
return
|
|
951
|
+
return -1;
|
|
899
952
|
}
|
|
900
|
-
function
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
else if (ch === ")") balance--;
|
|
953
|
+
function currentSessionId(entries) {
|
|
954
|
+
const resetIndex = latestProjectionResetIndex(entries);
|
|
955
|
+
if (resetIndex < 0) {
|
|
956
|
+
return INITIAL_SESSION_ID;
|
|
905
957
|
}
|
|
906
|
-
return
|
|
958
|
+
return entrySessionId(entries[resetIndex]);
|
|
907
959
|
}
|
|
908
|
-
function
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
960
|
+
function nextSessionId(entries) {
|
|
961
|
+
const resetCount = entries.filter(
|
|
962
|
+
(entry) => entry.type === "projection_reset"
|
|
963
|
+
).length;
|
|
964
|
+
return `${SESSION_ID_PREFIX}${resetCount + 1}`;
|
|
965
|
+
}
|
|
966
|
+
function projectionEntries(entries, sessionId) {
|
|
967
|
+
if (sessionId) {
|
|
968
|
+
const sessionEntries = [];
|
|
969
|
+
let started = false;
|
|
970
|
+
for (const entry of entries) {
|
|
971
|
+
const entryId = entrySessionId(entry);
|
|
972
|
+
if (!started) {
|
|
973
|
+
if (entryId !== sessionId) {
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
started = true;
|
|
977
|
+
} else if (entry.type === "projection_reset" && entryId !== sessionId) {
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
980
|
+
if (entryId === sessionId) {
|
|
981
|
+
sessionEntries.push(entry);
|
|
982
|
+
}
|
|
914
983
|
}
|
|
915
|
-
|
|
984
|
+
return sessionEntries;
|
|
916
985
|
}
|
|
917
|
-
|
|
918
|
-
|
|
986
|
+
const resetIndex = latestProjectionResetIndex(entries);
|
|
987
|
+
const startIndex = resetIndex < 0 ? 0 : resetIndex;
|
|
988
|
+
const currentId = resetIndex < 0 ? INITIAL_SESSION_ID : entrySessionId(entries[resetIndex]);
|
|
989
|
+
return entries.slice(startIndex).filter((entry) => entrySessionId(entry) === currentId);
|
|
990
|
+
}
|
|
991
|
+
function findLastIndex(values, predicate) {
|
|
992
|
+
for (let index = values.length - 1; index >= 0; index -= 1) {
|
|
993
|
+
if (predicate(values[index])) return index;
|
|
919
994
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
995
|
+
return -1;
|
|
996
|
+
}
|
|
997
|
+
function piEntry(message, sessionId, requester) {
|
|
998
|
+
return {
|
|
999
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1000
|
+
type: "pi_message",
|
|
1001
|
+
sessionId,
|
|
1002
|
+
message,
|
|
1003
|
+
...requester ? { requester } : {}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function resetEntry(messages, sessionId, requester) {
|
|
1007
|
+
return {
|
|
1008
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1009
|
+
type: "projection_reset",
|
|
1010
|
+
sessionId,
|
|
1011
|
+
messages,
|
|
1012
|
+
...requester ? { requester } : {}
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function requesterRecordedEntry(requester, sessionId) {
|
|
1016
|
+
return {
|
|
1017
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1018
|
+
type: "requester_recorded",
|
|
1019
|
+
sessionId,
|
|
1020
|
+
requester
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
function mcpProviderConnectedEntry(provider, sessionId) {
|
|
1024
|
+
return {
|
|
1025
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1026
|
+
type: "mcp_provider_connected",
|
|
1027
|
+
sessionId,
|
|
1028
|
+
provider
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function authorizationObservationMessage(entry) {
|
|
1032
|
+
const label = entry.kind === "mcp" ? "MCP authorization" : "Authorization";
|
|
1033
|
+
return {
|
|
1034
|
+
role: "user",
|
|
1035
|
+
content: [
|
|
1036
|
+
{
|
|
1037
|
+
type: "text",
|
|
1038
|
+
text: `${label} completed for provider "${entry.provider}". Continue the blocked request and retry the provider operation if needed.`
|
|
1039
|
+
}
|
|
1040
|
+
],
|
|
1041
|
+
timestamp: entry.createdAtMs
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
function authorizationRequestedEntry(args) {
|
|
1045
|
+
return {
|
|
1046
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1047
|
+
type: "authorization_requested",
|
|
1048
|
+
sessionId: args.sessionId,
|
|
1049
|
+
createdAtMs: args.createdAtMs,
|
|
1050
|
+
kind: args.kind,
|
|
1051
|
+
provider: args.provider,
|
|
1052
|
+
requesterId: args.requesterId,
|
|
1053
|
+
authorizationId: args.authorizationId,
|
|
1054
|
+
delivery: args.delivery
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function authorizationCompletedEntry(args) {
|
|
1058
|
+
return {
|
|
1059
|
+
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1060
|
+
type: "authorization_completed",
|
|
1061
|
+
sessionId: args.sessionId,
|
|
1062
|
+
createdAtMs: args.createdAtMs,
|
|
1063
|
+
kind: args.kind,
|
|
1064
|
+
provider: args.provider,
|
|
1065
|
+
requesterId: args.requesterId,
|
|
1066
|
+
authorizationId: args.authorizationId
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
function decode(value) {
|
|
1070
|
+
if (typeof value === "string") {
|
|
1071
|
+
return decode(JSON.parse(value));
|
|
929
1072
|
}
|
|
930
|
-
|
|
931
|
-
|
|
1073
|
+
const parsed = sessionLogEntrySchema.safeParse(value);
|
|
1074
|
+
if (parsed.success) {
|
|
1075
|
+
return parsed.data;
|
|
932
1076
|
}
|
|
933
|
-
return
|
|
1077
|
+
return piEntry(piMessageSchema.parse(value), INITIAL_SESSION_ID);
|
|
934
1078
|
}
|
|
935
|
-
function
|
|
936
|
-
let
|
|
937
|
-
let
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1079
|
+
function project(entries, sessionId) {
|
|
1080
|
+
let messages = [];
|
|
1081
|
+
let requester;
|
|
1082
|
+
for (const entry of projectionEntries(entries, sessionId)) {
|
|
1083
|
+
if (entry.type === "pi_message") {
|
|
1084
|
+
messages.push(entry.message);
|
|
1085
|
+
if (entry.message.role === "user" && entry.requester) {
|
|
1086
|
+
requester = entry.requester;
|
|
1087
|
+
}
|
|
943
1088
|
continue;
|
|
944
1089
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
result += angleToken.text;
|
|
948
|
-
i = angleToken.end;
|
|
1090
|
+
if (entry.type === "requester_recorded") {
|
|
1091
|
+
requester = entry.requester;
|
|
949
1092
|
continue;
|
|
950
1093
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
result += mdLink.text;
|
|
954
|
-
i = mdLink.end;
|
|
1094
|
+
if (entry.type === "authorization_completed") {
|
|
1095
|
+
messages.push(authorizationObservationMessage(entry));
|
|
955
1096
|
continue;
|
|
956
1097
|
}
|
|
957
|
-
if (
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
}
|
|
1098
|
+
if (entry.type === "mcp_provider_connected" || entry.type === "authorization_requested") {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
messages = [...entry.messages];
|
|
1102
|
+
if (entry.requester) {
|
|
1103
|
+
requester = entry.requester;
|
|
964
1104
|
}
|
|
965
|
-
result += line[i];
|
|
966
|
-
i++;
|
|
967
1105
|
}
|
|
968
|
-
return
|
|
1106
|
+
return { messages, requester };
|
|
969
1107
|
}
|
|
970
|
-
function
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
continue;
|
|
1108
|
+
function projectMessages(entries, sessionId) {
|
|
1109
|
+
return project(entries, sessionId).messages;
|
|
1110
|
+
}
|
|
1111
|
+
function connectedMcpProviders(entries, sessionId) {
|
|
1112
|
+
const providers = /* @__PURE__ */ new Set();
|
|
1113
|
+
for (const entry of projectionEntries(entries, sessionId)) {
|
|
1114
|
+
if (entry.type === "mcp_provider_connected") {
|
|
1115
|
+
providers.add(entry.provider);
|
|
979
1116
|
}
|
|
980
|
-
out.push(inCodeBlock ? line : wrapBareUrlsOnLine(line));
|
|
981
1117
|
}
|
|
982
|
-
return
|
|
1118
|
+
return [...providers].sort((left, right) => left.localeCompare(right));
|
|
983
1119
|
}
|
|
984
|
-
function
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1120
|
+
function commitEntries(existingMessages, nextMessages, sessionId, entries, existingRequester, requester) {
|
|
1121
|
+
const matchingPrefix = countMatchingPrefix(existingMessages, nextMessages);
|
|
1122
|
+
if (matchingPrefix === existingMessages.length) {
|
|
1123
|
+
const newMessages = nextMessages.slice(matchingPrefix);
|
|
1124
|
+
if (newMessages.length === 0 && requester && !isDeepStrictEqual(existingRequester, requester)) {
|
|
1125
|
+
return [requesterRecordedEntry(requester, sessionId)];
|
|
1126
|
+
}
|
|
1127
|
+
const requesterIndex = requester ? findLastIndex(newMessages, (m) => m.role === "user") : -1;
|
|
1128
|
+
return newMessages.map(
|
|
1129
|
+
(message, index) => piEntry(
|
|
1130
|
+
message,
|
|
1131
|
+
sessionId,
|
|
1132
|
+
index === requesterIndex ? requester : void 0
|
|
1133
|
+
)
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
return [
|
|
1137
|
+
resetEntry(
|
|
1138
|
+
nextMessages,
|
|
1139
|
+
nextSessionId(entries),
|
|
1140
|
+
requester ?? existingRequester
|
|
1141
|
+
)
|
|
1142
|
+
];
|
|
1143
|
+
}
|
|
1144
|
+
function redisStore(redisStateAdapter) {
|
|
1145
|
+
const client = redisStateAdapter.getClient();
|
|
1146
|
+
return {
|
|
1147
|
+
async append({ entries, scope, ttlMs }) {
|
|
1148
|
+
const listKey = key(scope);
|
|
1149
|
+
if (entries.length > 0) {
|
|
1150
|
+
await client.rPush(
|
|
1151
|
+
listKey,
|
|
1152
|
+
entries.map((entry) => JSON.stringify(entry))
|
|
1153
|
+
);
|
|
999
1154
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
result.push(line);
|
|
1006
|
-
continue;
|
|
1155
|
+
await client.pExpire(listKey, Math.max(1, ttlMs));
|
|
1156
|
+
},
|
|
1157
|
+
async read(scope) {
|
|
1158
|
+
const values = await client.lRange(key(scope), 0, -1);
|
|
1159
|
+
return values.map(decode);
|
|
1007
1160
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
function stateStore() {
|
|
1164
|
+
const stateAdapter = getStateAdapter();
|
|
1165
|
+
return {
|
|
1166
|
+
async append({ entries, scope, ttlMs }) {
|
|
1167
|
+
const listKey = rawKey(scope);
|
|
1168
|
+
for (const entry of entries) {
|
|
1169
|
+
await stateAdapter.appendToList(listKey, entry, {
|
|
1170
|
+
ttlMs: Math.max(1, ttlMs)
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
async read(scope) {
|
|
1175
|
+
const values = await stateAdapter.getList(rawKey(scope));
|
|
1176
|
+
return values.map(decode);
|
|
1011
1177
|
}
|
|
1012
|
-
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
async function defaultStore() {
|
|
1181
|
+
const { redisStateAdapter, stateAdapter } = await getConnectedStateContext();
|
|
1182
|
+
if (redisStateAdapter) {
|
|
1183
|
+
return redisStore(redisStateAdapter);
|
|
1013
1184
|
}
|
|
1014
|
-
|
|
1185
|
+
await stateAdapter.connect();
|
|
1186
|
+
return stateStore();
|
|
1015
1187
|
}
|
|
1016
|
-
function
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
normalized = ensureBlockSpacing(normalized);
|
|
1020
|
-
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
1188
|
+
async function loadEntries(args) {
|
|
1189
|
+
const store = args.store ?? await defaultStore();
|
|
1190
|
+
return await store.read(args);
|
|
1021
1191
|
}
|
|
1022
|
-
function
|
|
1023
|
-
const
|
|
1024
|
-
if (
|
|
1025
|
-
return
|
|
1192
|
+
async function loadMessages(args) {
|
|
1193
|
+
const messageCount = normalizeMessageCount(args.messageCount);
|
|
1194
|
+
if (messageCount === 0) {
|
|
1195
|
+
return [];
|
|
1026
1196
|
}
|
|
1027
|
-
|
|
1197
|
+
const store = args.store ?? await defaultStore();
|
|
1198
|
+
const messages = projectMessages(await store.read(args), args.sessionId);
|
|
1199
|
+
return messages.length >= messageCount ? messages.slice(0, messageCount) : void 0;
|
|
1028
1200
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
var MAX_INLINE_LINES = 45;
|
|
1033
|
-
var CONTINUED_MARKER = "\n\n[Continued below]";
|
|
1034
|
-
function countSlackLines(text) {
|
|
1035
|
-
if (!text) {
|
|
1036
|
-
return 0;
|
|
1037
|
-
}
|
|
1038
|
-
return text.split("\n").length;
|
|
1201
|
+
async function loadProjection(args) {
|
|
1202
|
+
const store = args.store ?? await defaultStore();
|
|
1203
|
+
return project(await store.read(args), args.sessionId).messages;
|
|
1039
1204
|
}
|
|
1040
|
-
function
|
|
1041
|
-
return
|
|
1205
|
+
async function loadConnectedMcpProviders(args) {
|
|
1206
|
+
return connectedMcpProviders(await loadEntries(args));
|
|
1042
1207
|
}
|
|
1043
|
-
function
|
|
1044
|
-
|
|
1045
|
-
|
|
1208
|
+
async function recordMcpProviderConnected(args) {
|
|
1209
|
+
const store = args.store ?? await defaultStore();
|
|
1210
|
+
const entries = await store.read(args);
|
|
1211
|
+
const sessionId = currentSessionId(entries);
|
|
1212
|
+
if (connectedMcpProviders(entries).includes(args.provider)) {
|
|
1213
|
+
return;
|
|
1046
1214
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
}
|
|
1052
|
-
const spaceIndex = bounded.lastIndexOf(" ");
|
|
1053
|
-
if (spaceIndex > 0) {
|
|
1054
|
-
return spaceIndex;
|
|
1055
|
-
}
|
|
1056
|
-
return maxChars;
|
|
1215
|
+
await store.append({
|
|
1216
|
+
scope: args,
|
|
1217
|
+
entries: [mcpProviderConnectedEntry(args.provider, sessionId)],
|
|
1218
|
+
ttlMs: args.ttlMs
|
|
1219
|
+
});
|
|
1057
1220
|
}
|
|
1058
|
-
function
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1221
|
+
async function recordAuthorizationRequested(args) {
|
|
1222
|
+
const store = args.store ?? await defaultStore();
|
|
1223
|
+
const entries = await store.read(args);
|
|
1224
|
+
const sessionId = currentSessionId(entries);
|
|
1225
|
+
if (projectionEntries(entries).some(
|
|
1226
|
+
(entry) => entry.type === "authorization_requested" && entry.authorizationId === args.authorizationId
|
|
1227
|
+
)) {
|
|
1228
|
+
return;
|
|
1065
1229
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1230
|
+
await store.append({
|
|
1231
|
+
scope: args,
|
|
1232
|
+
entries: [
|
|
1233
|
+
authorizationRequestedEntry({
|
|
1234
|
+
createdAtMs: Date.now(),
|
|
1235
|
+
kind: args.kind,
|
|
1236
|
+
sessionId,
|
|
1237
|
+
provider: args.provider,
|
|
1238
|
+
requesterId: args.requesterId,
|
|
1239
|
+
authorizationId: args.authorizationId,
|
|
1240
|
+
delivery: args.delivery
|
|
1241
|
+
})
|
|
1242
|
+
],
|
|
1243
|
+
ttlMs: args.ttlMs
|
|
1244
|
+
});
|
|
1080
1245
|
}
|
|
1081
|
-
function
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
if (!open) {
|
|
1090
|
-
open = {
|
|
1091
|
-
fence: openerMatch[1],
|
|
1092
|
-
openerLine: trimmed
|
|
1093
|
-
};
|
|
1094
|
-
continue;
|
|
1095
|
-
}
|
|
1096
|
-
if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
|
|
1097
|
-
open = void 0;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
if (!open) {
|
|
1101
|
-
return null;
|
|
1246
|
+
async function recordAuthorizationCompleted(args) {
|
|
1247
|
+
const store = args.store ?? await defaultStore();
|
|
1248
|
+
const entries = await store.read(args);
|
|
1249
|
+
const sessionId = currentSessionId(entries);
|
|
1250
|
+
if (projectionEntries(entries).some(
|
|
1251
|
+
(entry) => entry.type === "authorization_completed" && entry.authorizationId === args.authorizationId
|
|
1252
|
+
)) {
|
|
1253
|
+
return;
|
|
1102
1254
|
}
|
|
1255
|
+
await store.append({
|
|
1256
|
+
scope: args,
|
|
1257
|
+
entries: [
|
|
1258
|
+
authorizationCompletedEntry({
|
|
1259
|
+
createdAtMs: Date.now(),
|
|
1260
|
+
kind: args.kind,
|
|
1261
|
+
sessionId,
|
|
1262
|
+
provider: args.provider,
|
|
1263
|
+
requesterId: args.requesterId,
|
|
1264
|
+
authorizationId: args.authorizationId
|
|
1265
|
+
})
|
|
1266
|
+
],
|
|
1267
|
+
ttlMs: args.ttlMs
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
async function commitMessages(args) {
|
|
1271
|
+
const store = args.store ?? await defaultStore();
|
|
1272
|
+
const entries = await store.read(args);
|
|
1273
|
+
const existingProjection = project(entries);
|
|
1274
|
+
const currentId = currentSessionId(entries);
|
|
1275
|
+
const nextEntries = commitEntries(
|
|
1276
|
+
existingProjection.messages,
|
|
1277
|
+
args.messages,
|
|
1278
|
+
currentId,
|
|
1279
|
+
entries,
|
|
1280
|
+
existingProjection.requester,
|
|
1281
|
+
args.requester
|
|
1282
|
+
);
|
|
1283
|
+
await store.append({
|
|
1284
|
+
scope: args,
|
|
1285
|
+
entries: nextEntries,
|
|
1286
|
+
ttlMs: args.ttlMs
|
|
1287
|
+
});
|
|
1103
1288
|
return {
|
|
1104
|
-
|
|
1105
|
-
${open.fence}`,
|
|
1106
|
-
reopenPrefix: `${open.openerLine}
|
|
1107
|
-
`
|
|
1289
|
+
sessionId: nextEntries.find((entry) => entry.type === "projection_reset")?.sessionId ?? currentId
|
|
1108
1290
|
};
|
|
1109
1291
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1292
|
+
|
|
1293
|
+
// src/chat/xml.ts
|
|
1294
|
+
function escapeXml(value) {
|
|
1295
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
1113
1296
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1297
|
+
|
|
1298
|
+
// src/chat/prompt.ts
|
|
1299
|
+
import fs from "fs";
|
|
1300
|
+
import path from "path";
|
|
1301
|
+
|
|
1302
|
+
// src/chat/interruption-marker.ts
|
|
1303
|
+
var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
|
|
1304
|
+
function getInterruptionMarker() {
|
|
1305
|
+
return INTERRUPTED_MARKER;
|
|
1116
1306
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1307
|
+
|
|
1308
|
+
// src/chat/slack/status-format.ts
|
|
1309
|
+
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
1310
|
+
function truncateStatusText(text) {
|
|
1311
|
+
const trimmed = text.trim();
|
|
1312
|
+
if (!trimmed) {
|
|
1313
|
+
return "";
|
|
1124
1314
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
return { prefix, rest };
|
|
1315
|
+
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
1316
|
+
return trimmed;
|
|
1128
1317
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
forceSplitBudget(text, carryoverBudget)
|
|
1137
|
-
));
|
|
1318
|
+
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// src/chat/slack/mrkdwn.ts
|
|
1322
|
+
function readInlineCodeSpan(line, start) {
|
|
1323
|
+
if (line[start] !== "`") {
|
|
1324
|
+
return void 0;
|
|
1138
1325
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1326
|
+
let n = 1;
|
|
1327
|
+
while (line[start + n] === "`") {
|
|
1328
|
+
n++;
|
|
1142
1329
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
const budget = {
|
|
1150
|
-
maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
|
|
1151
|
-
maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
|
|
1152
|
-
};
|
|
1153
|
-
const { prefix, rest } = (() => {
|
|
1154
|
-
if (options?.forceSplit) {
|
|
1155
|
-
return takeSlackContinuationChunk(text, budget);
|
|
1330
|
+
const marker = "`".repeat(n);
|
|
1331
|
+
let search = start + n;
|
|
1332
|
+
while (search < line.length) {
|
|
1333
|
+
const close = line.indexOf(marker, search);
|
|
1334
|
+
if (close === -1) {
|
|
1335
|
+
return void 0;
|
|
1156
1336
|
}
|
|
1157
|
-
const
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
};
|
|
1337
|
+
const after = close + n;
|
|
1338
|
+
if (line[after] !== "`") {
|
|
1339
|
+
return { text: line.slice(start, after), end: after };
|
|
1340
|
+
}
|
|
1341
|
+
search = after + 1;
|
|
1342
|
+
}
|
|
1343
|
+
return void 0;
|
|
1165
1344
|
}
|
|
1166
|
-
function
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
const normalized = text.replace(/\r\n?/g, "\n");
|
|
1170
|
-
if (!normalized) {
|
|
1171
|
-
return { prefix: "", rest: "" };
|
|
1345
|
+
function readExistingSlackAngleToken(line, start) {
|
|
1346
|
+
if (line[start] !== "<") {
|
|
1347
|
+
return void 0;
|
|
1172
1348
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1349
|
+
const close = line.indexOf(">", start + 1);
|
|
1350
|
+
if (close === -1) {
|
|
1351
|
+
return void 0;
|
|
1175
1352
|
}
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
if (prefix) {
|
|
1180
|
-
return {
|
|
1181
|
-
prefix,
|
|
1182
|
-
rest: normalized.slice(prefix.length).trimStart()
|
|
1183
|
-
};
|
|
1353
|
+
const body = line.slice(start + 1, close);
|
|
1354
|
+
if (/^(?:https?:\/\/|@|#|!)/.test(body)) {
|
|
1355
|
+
return { text: line.slice(start, close + 1), end: close + 1 };
|
|
1184
1356
|
}
|
|
1185
|
-
|
|
1186
|
-
return {
|
|
1187
|
-
prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
|
|
1188
|
-
rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
|
|
1189
|
-
};
|
|
1357
|
+
return void 0;
|
|
1190
1358
|
}
|
|
1191
|
-
function
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
return [];
|
|
1359
|
+
function readMarkdownLink(line, start) {
|
|
1360
|
+
if (line[start] !== "[") {
|
|
1361
|
+
return void 0;
|
|
1195
1362
|
}
|
|
1196
|
-
const
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
while (remaining) {
|
|
1200
|
-
const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
|
|
1201
|
-
if (fitsFinalChunk) {
|
|
1202
|
-
chunks.push(
|
|
1203
|
-
options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
|
|
1204
|
-
);
|
|
1205
|
-
break;
|
|
1206
|
-
}
|
|
1207
|
-
const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
|
|
1208
|
-
...continuationBudget,
|
|
1209
|
-
forceSplit: true
|
|
1210
|
-
});
|
|
1211
|
-
chunks.push(renderedPrefix);
|
|
1212
|
-
remaining = rest;
|
|
1363
|
+
const labelEnd = line.indexOf("](", start + 1);
|
|
1364
|
+
if (labelEnd === -1) {
|
|
1365
|
+
return void 0;
|
|
1213
1366
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1367
|
+
const destStart = labelEnd + 2;
|
|
1368
|
+
if (!line.startsWith("http://", destStart) && !line.startsWith("https://", destStart)) {
|
|
1369
|
+
return void 0;
|
|
1216
1370
|
}
|
|
1217
|
-
|
|
1371
|
+
const closeParens = line.indexOf(")", destStart);
|
|
1372
|
+
if (closeParens === -1) {
|
|
1373
|
+
return void 0;
|
|
1374
|
+
}
|
|
1375
|
+
return { text: line.slice(start, closeParens + 1), end: closeParens + 1 };
|
|
1218
1376
|
}
|
|
1219
|
-
function
|
|
1220
|
-
|
|
1377
|
+
function hasUnmatchedClosingParen(text) {
|
|
1378
|
+
let balance = 0;
|
|
1379
|
+
for (const ch of text) {
|
|
1380
|
+
if (ch === "(") balance++;
|
|
1381
|
+
else if (ch === ")") balance--;
|
|
1382
|
+
}
|
|
1383
|
+
return balance < 0;
|
|
1221
1384
|
}
|
|
1222
|
-
function
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
if (
|
|
1227
|
-
|
|
1228
|
-
raw: "",
|
|
1229
|
-
files
|
|
1230
|
-
};
|
|
1385
|
+
function readBareUrl(line, start) {
|
|
1386
|
+
let end = start;
|
|
1387
|
+
while (end < line.length) {
|
|
1388
|
+
const ch = line[end];
|
|
1389
|
+
if (/\s/.test(ch) || ch === "<" || ch === ">" || ch === '"' || ch === "`" || ch === "|" || ch === "*") {
|
|
1390
|
+
break;
|
|
1231
1391
|
}
|
|
1232
|
-
|
|
1233
|
-
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
1234
|
-
);
|
|
1392
|
+
end++;
|
|
1235
1393
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1394
|
+
if (end === start) {
|
|
1395
|
+
return void 0;
|
|
1396
|
+
}
|
|
1397
|
+
let raw = line.slice(start, end);
|
|
1398
|
+
let suffix = "";
|
|
1399
|
+
const peel = () => {
|
|
1400
|
+
suffix = raw.slice(-1) + suffix;
|
|
1401
|
+
raw = raw.slice(0, -1);
|
|
1239
1402
|
};
|
|
1403
|
+
const shouldPeel = () => raw.endsWith("_") || /[.,!?;:]$/.test(raw) || raw.endsWith(")") && hasUnmatchedClosingParen(raw);
|
|
1404
|
+
while (raw.length > 0 && shouldPeel()) {
|
|
1405
|
+
peel();
|
|
1406
|
+
}
|
|
1407
|
+
if (!/^https?:\/\/.+/.test(raw)) {
|
|
1408
|
+
return void 0;
|
|
1409
|
+
}
|
|
1410
|
+
return { url: raw, suffix, end };
|
|
1240
1411
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1412
|
+
function wrapBareUrlsOnLine(line) {
|
|
1413
|
+
let result = "";
|
|
1414
|
+
let i = 0;
|
|
1415
|
+
while (i < line.length) {
|
|
1416
|
+
const codeSpan = readInlineCodeSpan(line, i);
|
|
1417
|
+
if (codeSpan) {
|
|
1418
|
+
result += codeSpan.text;
|
|
1419
|
+
i = codeSpan.end;
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
const angleToken = readExistingSlackAngleToken(line, i);
|
|
1423
|
+
if (angleToken) {
|
|
1424
|
+
result += angleToken.text;
|
|
1425
|
+
i = angleToken.end;
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
const mdLink = readMarkdownLink(line, i);
|
|
1429
|
+
if (mdLink) {
|
|
1430
|
+
result += mdLink.text;
|
|
1431
|
+
i = mdLink.end;
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
if (line.startsWith("https://", i) || line.startsWith("http://", i)) {
|
|
1435
|
+
const parsed = readBareUrl(line, i);
|
|
1436
|
+
if (parsed) {
|
|
1437
|
+
result += `<${parsed.url}>${parsed.suffix}`;
|
|
1438
|
+
i = parsed.end;
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
result += line[i];
|
|
1443
|
+
i++;
|
|
1444
|
+
}
|
|
1445
|
+
return result;
|
|
1249
1446
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
const
|
|
1255
|
-
|
|
1256
|
-
|
|
1447
|
+
function wrapBareUrls(text) {
|
|
1448
|
+
const lines = text.split("\n");
|
|
1449
|
+
const out = [];
|
|
1450
|
+
let inCodeBlock = false;
|
|
1451
|
+
for (const line of lines) {
|
|
1452
|
+
if (line.trimStart().startsWith("```")) {
|
|
1453
|
+
inCodeBlock = !inCodeBlock;
|
|
1454
|
+
out.push(line);
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1457
|
+
out.push(inCodeBlock ? line : wrapBareUrlsOnLine(line));
|
|
1458
|
+
}
|
|
1459
|
+
return out.join("\n");
|
|
1257
1460
|
}
|
|
1258
|
-
function
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
},
|
|
1273
|
-
`Loaded ${fileName}`
|
|
1274
|
-
);
|
|
1461
|
+
function ensureBlockSpacing(text) {
|
|
1462
|
+
const codeBlockPattern = /^```/;
|
|
1463
|
+
const listItemPattern = /^[-*•]\s|^\d+\.\s/;
|
|
1464
|
+
const lines = text.split("\n");
|
|
1465
|
+
const result = [];
|
|
1466
|
+
let inCodeBlock = false;
|
|
1467
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1468
|
+
const line = lines[i];
|
|
1469
|
+
const isCodeFence = codeBlockPattern.test(line.trimStart());
|
|
1470
|
+
if (isCodeFence) {
|
|
1471
|
+
if (!inCodeBlock) {
|
|
1472
|
+
const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
|
|
1473
|
+
if (prev2 !== void 0 && prev2.trim() !== "") {
|
|
1474
|
+
result.push("");
|
|
1275
1475
|
}
|
|
1276
|
-
return raw;
|
|
1277
1476
|
}
|
|
1278
|
-
|
|
1477
|
+
inCodeBlock = !inCodeBlock;
|
|
1478
|
+
result.push(line);
|
|
1279
1479
|
continue;
|
|
1280
1480
|
}
|
|
1481
|
+
if (inCodeBlock) {
|
|
1482
|
+
result.push(line);
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
1486
|
+
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
|
|
1487
|
+
result.push("");
|
|
1488
|
+
}
|
|
1489
|
+
result.push(line);
|
|
1281
1490
|
}
|
|
1282
|
-
return
|
|
1283
|
-
}
|
|
1284
|
-
function loadSoul() {
|
|
1285
|
-
const soul = loadOptionalMarkdownFile(soulPathCandidates(), "SOUL.md");
|
|
1286
|
-
if (soul) {
|
|
1287
|
-
return soul;
|
|
1288
|
-
}
|
|
1289
|
-
logWarn(
|
|
1290
|
-
"soul_load_fallback",
|
|
1291
|
-
{},
|
|
1292
|
-
{
|
|
1293
|
-
"app.file.candidates": soulPathCandidates()
|
|
1294
|
-
},
|
|
1295
|
-
"SOUL.md not found; using built-in default personality"
|
|
1296
|
-
);
|
|
1297
|
-
return DEFAULT_SOUL;
|
|
1491
|
+
return result.join("\n");
|
|
1298
1492
|
}
|
|
1299
|
-
function
|
|
1300
|
-
|
|
1493
|
+
function normalizeSlackReplyMarkdown(text) {
|
|
1494
|
+
let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
|
|
1495
|
+
normalized = wrapBareUrls(normalized);
|
|
1496
|
+
normalized = ensureBlockSpacing(normalized);
|
|
1497
|
+
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
1301
1498
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
logWarn(
|
|
1307
|
-
"soul_load_failed",
|
|
1308
|
-
{},
|
|
1309
|
-
{
|
|
1310
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1311
|
-
},
|
|
1312
|
-
"Failed to load SOUL.md; using built-in default personality"
|
|
1313
|
-
);
|
|
1314
|
-
return DEFAULT_SOUL;
|
|
1315
|
-
}
|
|
1316
|
-
})();
|
|
1317
|
-
var JUNIOR_WORLD = (() => {
|
|
1318
|
-
try {
|
|
1319
|
-
return loadWorld();
|
|
1320
|
-
} catch (error) {
|
|
1321
|
-
logWarn(
|
|
1322
|
-
"world_load_failed",
|
|
1323
|
-
{},
|
|
1324
|
-
{
|
|
1325
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1326
|
-
},
|
|
1327
|
-
"Failed to load WORLD.md; omitting world prompt context"
|
|
1328
|
-
);
|
|
1329
|
-
return null;
|
|
1499
|
+
function normalizeSlackStatusText(text) {
|
|
1500
|
+
const trimmed = text.trim();
|
|
1501
|
+
if (!trimmed) {
|
|
1502
|
+
return "";
|
|
1330
1503
|
}
|
|
1331
|
-
|
|
1332
|
-
function workspaceSkillDir(skillName) {
|
|
1333
|
-
return sandboxSkillDir(skillName);
|
|
1504
|
+
return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
|
|
1334
1505
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
return
|
|
1506
|
+
|
|
1507
|
+
// src/chat/slack/output.ts
|
|
1508
|
+
var MAX_INLINE_CHARS = 2200;
|
|
1509
|
+
var MAX_INLINE_LINES = 45;
|
|
1510
|
+
var CONTINUED_MARKER = "\n\n[Continued below]";
|
|
1511
|
+
function countSlackLines(text) {
|
|
1512
|
+
if (!text) {
|
|
1513
|
+
return 0;
|
|
1343
1514
|
}
|
|
1515
|
+
return text.split("\n").length;
|
|
1344
1516
|
}
|
|
1345
|
-
function
|
|
1346
|
-
|
|
1347
|
-
if (lines.length === 0) {
|
|
1348
|
-
return null;
|
|
1349
|
-
}
|
|
1350
|
-
return ["<requester>", ...lines, "</requester>"];
|
|
1351
|
-
}
|
|
1352
|
-
function renderTag(tag, lines) {
|
|
1353
|
-
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
1354
|
-
}
|
|
1355
|
-
function renderTagBlock(tag, content) {
|
|
1356
|
-
return [`<${tag}>`, content, `</${tag}>`].join("\n");
|
|
1357
|
-
}
|
|
1358
|
-
function formatSkillEntry(skill) {
|
|
1359
|
-
const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
|
|
1360
|
-
const lines = [];
|
|
1361
|
-
lines.push(" <skill>");
|
|
1362
|
-
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
1363
|
-
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
1364
|
-
lines.push(` <location>${escapeXml(skillLocation)}</location>`);
|
|
1365
|
-
lines.push(" </skill>");
|
|
1366
|
-
return lines;
|
|
1517
|
+
function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
1518
|
+
return text.length <= maxChars && countSlackLines(text) <= maxLines;
|
|
1367
1519
|
}
|
|
1368
|
-
function
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
);
|
|
1372
|
-
const invokedExplicitOnly = invocation ? skills.filter(
|
|
1373
|
-
(s) => s.disableModelInvocation === true && s.name === invocation.skillName
|
|
1374
|
-
) : [];
|
|
1375
|
-
const sections = [];
|
|
1376
|
-
if (autoSelectable.length > 0) {
|
|
1377
|
-
const available = [
|
|
1378
|
-
"<available-skills>",
|
|
1379
|
-
"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."
|
|
1380
|
-
];
|
|
1381
|
-
for (const skill of autoSelectable) {
|
|
1382
|
-
available.push(...formatSkillEntry(skill));
|
|
1383
|
-
}
|
|
1384
|
-
available.push("</available-skills>");
|
|
1385
|
-
sections.push(available.join("\n"));
|
|
1520
|
+
function findSplitIndex(text, maxChars) {
|
|
1521
|
+
if (text.length <= maxChars) {
|
|
1522
|
+
return text.length;
|
|
1386
1523
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
];
|
|
1392
|
-
for (const skill of invokedExplicitOnly) {
|
|
1393
|
-
userCallable.push(...formatSkillEntry(skill));
|
|
1394
|
-
}
|
|
1395
|
-
userCallable.push("</user-callable-skills>");
|
|
1396
|
-
sections.push(userCallable.join("\n"));
|
|
1524
|
+
const bounded = text.slice(0, maxChars);
|
|
1525
|
+
const newlineIndex = bounded.lastIndexOf("\n");
|
|
1526
|
+
if (newlineIndex > 0) {
|
|
1527
|
+
return newlineIndex;
|
|
1397
1528
|
}
|
|
1398
|
-
|
|
1529
|
+
const spaceIndex = bounded.lastIndexOf(" ");
|
|
1530
|
+
if (spaceIndex > 0) {
|
|
1531
|
+
return spaceIndex;
|
|
1532
|
+
}
|
|
1533
|
+
return maxChars;
|
|
1399
1534
|
}
|
|
1400
|
-
function
|
|
1401
|
-
if (
|
|
1402
|
-
return
|
|
1535
|
+
function splitByLineBudget(text, maxLines) {
|
|
1536
|
+
if (maxLines <= 0) {
|
|
1537
|
+
return "";
|
|
1403
1538
|
}
|
|
1404
|
-
const lines =
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
for (const catalog of catalogs) {
|
|
1408
|
-
lines.push(" <catalog>");
|
|
1409
|
-
lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
|
|
1410
|
-
lines.push(
|
|
1411
|
-
` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
|
|
1412
|
-
);
|
|
1413
|
-
lines.push(" </catalog>");
|
|
1539
|
+
const lines = text.split("\n");
|
|
1540
|
+
if (lines.length <= maxLines) {
|
|
1541
|
+
return text;
|
|
1414
1542
|
}
|
|
1415
|
-
return lines.join("\n");
|
|
1543
|
+
return lines.slice(0, maxLines).join("\n");
|
|
1416
1544
|
}
|
|
1417
|
-
function
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
const
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1545
|
+
function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
1546
|
+
return {
|
|
1547
|
+
maxChars: Math.max(1, maxChars - suffix.length),
|
|
1548
|
+
maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function forceSplitBudget(text, budget) {
|
|
1552
|
+
const lineCount = countSlackLines(text);
|
|
1553
|
+
return {
|
|
1554
|
+
maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
|
|
1555
|
+
maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
function getFenceContinuation(text) {
|
|
1559
|
+
let open;
|
|
1560
|
+
for (const line of text.split("\n")) {
|
|
1561
|
+
const trimmed = line.trimStart();
|
|
1562
|
+
const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
1563
|
+
if (!openerMatch) {
|
|
1564
|
+
continue;
|
|
1429
1565
|
}
|
|
1430
|
-
if (
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1566
|
+
if (!open) {
|
|
1567
|
+
open = {
|
|
1568
|
+
fence: openerMatch[1],
|
|
1569
|
+
openerLine: trimmed
|
|
1570
|
+
};
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
|
|
1574
|
+
open = void 0;
|
|
1434
1575
|
}
|
|
1435
|
-
lines.push(" </tool>");
|
|
1436
1576
|
}
|
|
1437
|
-
|
|
1438
|
-
}
|
|
1439
|
-
function formatReferenceFilesLines() {
|
|
1440
|
-
const files = listReferenceFiles();
|
|
1441
|
-
if (files.length === 0) {
|
|
1577
|
+
if (!open) {
|
|
1442
1578
|
return null;
|
|
1443
1579
|
}
|
|
1444
|
-
return
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1580
|
+
return {
|
|
1581
|
+
closeSuffix: text.endsWith("\n") ? open.fence : `
|
|
1582
|
+
${open.fence}`,
|
|
1583
|
+
reopenPrefix: `${open.openerLine}
|
|
1584
|
+
`
|
|
1585
|
+
};
|
|
1448
1586
|
}
|
|
1449
|
-
function
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1587
|
+
function appendSlackSuffix(text, marker) {
|
|
1588
|
+
const carryover = getFenceContinuation(text);
|
|
1589
|
+
return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
|
|
1590
|
+
}
|
|
1591
|
+
function stripTrailingContinuationMarker(text) {
|
|
1592
|
+
return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
|
|
1593
|
+
}
|
|
1594
|
+
function takeSlackContinuationChunk(text, budget) {
|
|
1595
|
+
let { prefix, rest } = takeSlackInlinePrefix(text, budget);
|
|
1596
|
+
if (!rest) {
|
|
1597
|
+
({ prefix, rest } = takeSlackInlinePrefix(
|
|
1598
|
+
text,
|
|
1599
|
+
forceSplitBudget(text, budget)
|
|
1600
|
+
));
|
|
1454
1601
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1602
|
+
let carryover = rest ? getFenceContinuation(prefix) : null;
|
|
1603
|
+
if (!carryover) {
|
|
1604
|
+
return { prefix, rest };
|
|
1457
1605
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1606
|
+
const carryoverBudget = reserveInlineBudgetForSuffix(
|
|
1607
|
+
`${carryover.closeSuffix}${CONTINUED_MARKER}`
|
|
1608
|
+
);
|
|
1609
|
+
({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
|
|
1610
|
+
if (!rest) {
|
|
1611
|
+
({ prefix, rest } = takeSlackInlinePrefix(
|
|
1612
|
+
text,
|
|
1613
|
+
forceSplitBudget(text, carryoverBudget)
|
|
1614
|
+
));
|
|
1615
|
+
}
|
|
1616
|
+
carryover = rest ? getFenceContinuation(prefix) : null;
|
|
1617
|
+
if (!carryover) {
|
|
1618
|
+
return { prefix, rest };
|
|
1619
|
+
}
|
|
1620
|
+
return {
|
|
1621
|
+
prefix,
|
|
1622
|
+
rest: `${carryover.reopenPrefix}${rest}`
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
function takeSlackContinuationPrefix(text, options) {
|
|
1626
|
+
const budget = {
|
|
1627
|
+
maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
|
|
1628
|
+
maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
|
|
1629
|
+
};
|
|
1630
|
+
const { prefix, rest } = (() => {
|
|
1631
|
+
if (options?.forceSplit) {
|
|
1632
|
+
return takeSlackContinuationChunk(text, budget);
|
|
1467
1633
|
}
|
|
1634
|
+
const initial = takeSlackInlinePrefix(text, budget);
|
|
1635
|
+
return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
|
|
1636
|
+
})();
|
|
1637
|
+
return {
|
|
1638
|
+
prefix,
|
|
1639
|
+
renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
|
|
1640
|
+
rest
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
function takeSlackInlinePrefix(text, options) {
|
|
1644
|
+
const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
|
|
1645
|
+
const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
|
|
1646
|
+
const normalized = text.replace(/\r\n?/g, "\n");
|
|
1647
|
+
if (!normalized) {
|
|
1648
|
+
return { prefix: "", rest: "" };
|
|
1468
1649
|
}
|
|
1469
|
-
if (
|
|
1470
|
-
|
|
1650
|
+
if (fitsInlineBudget(normalized, maxChars, maxLines)) {
|
|
1651
|
+
return { prefix: normalized, rest: "" };
|
|
1471
1652
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1653
|
+
const lineBounded = splitByLineBudget(normalized, maxLines);
|
|
1654
|
+
const cutIndex = findSplitIndex(lineBounded, maxChars);
|
|
1655
|
+
const prefix = lineBounded.slice(0, cutIndex).trimEnd();
|
|
1656
|
+
if (prefix) {
|
|
1657
|
+
return {
|
|
1658
|
+
prefix,
|
|
1659
|
+
rest: normalized.slice(prefix.length).trimStart()
|
|
1660
|
+
};
|
|
1474
1661
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
);
|
|
1481
|
-
if (keys.length === 0) return null;
|
|
1482
|
-
return keys.map(
|
|
1483
|
-
(key2) => `- ${escapeXml(key2)}: ${formatConfigurationValue(configuration?.[key2])}`
|
|
1484
|
-
);
|
|
1662
|
+
const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
|
|
1663
|
+
return {
|
|
1664
|
+
prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
|
|
1665
|
+
rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
|
|
1666
|
+
};
|
|
1485
1667
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
"- 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.",
|
|
1491
|
-
"- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
|
|
1492
|
-
"- 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.",
|
|
1493
|
-
"- 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.",
|
|
1494
|
-
`- 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.`,
|
|
1495
|
-
"- 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.",
|
|
1496
|
-
"- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
|
|
1497
|
-
"- 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."
|
|
1498
|
-
];
|
|
1499
|
-
var TOOL_CALL_STYLE_RULES = [
|
|
1500
|
-
"- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
|
|
1501
|
-
"- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
|
|
1502
|
-
"- 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.",
|
|
1503
|
-
"- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
|
|
1504
|
-
];
|
|
1505
|
-
var SKILL_POLICY_RULES = [
|
|
1506
|
-
"- Only load skills listed in `<available-skills>`, `<user-callable-skills>`, or named by `<explicit-skill-trigger>`. Never guess or invent a skill name.",
|
|
1507
|
-
"- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
|
|
1508
|
-
];
|
|
1509
|
-
var EXECUTION_CONTRACT_RULES = [
|
|
1510
|
-
"- Actionable request: act in this turn.",
|
|
1511
|
-
"- 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.",
|
|
1512
|
-
"- 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.",
|
|
1513
|
-
"- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
|
|
1514
|
-
"- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
|
|
1515
|
-
"- 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."
|
|
1516
|
-
];
|
|
1517
|
-
var CONVERSATION_RULES = [
|
|
1518
|
-
"- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
|
|
1519
|
-
"- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
|
|
1520
|
-
"- Runtime owns continuation and authorization notices; on resumed turns, answer with the final requested content only."
|
|
1521
|
-
];
|
|
1522
|
-
var SLACK_ACTION_RULES = [
|
|
1523
|
-
"- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
|
|
1524
|
-
"- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
|
|
1525
|
-
"- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
|
|
1526
|
-
"- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
|
|
1527
|
-
"- 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.",
|
|
1528
|
-
"- Do not use reactions as progress indicators."
|
|
1529
|
-
];
|
|
1530
|
-
var SAFETY_RULES = [
|
|
1531
|
-
"- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
|
|
1532
|
-
"- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
|
|
1533
|
-
"- 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."
|
|
1534
|
-
];
|
|
1535
|
-
var FAILURE_RULES = [
|
|
1536
|
-
"- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
|
|
1537
|
-
"- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
|
|
1538
|
-
"- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
|
|
1539
|
-
];
|
|
1540
|
-
function renderRuleSection(tag, lines) {
|
|
1541
|
-
return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
|
|
1542
|
-
}
|
|
1543
|
-
function buildBehaviorSection() {
|
|
1544
|
-
return [
|
|
1545
|
-
renderRuleSection("tool-policy", TOOL_POLICY_RULES),
|
|
1546
|
-
renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
|
|
1547
|
-
renderRuleSection("skill-policy", SKILL_POLICY_RULES),
|
|
1548
|
-
renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
|
|
1549
|
-
renderRuleSection("conversation", CONVERSATION_RULES),
|
|
1550
|
-
renderRuleSection("slack-actions", SLACK_ACTION_RULES),
|
|
1551
|
-
renderRuleSection("safety", SAFETY_RULES),
|
|
1552
|
-
renderRuleSection("failure-handling", FAILURE_RULES)
|
|
1553
|
-
].join("\n\n");
|
|
1554
|
-
}
|
|
1555
|
-
function buildOutputSection() {
|
|
1556
|
-
const openTag = `<output format="slack-markdown" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
|
|
1557
|
-
return [
|
|
1558
|
-
openTag,
|
|
1559
|
-
"- Start with the answer or result, not internal process narration.",
|
|
1560
|
-
"- 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.",
|
|
1561
|
-
"- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
|
|
1562
|
-
"- 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.",
|
|
1563
|
-
"- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
|
|
1564
|
-
"</output>"
|
|
1565
|
-
].join("\n");
|
|
1566
|
-
}
|
|
1567
|
-
function buildIdentitySection() {
|
|
1568
|
-
return [
|
|
1569
|
-
"# Identity",
|
|
1570
|
-
`Your Slack username is \`${botConfig.userName}\`.`
|
|
1571
|
-
].join("\n");
|
|
1572
|
-
}
|
|
1573
|
-
function buildPersonalitySection() {
|
|
1574
|
-
return ["# Personality", JUNIOR_PERSONALITY.trim()].join("\n");
|
|
1575
|
-
}
|
|
1576
|
-
function buildWorldSection() {
|
|
1577
|
-
if (!JUNIOR_WORLD) {
|
|
1578
|
-
return null;
|
|
1579
|
-
}
|
|
1580
|
-
return ["# World", JUNIOR_WORLD.trim()].join("\n");
|
|
1581
|
-
}
|
|
1582
|
-
function buildRuntimeSection(params) {
|
|
1583
|
-
const lines = [
|
|
1584
|
-
params.conversationId ? `- gen_ai.conversation.id: ${escapeXml(params.conversationId)}` : "",
|
|
1585
|
-
params.slackConversation?.type ? `- slack.conversation.type: ${escapeXml(params.slackConversation.type)}` : "",
|
|
1586
|
-
params.slackConversation?.name ? `- slack.conversation.name: ${escapeXml(params.slackConversation.name)}` : ""
|
|
1587
|
-
].filter(Boolean);
|
|
1588
|
-
if (lines.length === 0) {
|
|
1589
|
-
return null;
|
|
1590
|
-
}
|
|
1591
|
-
return renderTagBlock("runtime", lines.join("\n"));
|
|
1592
|
-
}
|
|
1593
|
-
function buildContextSection(params) {
|
|
1594
|
-
const blocks = [];
|
|
1595
|
-
const referenceLines = formatReferenceFilesLines();
|
|
1596
|
-
if (referenceLines) {
|
|
1597
|
-
blocks.push(
|
|
1598
|
-
renderTag("reference-files", [
|
|
1599
|
-
"Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
|
|
1600
|
-
...referenceLines
|
|
1601
|
-
])
|
|
1602
|
-
);
|
|
1603
|
-
}
|
|
1604
|
-
const requesterLines = renderRequesterBlock({
|
|
1605
|
-
full_name: params.requester?.fullName,
|
|
1606
|
-
user_name: params.requester?.userName,
|
|
1607
|
-
user_id: params.requester?.userId
|
|
1608
|
-
});
|
|
1609
|
-
if (requesterLines) {
|
|
1610
|
-
blocks.push(requesterLines);
|
|
1611
|
-
}
|
|
1612
|
-
const artifactLines = formatArtifactsLines(params.artifactState);
|
|
1613
|
-
if (artifactLines) {
|
|
1614
|
-
blocks.push(renderTag("artifacts", artifactLines));
|
|
1615
|
-
}
|
|
1616
|
-
const configLines = formatConfigurationLines(params.configuration);
|
|
1617
|
-
if (configLines) {
|
|
1618
|
-
blocks.push(
|
|
1619
|
-
renderTag("configuration", [
|
|
1620
|
-
"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.",
|
|
1621
|
-
...configLines
|
|
1622
|
-
])
|
|
1623
|
-
);
|
|
1624
|
-
}
|
|
1625
|
-
if (params.invocation) {
|
|
1626
|
-
blocks.push(
|
|
1627
|
-
renderTag("explicit-skill-trigger", [
|
|
1628
|
-
"Treat this skill as selected. Load it unless the tool says it is unavailable.",
|
|
1629
|
-
`/${escapeXml(params.invocation.skillName)}`
|
|
1630
|
-
])
|
|
1631
|
-
);
|
|
1632
|
-
}
|
|
1633
|
-
const body = blocks.map((block) => block.join("\n")).join("\n\n");
|
|
1634
|
-
if (!body) {
|
|
1635
|
-
return null;
|
|
1636
|
-
}
|
|
1637
|
-
return renderTagBlock("context", body);
|
|
1638
|
-
}
|
|
1639
|
-
function buildCapabilitiesSection(params) {
|
|
1640
|
-
const blocks = [];
|
|
1641
|
-
const availableSkills = formatAvailableSkillsForPrompt(
|
|
1642
|
-
params.availableSkills,
|
|
1643
|
-
params.invocation
|
|
1644
|
-
);
|
|
1645
|
-
if (availableSkills) {
|
|
1646
|
-
blocks.push(availableSkills);
|
|
1647
|
-
}
|
|
1648
|
-
const activeCatalogs = formatActiveMcpCatalogsForPrompt(
|
|
1649
|
-
params.activeMcpCatalogs
|
|
1650
|
-
);
|
|
1651
|
-
if (activeCatalogs) {
|
|
1652
|
-
blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
|
|
1653
|
-
}
|
|
1654
|
-
const toolGuidance = formatToolGuidanceForPrompt(params.toolGuidance ?? []);
|
|
1655
|
-
if (toolGuidance) {
|
|
1656
|
-
blocks.push(renderTagBlock("tool-guidance", toolGuidance));
|
|
1657
|
-
}
|
|
1658
|
-
if (blocks.length === 0) {
|
|
1659
|
-
return null;
|
|
1668
|
+
function splitSlackReplyText(text, options) {
|
|
1669
|
+
const normalized = normalizeSlackReplyMarkdown(text);
|
|
1670
|
+
if (!normalized) {
|
|
1671
|
+
return [];
|
|
1660
1672
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1673
|
+
const chunks = [];
|
|
1674
|
+
const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
1675
|
+
let remaining = normalized;
|
|
1676
|
+
while (remaining) {
|
|
1677
|
+
const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
|
|
1678
|
+
if (fitsFinalChunk) {
|
|
1679
|
+
chunks.push(
|
|
1680
|
+
options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
|
|
1681
|
+
);
|
|
1682
|
+
break;
|
|
1683
|
+
}
|
|
1684
|
+
const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
|
|
1685
|
+
...continuationBudget,
|
|
1686
|
+
forceSplit: true
|
|
1687
|
+
});
|
|
1688
|
+
chunks.push(renderedPrefix);
|
|
1689
|
+
remaining = rest;
|
|
1678
1690
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
availableSkills: params.availableSkills,
|
|
1682
|
-
activeMcpCatalogs: params.activeMcpCatalogs ?? [],
|
|
1683
|
-
invocation: params.invocation,
|
|
1684
|
-
toolGuidance: params.toolGuidance ?? []
|
|
1685
|
-
}),
|
|
1686
|
-
buildContextSection({
|
|
1687
|
-
requester: params.requester,
|
|
1688
|
-
artifactState: params.artifactState,
|
|
1689
|
-
configuration: params.configuration,
|
|
1690
|
-
invocation: params.invocation
|
|
1691
|
-
}),
|
|
1692
|
-
buildRuntimeSection(params.runtime ?? {})
|
|
1693
|
-
].filter((section) => Boolean(section));
|
|
1694
|
-
if (runtimeSections.length === 0) {
|
|
1695
|
-
return null;
|
|
1691
|
+
if (chunks.length === 2) {
|
|
1692
|
+
chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
|
|
1696
1693
|
}
|
|
1697
|
-
|
|
1698
|
-
`<${TURN_CONTEXT_TAG}>`,
|
|
1699
|
-
TURN_CONTEXT_HEADER,
|
|
1700
|
-
"The current user instruction appears after this block in the same message.",
|
|
1701
|
-
...runtimeSections,
|
|
1702
|
-
`</${TURN_CONTEXT_TAG}>`
|
|
1703
|
-
].filter((section) => Boolean(section));
|
|
1704
|
-
return sections.join("\n\n");
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
// src/chat/state/turn-session.ts
|
|
1708
|
-
import { THREAD_STATE_TTL_MS } from "chat";
|
|
1709
|
-
|
|
1710
|
-
// src/chat/state/session-log.ts
|
|
1711
|
-
import { isDeepStrictEqual } from "util";
|
|
1712
|
-
import { z } from "zod";
|
|
1713
|
-
var AGENT_SESSION_LOG_PREFIX = "junior:agent-session-log";
|
|
1714
|
-
var AGENT_SESSION_LOG_SCHEMA_VERSION = 1;
|
|
1715
|
-
var INITIAL_SESSION_ID = "session_0";
|
|
1716
|
-
var SESSION_ID_PREFIX = "session_";
|
|
1717
|
-
var piMessageSchema = z.object({
|
|
1718
|
-
role: z.string()
|
|
1719
|
-
}).passthrough().transform((value) => value);
|
|
1720
|
-
var piMessageEntrySchema = z.object({
|
|
1721
|
-
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
1722
|
-
type: z.literal("pi_message"),
|
|
1723
|
-
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
1724
|
-
message: piMessageSchema
|
|
1725
|
-
});
|
|
1726
|
-
var projectionResetEntrySchema = z.object({
|
|
1727
|
-
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
1728
|
-
type: z.literal("projection_reset"),
|
|
1729
|
-
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
1730
|
-
messages: z.array(piMessageSchema)
|
|
1731
|
-
});
|
|
1732
|
-
var mcpProviderConnectedEntrySchema = z.object({
|
|
1733
|
-
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
1734
|
-
type: z.literal("mcp_provider_connected"),
|
|
1735
|
-
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
1736
|
-
provider: z.string().min(1)
|
|
1737
|
-
});
|
|
1738
|
-
var authorizationKindSchema = z.union([
|
|
1739
|
-
z.literal("plugin"),
|
|
1740
|
-
z.literal("mcp")
|
|
1741
|
-
]);
|
|
1742
|
-
var authorizationRequestedEntrySchema = z.object({
|
|
1743
|
-
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
1744
|
-
type: z.literal("authorization_requested"),
|
|
1745
|
-
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
1746
|
-
createdAtMs: z.number().int().nonnegative(),
|
|
1747
|
-
kind: authorizationKindSchema,
|
|
1748
|
-
provider: z.string().min(1),
|
|
1749
|
-
requesterId: z.string().min(1),
|
|
1750
|
-
authorizationId: z.string().min(1),
|
|
1751
|
-
delivery: z.union([
|
|
1752
|
-
z.literal("private_link_sent"),
|
|
1753
|
-
z.literal("private_link_reused")
|
|
1754
|
-
])
|
|
1755
|
-
});
|
|
1756
|
-
var authorizationCompletedEntrySchema = z.object({
|
|
1757
|
-
schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
|
|
1758
|
-
type: z.literal("authorization_completed"),
|
|
1759
|
-
sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
|
|
1760
|
-
createdAtMs: z.number().int().nonnegative(),
|
|
1761
|
-
kind: authorizationKindSchema,
|
|
1762
|
-
provider: z.string().min(1),
|
|
1763
|
-
requesterId: z.string().min(1),
|
|
1764
|
-
authorizationId: z.string().min(1)
|
|
1765
|
-
});
|
|
1766
|
-
var sessionLogEntrySchema = z.discriminatedUnion("type", [
|
|
1767
|
-
piMessageEntrySchema,
|
|
1768
|
-
projectionResetEntrySchema,
|
|
1769
|
-
mcpProviderConnectedEntrySchema,
|
|
1770
|
-
authorizationRequestedEntrySchema,
|
|
1771
|
-
authorizationCompletedEntrySchema
|
|
1772
|
-
]);
|
|
1773
|
-
function key(scope) {
|
|
1774
|
-
const prefix = getChatConfig().state.keyPrefix;
|
|
1775
|
-
return [
|
|
1776
|
-
...prefix ? [prefix] : [],
|
|
1777
|
-
AGENT_SESSION_LOG_PREFIX,
|
|
1778
|
-
scope.conversationId
|
|
1779
|
-
].join(":");
|
|
1780
|
-
}
|
|
1781
|
-
function rawKey(scope) {
|
|
1782
|
-
return [AGENT_SESSION_LOG_PREFIX, scope.conversationId].join(":");
|
|
1694
|
+
return chunks;
|
|
1783
1695
|
}
|
|
1784
|
-
function
|
|
1785
|
-
return
|
|
1696
|
+
function getSlackContinuationBudget() {
|
|
1697
|
+
return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
1786
1698
|
}
|
|
1787
|
-
function
|
|
1788
|
-
const
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1699
|
+
function buildSlackOutputMessage(text, files) {
|
|
1700
|
+
const normalized = normalizeSlackReplyMarkdown(text);
|
|
1701
|
+
const fileCount = files?.length ?? 0;
|
|
1702
|
+
if (!normalized) {
|
|
1703
|
+
if (fileCount > 0) {
|
|
1704
|
+
return {
|
|
1705
|
+
raw: "",
|
|
1706
|
+
files
|
|
1707
|
+
};
|
|
1792
1708
|
}
|
|
1709
|
+
throw new Error(
|
|
1710
|
+
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
1711
|
+
);
|
|
1793
1712
|
}
|
|
1794
|
-
return
|
|
1713
|
+
return {
|
|
1714
|
+
markdown: normalized,
|
|
1715
|
+
files
|
|
1716
|
+
};
|
|
1795
1717
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1718
|
+
var slackOutputPolicy = {
|
|
1719
|
+
maxInlineChars: MAX_INLINE_CHARS,
|
|
1720
|
+
maxInlineLines: MAX_INLINE_LINES
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
// src/chat/prompt.ts
|
|
1724
|
+
var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
|
|
1725
|
+
function getLoggedMarkdownFiles() {
|
|
1726
|
+
const globalState = globalThis;
|
|
1727
|
+
globalState.__juniorLoggedMarkdownFiles ??= /* @__PURE__ */ new Set();
|
|
1728
|
+
return globalState.__juniorLoggedMarkdownFiles;
|
|
1798
1729
|
}
|
|
1799
|
-
function
|
|
1800
|
-
for (
|
|
1801
|
-
|
|
1802
|
-
|
|
1730
|
+
function loadOptionalMarkdownFile(candidates, fileName) {
|
|
1731
|
+
for (const resolved of candidates) {
|
|
1732
|
+
try {
|
|
1733
|
+
const raw = fs.readFileSync(resolved, "utf8").trim();
|
|
1734
|
+
if (raw.length > 0) {
|
|
1735
|
+
const loggedMarkdownFiles = getLoggedMarkdownFiles();
|
|
1736
|
+
const logKey = `${fileName}:${resolved}`;
|
|
1737
|
+
if (!loggedMarkdownFiles.has(logKey)) {
|
|
1738
|
+
loggedMarkdownFiles.add(logKey);
|
|
1739
|
+
logInfo(
|
|
1740
|
+
`${fileName.toLowerCase()}_loaded`,
|
|
1741
|
+
{},
|
|
1742
|
+
{
|
|
1743
|
+
"file.path": resolved
|
|
1744
|
+
},
|
|
1745
|
+
`Loaded ${fileName}`
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
return raw;
|
|
1749
|
+
}
|
|
1750
|
+
} catch {
|
|
1751
|
+
continue;
|
|
1803
1752
|
}
|
|
1804
1753
|
}
|
|
1805
|
-
return
|
|
1754
|
+
return null;
|
|
1806
1755
|
}
|
|
1807
|
-
function
|
|
1808
|
-
const
|
|
1809
|
-
if (
|
|
1810
|
-
return
|
|
1756
|
+
function loadSoul() {
|
|
1757
|
+
const soul = loadOptionalMarkdownFile(soulPathCandidates(), "SOUL.md");
|
|
1758
|
+
if (soul) {
|
|
1759
|
+
return soul;
|
|
1811
1760
|
}
|
|
1812
|
-
|
|
1761
|
+
logWarn(
|
|
1762
|
+
"soul_load_fallback",
|
|
1763
|
+
{},
|
|
1764
|
+
{
|
|
1765
|
+
"app.file.candidates": soulPathCandidates()
|
|
1766
|
+
},
|
|
1767
|
+
"SOUL.md not found; using built-in default personality"
|
|
1768
|
+
);
|
|
1769
|
+
return DEFAULT_SOUL;
|
|
1813
1770
|
}
|
|
1814
|
-
function
|
|
1815
|
-
|
|
1816
|
-
(entry) => entry.type === "projection_reset"
|
|
1817
|
-
).length;
|
|
1818
|
-
return `${SESSION_ID_PREFIX}${resetCount + 1}`;
|
|
1771
|
+
function loadWorld() {
|
|
1772
|
+
return loadOptionalMarkdownFile(worldPathCandidates(), "WORLD.md");
|
|
1819
1773
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
}
|
|
1834
|
-
if (entryId === sessionId) {
|
|
1835
|
-
sessionEntries.push(entry);
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
return sessionEntries;
|
|
1774
|
+
var JUNIOR_PERSONALITY = (() => {
|
|
1775
|
+
try {
|
|
1776
|
+
return loadSoul();
|
|
1777
|
+
} catch (error) {
|
|
1778
|
+
logWarn(
|
|
1779
|
+
"soul_load_failed",
|
|
1780
|
+
{},
|
|
1781
|
+
{
|
|
1782
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1783
|
+
},
|
|
1784
|
+
"Failed to load SOUL.md; using built-in default personality"
|
|
1785
|
+
);
|
|
1786
|
+
return DEFAULT_SOUL;
|
|
1839
1787
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1788
|
+
})();
|
|
1789
|
+
var JUNIOR_WORLD = (() => {
|
|
1790
|
+
try {
|
|
1791
|
+
return loadWorld();
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
logWarn(
|
|
1794
|
+
"world_load_failed",
|
|
1795
|
+
{},
|
|
1796
|
+
{
|
|
1797
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1798
|
+
},
|
|
1799
|
+
"Failed to load WORLD.md; omitting world prompt context"
|
|
1800
|
+
);
|
|
1801
|
+
return null;
|
|
1802
|
+
}
|
|
1803
|
+
})();
|
|
1804
|
+
function workspaceSkillDir(skillName) {
|
|
1805
|
+
return sandboxSkillDir(skillName);
|
|
1844
1806
|
}
|
|
1845
|
-
function
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
}
|
|
1807
|
+
function formatConfigurationValue(value) {
|
|
1808
|
+
if (typeof value === "string") {
|
|
1809
|
+
return escapeXml(value);
|
|
1810
|
+
}
|
|
1811
|
+
try {
|
|
1812
|
+
return escapeXml(JSON.stringify(value));
|
|
1813
|
+
} catch {
|
|
1814
|
+
return escapeXml(String(value));
|
|
1815
|
+
}
|
|
1852
1816
|
}
|
|
1853
|
-
function
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
};
|
|
1817
|
+
function renderRequesterBlock(fields) {
|
|
1818
|
+
const lines = Object.entries(fields).filter(([, value]) => Boolean(value)).map(([key2, value]) => `- ${key2}: ${escapeXml(value)}`);
|
|
1819
|
+
if (lines.length === 0) {
|
|
1820
|
+
return null;
|
|
1821
|
+
}
|
|
1822
|
+
return ["<requester>", ...lines, "</requester>"];
|
|
1860
1823
|
}
|
|
1861
|
-
function
|
|
1862
|
-
return {
|
|
1863
|
-
schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
|
|
1864
|
-
type: "mcp_provider_connected",
|
|
1865
|
-
sessionId,
|
|
1866
|
-
provider
|
|
1867
|
-
};
|
|
1824
|
+
function renderTag(tag, lines) {
|
|
1825
|
+
return [`<${tag}>`, ...lines, `</${tag}>`];
|
|
1868
1826
|
}
|
|
1869
|
-
function
|
|
1870
|
-
|
|
1871
|
-
return {
|
|
1872
|
-
role: "user",
|
|
1873
|
-
content: [
|
|
1874
|
-
{
|
|
1875
|
-
type: "text",
|
|
1876
|
-
text: `${label} completed for provider "${entry.provider}". Continue the blocked request and retry the provider operation if needed.`
|
|
1877
|
-
}
|
|
1878
|
-
],
|
|
1879
|
-
timestamp: entry.createdAtMs
|
|
1880
|
-
};
|
|
1827
|
+
function renderTagBlock(tag, content) {
|
|
1828
|
+
return [`<${tag}>`, content, `</${tag}>`].join("\n");
|
|
1881
1829
|
}
|
|
1882
|
-
function
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
authorizationId: args.authorizationId,
|
|
1892
|
-
delivery: args.delivery
|
|
1893
|
-
};
|
|
1830
|
+
function formatSkillEntry(skill) {
|
|
1831
|
+
const skillLocation = `${workspaceSkillDir(skill.name)}/SKILL.md`;
|
|
1832
|
+
const lines = [];
|
|
1833
|
+
lines.push(" <skill>");
|
|
1834
|
+
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
1835
|
+
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
1836
|
+
lines.push(` <location>${escapeXml(skillLocation)}</location>`);
|
|
1837
|
+
lines.push(" </skill>");
|
|
1838
|
+
return lines;
|
|
1894
1839
|
}
|
|
1895
|
-
function
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1840
|
+
function formatAvailableSkillsForPrompt(skills, invocation) {
|
|
1841
|
+
const autoSelectable = skills.filter(
|
|
1842
|
+
(s) => s.disableModelInvocation !== true
|
|
1843
|
+
);
|
|
1844
|
+
const invokedExplicitOnly = invocation ? skills.filter(
|
|
1845
|
+
(s) => s.disableModelInvocation === true && s.name === invocation.skillName
|
|
1846
|
+
) : [];
|
|
1847
|
+
const sections = [];
|
|
1848
|
+
if (autoSelectable.length > 0) {
|
|
1849
|
+
const available = [
|
|
1850
|
+
"<available-skills>",
|
|
1851
|
+
"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."
|
|
1852
|
+
];
|
|
1853
|
+
for (const skill of autoSelectable) {
|
|
1854
|
+
available.push(...formatSkillEntry(skill));
|
|
1855
|
+
}
|
|
1856
|
+
available.push("</available-skills>");
|
|
1857
|
+
sections.push(available.join("\n"));
|
|
1858
|
+
}
|
|
1859
|
+
if (invokedExplicitOnly.length > 0) {
|
|
1860
|
+
const userCallable = [
|
|
1861
|
+
"<user-callable-skills>",
|
|
1862
|
+
"The user's current message explicitly references this skill by name. Load it when relevant to the request."
|
|
1863
|
+
];
|
|
1864
|
+
for (const skill of invokedExplicitOnly) {
|
|
1865
|
+
userCallable.push(...formatSkillEntry(skill));
|
|
1866
|
+
}
|
|
1867
|
+
userCallable.push("</user-callable-skills>");
|
|
1868
|
+
sections.push(userCallable.join("\n"));
|
|
1869
|
+
}
|
|
1870
|
+
return sections.length > 0 ? sections.join("\n") : null;
|
|
1906
1871
|
}
|
|
1907
|
-
function
|
|
1908
|
-
if (
|
|
1909
|
-
return
|
|
1872
|
+
function formatActiveMcpCatalogsForPrompt(catalogs) {
|
|
1873
|
+
if (catalogs.length === 0) {
|
|
1874
|
+
return null;
|
|
1910
1875
|
}
|
|
1911
|
-
const
|
|
1912
|
-
|
|
1913
|
-
|
|
1876
|
+
const lines = [
|
|
1877
|
+
"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`."
|
|
1878
|
+
];
|
|
1879
|
+
for (const catalog of catalogs) {
|
|
1880
|
+
lines.push(" <catalog>");
|
|
1881
|
+
lines.push(` <provider>${escapeXml(catalog.provider)}</provider>`);
|
|
1882
|
+
lines.push(
|
|
1883
|
+
` <available_tool_count>${catalog.available_tool_count}</available_tool_count>`
|
|
1884
|
+
);
|
|
1885
|
+
lines.push(" </catalog>");
|
|
1914
1886
|
}
|
|
1915
|
-
return
|
|
1887
|
+
return lines.join("\n");
|
|
1916
1888
|
}
|
|
1917
|
-
function
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1889
|
+
function formatToolGuidanceForPrompt(tools) {
|
|
1890
|
+
const guidedTools = tools.filter(
|
|
1891
|
+
(tool) => Boolean(tool.promptSnippet?.trim()) || (tool.promptGuidelines?.length ?? 0) > 0
|
|
1892
|
+
);
|
|
1893
|
+
if (guidedTools.length === 0) {
|
|
1894
|
+
return null;
|
|
1895
|
+
}
|
|
1896
|
+
const lines = [];
|
|
1897
|
+
for (const tool of guidedTools) {
|
|
1898
|
+
lines.push(` <tool name="${escapeXml(tool.name)}">`);
|
|
1899
|
+
if (tool.promptSnippet?.trim()) {
|
|
1900
|
+
lines.push(` - ${escapeXml(tool.promptSnippet.trim())}`);
|
|
1927
1901
|
}
|
|
1928
|
-
if (
|
|
1929
|
-
|
|
1902
|
+
if (tool.promptGuidelines && tool.promptGuidelines.length > 0) {
|
|
1903
|
+
for (const guideline of tool.promptGuidelines) {
|
|
1904
|
+
lines.push(` - ${escapeXml(guideline)}`);
|
|
1905
|
+
}
|
|
1930
1906
|
}
|
|
1931
|
-
|
|
1907
|
+
lines.push(" </tool>");
|
|
1932
1908
|
}
|
|
1933
|
-
return
|
|
1909
|
+
return lines.join("\n");
|
|
1934
1910
|
}
|
|
1935
|
-
function
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
providers.add(entry.provider);
|
|
1940
|
-
}
|
|
1911
|
+
function formatReferenceFilesLines() {
|
|
1912
|
+
const files = listReferenceFiles();
|
|
1913
|
+
if (files.length === 0) {
|
|
1914
|
+
return null;
|
|
1941
1915
|
}
|
|
1942
|
-
return
|
|
1916
|
+
return files.map((filePath) => {
|
|
1917
|
+
const name = path.basename(filePath);
|
|
1918
|
+
return `- ${escapeXml(name)} (${escapeXml(`${SANDBOX_DATA_ROOT}/${name}`)})`;
|
|
1919
|
+
});
|
|
1943
1920
|
}
|
|
1944
|
-
function
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1921
|
+
function formatArtifactsLines(artifactState) {
|
|
1922
|
+
if (!artifactState) return null;
|
|
1923
|
+
const lines = [];
|
|
1924
|
+
if (artifactState.lastCanvasId) {
|
|
1925
|
+
lines.push(`- last_canvas_id: ${escapeXml(artifactState.lastCanvasId)}`);
|
|
1948
1926
|
}
|
|
1949
|
-
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
if (
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
);
|
|
1927
|
+
if (artifactState.lastCanvasUrl) {
|
|
1928
|
+
lines.push(`- last_canvas_url: ${escapeXml(artifactState.lastCanvasUrl)}`);
|
|
1929
|
+
}
|
|
1930
|
+
if (artifactState.recentCanvases && artifactState.recentCanvases.length > 0) {
|
|
1931
|
+
lines.push("- recent_canvases:");
|
|
1932
|
+
for (const canvas of artifactState.recentCanvases) {
|
|
1933
|
+
lines.push(` - id: ${escapeXml(canvas.id)}`);
|
|
1934
|
+
if (canvas.title) lines.push(` title: ${escapeXml(canvas.title)}`);
|
|
1935
|
+
if (canvas.url) lines.push(` url: ${escapeXml(canvas.url)}`);
|
|
1936
|
+
if (canvas.createdAt) {
|
|
1937
|
+
lines.push(` created_at: ${escapeXml(canvas.createdAt)}`);
|
|
1961
1938
|
}
|
|
1962
|
-
await client.pExpire(listKey, Math.max(1, ttlMs));
|
|
1963
|
-
},
|
|
1964
|
-
async read(scope) {
|
|
1965
|
-
const values = await client.lRange(key(scope), 0, -1);
|
|
1966
|
-
return values.map(decode);
|
|
1967
1939
|
}
|
|
1968
|
-
}
|
|
1940
|
+
}
|
|
1941
|
+
if (artifactState.lastListId) {
|
|
1942
|
+
lines.push(`- last_list_id: ${escapeXml(artifactState.lastListId)}`);
|
|
1943
|
+
}
|
|
1944
|
+
if (artifactState.lastListUrl) {
|
|
1945
|
+
lines.push(`- last_list_url: ${escapeXml(artifactState.lastListUrl)}`);
|
|
1946
|
+
}
|
|
1947
|
+
return lines.length > 0 ? lines : null;
|
|
1969
1948
|
}
|
|
1970
|
-
function
|
|
1971
|
-
const
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
});
|
|
1979
|
-
}
|
|
1980
|
-
},
|
|
1981
|
-
async read(scope) {
|
|
1982
|
-
const values = await stateAdapter.getList(rawKey(scope));
|
|
1983
|
-
return values.map(decode);
|
|
1984
|
-
}
|
|
1985
|
-
};
|
|
1949
|
+
function formatConfigurationLines(configuration) {
|
|
1950
|
+
const keys = Object.keys(configuration ?? {}).sort(
|
|
1951
|
+
(a, b) => a.localeCompare(b)
|
|
1952
|
+
);
|
|
1953
|
+
if (keys.length === 0) return null;
|
|
1954
|
+
return keys.map(
|
|
1955
|
+
(key2) => `- ${escapeXml(key2)}: ${formatConfigurationValue(configuration?.[key2])}`
|
|
1956
|
+
);
|
|
1986
1957
|
}
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1958
|
+
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.";
|
|
1959
|
+
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.";
|
|
1960
|
+
var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
|
|
1961
|
+
var TOOL_POLICY_RULES = [
|
|
1962
|
+
"- 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.",
|
|
1963
|
+
"- 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.",
|
|
1964
|
+
"- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
|
|
1965
|
+
"- 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.",
|
|
1966
|
+
"- 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.",
|
|
1967
|
+
`- 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.`,
|
|
1968
|
+
"- 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.",
|
|
1969
|
+
"- For user-provided URLs, use `webFetch`; for discovery, use `webSearch` then fetch/read promising sources; for current time/date context, use `systemTime`.",
|
|
1970
|
+
"- 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."
|
|
1971
|
+
];
|
|
1972
|
+
var TOOL_CALL_STYLE_RULES = [
|
|
1973
|
+
"- For routine low-risk tool use, call the tool directly without narrating the obvious step first.",
|
|
1974
|
+
"- Briefly narrate only when it helps the user understand multi-step work, sensitive actions, destructive actions, or a notable change in approach.",
|
|
1975
|
+
"- 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.",
|
|
1976
|
+
"- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
|
|
1977
|
+
];
|
|
1978
|
+
var SKILL_POLICY_RULES = [
|
|
1979
|
+
"- Only load skills listed in `<available-skills>`, `<user-callable-skills>`, or named by `<explicit-skill-trigger>`. Never guess or invent a skill name.",
|
|
1980
|
+
"- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
|
|
1981
|
+
];
|
|
1982
|
+
var EXECUTION_CONTRACT_RULES = [
|
|
1983
|
+
"- Actionable request: act in this turn.",
|
|
1984
|
+
"- 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.",
|
|
1985
|
+
"- 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.",
|
|
1986
|
+
"- Ask the user only for missing access, approval, or a decision that blocks safe progress. Ask one focused question; otherwise infer conservatively and continue.",
|
|
1987
|
+
"- For conflicting evidence, compare sources and state which source is authoritative for the answer.",
|
|
1988
|
+
"- 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."
|
|
1989
|
+
];
|
|
1990
|
+
var CONVERSATION_RULES = [
|
|
1991
|
+
"- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
|
|
1992
|
+
"- Preserve attribution roles from thread context: the requester is the person asking now, which may differ from the original reporter or subject.",
|
|
1993
|
+
"- Runtime owns continuation and authorization notices; on resumed turns, answer with the final requested content only."
|
|
1994
|
+
];
|
|
1995
|
+
var SLACK_ACTION_RULES = [
|
|
1996
|
+
"- Context-bound Slack tools use runtime-owned targets; do not invent channel, canvas, list, or message IDs.",
|
|
1997
|
+
"- Use first-class Slack tools for Slack side effects; do not use bash, curl, or provider APIs to bypass Slack tool targeting.",
|
|
1998
|
+
"- Use channel-post and emoji-reaction tools only when the user explicitly asks for that Slack side effect.",
|
|
1999
|
+
"- For explicit channel-post or emoji-reaction requests, skip a duplicate thread text reply when the tool result already satisfies the request.",
|
|
2000
|
+
"- 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.",
|
|
2001
|
+
"- Do not use reactions as progress indicators."
|
|
2002
|
+
];
|
|
2003
|
+
var SAFETY_RULES = [
|
|
2004
|
+
"- Stay within the user's request and the runtime's available capabilities; do not pursue independent goals, persistence, replication, credential gathering, or access expansion.",
|
|
2005
|
+
"- Respect stop, pause, audit, and approval boundaries. Do not bypass safeguards or persuade the user to weaken them.",
|
|
2006
|
+
"- 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."
|
|
2007
|
+
];
|
|
2008
|
+
var FAILURE_RULES = [
|
|
2009
|
+
"- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
|
|
2010
|
+
"- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
|
|
2011
|
+
"- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
|
|
2012
|
+
];
|
|
2013
|
+
function renderRuleSection(tag, lines) {
|
|
2014
|
+
return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
|
|
1994
2015
|
}
|
|
1995
|
-
|
|
1996
|
-
const
|
|
1997
|
-
|
|
2016
|
+
function buildBehaviorSection(platform) {
|
|
2017
|
+
const sections = [
|
|
2018
|
+
renderRuleSection("tool-policy", TOOL_POLICY_RULES),
|
|
2019
|
+
renderRuleSection("tool-call-style", TOOL_CALL_STYLE_RULES),
|
|
2020
|
+
renderRuleSection("skill-policy", SKILL_POLICY_RULES),
|
|
2021
|
+
renderRuleSection("execution-contract", EXECUTION_CONTRACT_RULES),
|
|
2022
|
+
renderRuleSection("conversation", CONVERSATION_RULES),
|
|
2023
|
+
renderRuleSection("safety", SAFETY_RULES),
|
|
2024
|
+
renderRuleSection("failure-handling", FAILURE_RULES)
|
|
2025
|
+
];
|
|
2026
|
+
if (platform === "slack") {
|
|
2027
|
+
sections.splice(
|
|
2028
|
+
5,
|
|
2029
|
+
0,
|
|
2030
|
+
renderRuleSection("slack-actions", SLACK_ACTION_RULES)
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
return sections.join("\n\n");
|
|
1998
2034
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2035
|
+
function buildOutputSection(platform) {
|
|
2036
|
+
if (platform === "local") {
|
|
2037
|
+
return [
|
|
2038
|
+
`<output format="markdown">`,
|
|
2039
|
+
"- Start with the answer or result, not internal process narration.",
|
|
2040
|
+
"- Use concise Markdown suitable for terminal output: short paragraphs, bullets, links, and fenced code blocks when helpful.",
|
|
2041
|
+
"- End every turn with a final user-facing response.",
|
|
2042
|
+
"</output>"
|
|
2043
|
+
].join("\n");
|
|
2003
2044
|
}
|
|
2004
|
-
const
|
|
2005
|
-
|
|
2006
|
-
|
|
2045
|
+
const openTag = `<output format="slack-markdown" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
|
|
2046
|
+
return [
|
|
2047
|
+
openTag,
|
|
2048
|
+
"- Start with the answer or result, not internal process narration.",
|
|
2049
|
+
"- 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.",
|
|
2050
|
+
"- Keep replies brief and scannable; use bullets or short code blocks when helpful, and one compact thread reply when it fits.",
|
|
2051
|
+
"- 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.",
|
|
2052
|
+
"- Unless a successful Slack side-effect tool intentionally satisfied the request by itself, end every turn with a final user-facing markdown response.",
|
|
2053
|
+
"</output>"
|
|
2054
|
+
].join("\n");
|
|
2007
2055
|
}
|
|
2008
|
-
|
|
2009
|
-
const
|
|
2010
|
-
return
|
|
2056
|
+
function buildIdentitySection(platform) {
|
|
2057
|
+
const name = platform === "slack" ? `Your Slack username is \`${botConfig.userName}\`.` : `Your assistant name is \`${botConfig.userName}\`.`;
|
|
2058
|
+
return ["# Identity", name].join("\n");
|
|
2011
2059
|
}
|
|
2012
|
-
|
|
2013
|
-
return
|
|
2060
|
+
function buildPersonalitySection() {
|
|
2061
|
+
return ["# Personality", JUNIOR_PERSONALITY.trim()].join("\n");
|
|
2014
2062
|
}
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2063
|
+
function buildWorldSection() {
|
|
2064
|
+
if (!JUNIOR_WORLD) {
|
|
2065
|
+
return null;
|
|
2066
|
+
}
|
|
2067
|
+
return ["# World", JUNIOR_WORLD.trim()].join("\n");
|
|
2068
|
+
}
|
|
2069
|
+
function buildRuntimeSection(params) {
|
|
2070
|
+
const lines = [
|
|
2071
|
+
params.conversationId ? `- gen_ai.conversation.id: ${escapeXml(params.conversationId)}` : "",
|
|
2072
|
+
params.slackConversation?.type ? `- slack.conversation.type: ${escapeXml(params.slackConversation.type)}` : "",
|
|
2073
|
+
params.slackConversation?.name ? `- slack.conversation.name: ${escapeXml(params.slackConversation.name)}` : ""
|
|
2074
|
+
].filter(Boolean);
|
|
2075
|
+
if (lines.length === 0) {
|
|
2076
|
+
return null;
|
|
2077
|
+
}
|
|
2078
|
+
return renderTagBlock("runtime", lines.join("\n"));
|
|
2079
|
+
}
|
|
2080
|
+
function buildContextSection(params) {
|
|
2081
|
+
const blocks = [];
|
|
2082
|
+
const referenceLines = formatReferenceFilesLines();
|
|
2083
|
+
if (referenceLines) {
|
|
2084
|
+
blocks.push(
|
|
2085
|
+
renderTag("reference-files", [
|
|
2086
|
+
"Additional reference documents available in the sandbox. Read them with `readFile` when relevant.",
|
|
2087
|
+
...referenceLines
|
|
2088
|
+
])
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
const requesterLines = renderRequesterBlock({
|
|
2092
|
+
full_name: params.requester?.fullName,
|
|
2093
|
+
user_name: params.requester?.userName,
|
|
2094
|
+
user_id: params.requester?.userId
|
|
2095
|
+
});
|
|
2096
|
+
if (requesterLines) {
|
|
2097
|
+
blocks.push(requesterLines);
|
|
2098
|
+
}
|
|
2099
|
+
const artifactLines = formatArtifactsLines(params.artifactState);
|
|
2100
|
+
if (artifactLines) {
|
|
2101
|
+
blocks.push(renderTag("artifacts", artifactLines));
|
|
2102
|
+
}
|
|
2103
|
+
const configLines = formatConfigurationLines(params.configuration);
|
|
2104
|
+
if (configLines) {
|
|
2105
|
+
blocks.push(
|
|
2106
|
+
renderTag("configuration", [
|
|
2107
|
+
"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.",
|
|
2108
|
+
...configLines
|
|
2109
|
+
])
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
if (params.invocation) {
|
|
2113
|
+
blocks.push(
|
|
2114
|
+
renderTag("explicit-skill-trigger", [
|
|
2115
|
+
"Treat this skill as selected. Load it unless the tool says it is unavailable.",
|
|
2116
|
+
`/${escapeXml(params.invocation.skillName)}`
|
|
2117
|
+
])
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
const body = blocks.map((block) => block.join("\n")).join("\n\n");
|
|
2121
|
+
if (!body) {
|
|
2122
|
+
return null;
|
|
2021
2123
|
}
|
|
2022
|
-
|
|
2023
|
-
scope: args,
|
|
2024
|
-
entries: [mcpProviderConnectedEntry(args.provider, sessionId)],
|
|
2025
|
-
ttlMs: args.ttlMs
|
|
2026
|
-
});
|
|
2124
|
+
return renderTagBlock("context", body);
|
|
2027
2125
|
}
|
|
2028
|
-
|
|
2029
|
-
const
|
|
2030
|
-
const
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
)
|
|
2035
|
-
|
|
2126
|
+
function buildCapabilitiesSection(params) {
|
|
2127
|
+
const blocks = [];
|
|
2128
|
+
const availableSkills = formatAvailableSkillsForPrompt(
|
|
2129
|
+
params.availableSkills,
|
|
2130
|
+
params.invocation
|
|
2131
|
+
);
|
|
2132
|
+
if (availableSkills) {
|
|
2133
|
+
blocks.push(availableSkills);
|
|
2036
2134
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
kind: args.kind,
|
|
2043
|
-
sessionId,
|
|
2044
|
-
provider: args.provider,
|
|
2045
|
-
requesterId: args.requesterId,
|
|
2046
|
-
authorizationId: args.authorizationId,
|
|
2047
|
-
delivery: args.delivery
|
|
2048
|
-
})
|
|
2049
|
-
],
|
|
2050
|
-
ttlMs: args.ttlMs
|
|
2051
|
-
});
|
|
2052
|
-
}
|
|
2053
|
-
async function recordAuthorizationCompleted(args) {
|
|
2054
|
-
const store = args.store ?? await defaultStore();
|
|
2055
|
-
const entries = await store.read(args);
|
|
2056
|
-
const sessionId = currentSessionId(entries);
|
|
2057
|
-
if (projectionEntries(entries).some(
|
|
2058
|
-
(entry) => entry.type === "authorization_completed" && entry.authorizationId === args.authorizationId
|
|
2059
|
-
)) {
|
|
2060
|
-
return;
|
|
2135
|
+
const activeCatalogs = formatActiveMcpCatalogsForPrompt(
|
|
2136
|
+
params.activeMcpCatalogs
|
|
2137
|
+
);
|
|
2138
|
+
if (activeCatalogs) {
|
|
2139
|
+
blocks.push(renderTagBlock("active-mcp-catalogs", activeCatalogs));
|
|
2061
2140
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
requesterId: args.requesterId,
|
|
2071
|
-
authorizationId: args.authorizationId
|
|
2072
|
-
})
|
|
2073
|
-
],
|
|
2074
|
-
ttlMs: args.ttlMs
|
|
2075
|
-
});
|
|
2141
|
+
const toolGuidance = formatToolGuidanceForPrompt(params.toolGuidance ?? []);
|
|
2142
|
+
if (toolGuidance) {
|
|
2143
|
+
blocks.push(renderTagBlock("tool-guidance", toolGuidance));
|
|
2144
|
+
}
|
|
2145
|
+
if (blocks.length === 0) {
|
|
2146
|
+
return null;
|
|
2147
|
+
}
|
|
2148
|
+
return blocks.join("\n\n");
|
|
2076
2149
|
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2150
|
+
function buildStaticSystemPrompt(platform) {
|
|
2151
|
+
return [
|
|
2152
|
+
platform === "slack" ? SLACK_HEADER : LOCAL_HEADER,
|
|
2153
|
+
buildIdentitySection(platform),
|
|
2154
|
+
buildPersonalitySection(),
|
|
2155
|
+
buildWorldSection(),
|
|
2156
|
+
buildBehaviorSection(platform),
|
|
2157
|
+
buildOutputSection(platform)
|
|
2158
|
+
].filter((section) => Boolean(section)).join("\n\n");
|
|
2159
|
+
}
|
|
2160
|
+
var STATIC_SYSTEM_PROMPTS = {
|
|
2161
|
+
local: buildStaticSystemPrompt("local"),
|
|
2162
|
+
slack: buildStaticSystemPrompt("slack")
|
|
2163
|
+
};
|
|
2164
|
+
function buildSystemPrompt(params) {
|
|
2165
|
+
return STATIC_SYSTEM_PROMPTS[params.source.platform];
|
|
2166
|
+
}
|
|
2167
|
+
function buildTurnContextPrompt(params) {
|
|
2168
|
+
const includeSessionContext = params.includeSessionContext ?? true;
|
|
2169
|
+
if (!includeSessionContext) {
|
|
2170
|
+
return null;
|
|
2171
|
+
}
|
|
2172
|
+
const runtimeSections = [
|
|
2173
|
+
buildCapabilitiesSection({
|
|
2174
|
+
availableSkills: params.availableSkills,
|
|
2175
|
+
activeMcpCatalogs: params.activeMcpCatalogs ?? [],
|
|
2176
|
+
invocation: params.invocation,
|
|
2177
|
+
toolGuidance: params.toolGuidance ?? []
|
|
2178
|
+
}),
|
|
2179
|
+
buildContextSection({
|
|
2180
|
+
requester: params.requester,
|
|
2181
|
+
artifactState: params.artifactState,
|
|
2182
|
+
configuration: params.configuration,
|
|
2183
|
+
invocation: params.invocation
|
|
2184
|
+
}),
|
|
2185
|
+
buildRuntimeSection(params.runtime ?? {})
|
|
2186
|
+
].filter((section) => Boolean(section));
|
|
2187
|
+
if (runtimeSections.length === 0) {
|
|
2188
|
+
return null;
|
|
2189
|
+
}
|
|
2190
|
+
const sections = [
|
|
2191
|
+
`<${TURN_CONTEXT_TAG}>`,
|
|
2192
|
+
TURN_CONTEXT_HEADER,
|
|
2193
|
+
"The current user instruction appears after this block in the same message.",
|
|
2194
|
+
...runtimeSections,
|
|
2195
|
+
`</${TURN_CONTEXT_TAG}>`
|
|
2196
|
+
].filter((section) => Boolean(section));
|
|
2197
|
+
return sections.join("\n\n");
|
|
2096
2198
|
}
|
|
2097
2199
|
|
|
2098
2200
|
// src/chat/state/turn-session.ts
|
|
2201
|
+
import { THREAD_STATE_TTL_MS } from "chat";
|
|
2099
2202
|
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
2100
2203
|
var AGENT_TURN_SESSION_INDEX_KEY = `${AGENT_TURN_SESSION_PREFIX}:index`;
|
|
2101
2204
|
var AGENT_TURN_SESSION_INDEX_MAX_LENGTH = 5e3;
|
|
@@ -2109,6 +2212,9 @@ function agentTurnSessionConversationIndexKey(conversationId) {
|
|
|
2109
2212
|
function toFiniteNonNegativeNumber(value) {
|
|
2110
2213
|
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
2111
2214
|
}
|
|
2215
|
+
function toNonNegativeInteger(value) {
|
|
2216
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
2217
|
+
}
|
|
2112
2218
|
function parseAgentTurnUsage(value) {
|
|
2113
2219
|
if (!isRecord(value)) {
|
|
2114
2220
|
return void 0;
|
|
@@ -2128,23 +2234,6 @@ function parseAgentTurnUsage(value) {
|
|
|
2128
2234
|
}
|
|
2129
2235
|
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
2130
2236
|
}
|
|
2131
|
-
function parseAgentTurnRequester(value) {
|
|
2132
|
-
if (!isRecord(value)) {
|
|
2133
|
-
return void 0;
|
|
2134
|
-
}
|
|
2135
|
-
const requester = {};
|
|
2136
|
-
for (const field of [
|
|
2137
|
-
"email",
|
|
2138
|
-
"fullName",
|
|
2139
|
-
"slackUserId",
|
|
2140
|
-
"slackUserName"
|
|
2141
|
-
]) {
|
|
2142
|
-
if (typeof value[field] === "string" && value[field].trim()) {
|
|
2143
|
-
requester[field] = value[field].trim();
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
return Object.keys(requester).length > 0 ? requester : void 0;
|
|
2147
|
-
}
|
|
2148
2237
|
function parseStoredRecord(value) {
|
|
2149
2238
|
if (isRecord(value)) {
|
|
2150
2239
|
return value;
|
|
@@ -2184,9 +2273,12 @@ function parseAgentTurnSessionFields(parsed) {
|
|
|
2184
2273
|
const cumulativeUsage = parseAgentTurnUsage(parsed.cumulativeUsage);
|
|
2185
2274
|
const lastProgressAtMs = toFiniteNonNegativeNumber(parsed.lastProgressAtMs);
|
|
2186
2275
|
const logSessionId = typeof parsed.logSessionId === "string" ? parsed.logSessionId : void 0;
|
|
2187
|
-
const requester =
|
|
2276
|
+
const requester = parseStoredSlackRequester(parsed.requester);
|
|
2188
2277
|
const startedAtMs = toFiniteNonNegativeNumber(parsed.startedAtMs);
|
|
2189
2278
|
const surface = parseAgentTurnSurface(parsed.surface);
|
|
2279
|
+
const turnStartMessageIndex = toNonNegativeInteger(
|
|
2280
|
+
parsed.turnStartMessageIndex
|
|
2281
|
+
);
|
|
2190
2282
|
const destination = parsed.destination === void 0 ? void 0 : parseDestination(parsed.destination);
|
|
2191
2283
|
if (typeof conversationId !== "string" || typeof sessionId !== "string" || sliceId === void 0 || version === void 0 || updatedAtMs === void 0 || parsed.destination !== void 0 && !destination) {
|
|
2192
2284
|
return void 0;
|
|
@@ -2215,6 +2307,7 @@ function parseAgentTurnSessionFields(parsed) {
|
|
|
2215
2307
|
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
2216
2308
|
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {},
|
|
2217
2309
|
...surface ? { surface } : {},
|
|
2310
|
+
...turnStartMessageIndex !== void 0 ? { turnStartMessageIndex } : {},
|
|
2218
2311
|
...typeof parsed.traceId === "string" ? { traceId: parsed.traceId } : {}
|
|
2219
2312
|
};
|
|
2220
2313
|
}
|
|
@@ -2247,6 +2340,7 @@ function parseAgentTurnSessionSummary(value) {
|
|
|
2247
2340
|
const {
|
|
2248
2341
|
errorMessage: _errorMessage,
|
|
2249
2342
|
logSessionId: _logSessionId,
|
|
2343
|
+
turnStartMessageIndex: _turnStartMessageIndex,
|
|
2250
2344
|
...summary
|
|
2251
2345
|
} = parsed;
|
|
2252
2346
|
return summary;
|
|
@@ -2265,6 +2359,27 @@ async function appendAgentTurnSessionSummary(summary, ttlMs) {
|
|
|
2265
2359
|
)
|
|
2266
2360
|
]);
|
|
2267
2361
|
}
|
|
2362
|
+
async function recordConversationActivityBestEffort(args) {
|
|
2363
|
+
try {
|
|
2364
|
+
await recordConversationActivity({
|
|
2365
|
+
activityAtMs: args.summary.updatedAtMs,
|
|
2366
|
+
channelName: args.summary.channelName,
|
|
2367
|
+
conversationId: args.summary.conversationId,
|
|
2368
|
+
destination: args.summary.destination,
|
|
2369
|
+
nowMs: args.nowMs,
|
|
2370
|
+
requester: args.summary.requester,
|
|
2371
|
+
source: args.summary.surface
|
|
2372
|
+
});
|
|
2373
|
+
} catch (error) {
|
|
2374
|
+
logException(
|
|
2375
|
+
error,
|
|
2376
|
+
"conversation_activity_record_failed",
|
|
2377
|
+
{ conversationId: args.summary.conversationId },
|
|
2378
|
+
{},
|
|
2379
|
+
"Failed to mirror turn session summary into conversation activity"
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2268
2383
|
function materializePiMessages(committedMessageCount, includeProjectionTail, sessionMessages, sessionProjection) {
|
|
2269
2384
|
if (committedMessageCount === 0) {
|
|
2270
2385
|
return sessionProjection;
|
|
@@ -2301,7 +2416,8 @@ function materializeAgentTurnSessionRecord(stored, piMessages) {
|
|
|
2301
2416
|
...stored.requester ? { requester: stored.requester } : {},
|
|
2302
2417
|
...stored.resumedFromSliceId !== void 0 ? { resumedFromSliceId: stored.resumedFromSliceId } : {},
|
|
2303
2418
|
...stored.surface ? { surface: stored.surface } : {},
|
|
2304
|
-
...stored.traceId ? { traceId: stored.traceId } : {}
|
|
2419
|
+
...stored.traceId ? { traceId: stored.traceId } : {},
|
|
2420
|
+
...stored.turnStartMessageIndex !== void 0 ? { turnStartMessageIndex: stored.turnStartMessageIndex } : {}
|
|
2305
2421
|
};
|
|
2306
2422
|
}
|
|
2307
2423
|
async function getStoredAgentTurnSessionRecord(conversationId, sessionId) {
|
|
@@ -2367,7 +2483,8 @@ function buildStoredRecord(args) {
|
|
|
2367
2483
|
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
2368
2484
|
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {},
|
|
2369
2485
|
...args.surface ? { surface: args.surface } : {},
|
|
2370
|
-
...args.traceId ? { traceId: args.traceId } : {}
|
|
2486
|
+
...args.traceId ? { traceId: args.traceId } : {},
|
|
2487
|
+
...args.turnStartMessageIndex !== void 0 ? { turnStartMessageIndex: args.turnStartMessageIndex } : {}
|
|
2371
2488
|
};
|
|
2372
2489
|
}
|
|
2373
2490
|
async function setStoredRecord(args) {
|
|
@@ -2382,6 +2499,7 @@ async function setStoredRecord(args) {
|
|
|
2382
2499
|
committedMessageCount: _committedMessageCount,
|
|
2383
2500
|
errorMessage: _errorMessage,
|
|
2384
2501
|
logSessionId: _logSessionId,
|
|
2502
|
+
turnStartMessageIndex: _turnStartMessageIndex,
|
|
2385
2503
|
...summary
|
|
2386
2504
|
} = args.record;
|
|
2387
2505
|
await appendAgentTurnSessionSummary(summary, args.ttlMs);
|
|
@@ -2418,6 +2536,7 @@ async function updateAgentTurnSessionState(args) {
|
|
|
2418
2536
|
...args.existing.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.existing.resumedFromSliceId } : {},
|
|
2419
2537
|
...args.existing.surface ? { surface: args.existing.surface } : {},
|
|
2420
2538
|
...args.existing.traceId ? { traceId: args.existing.traceId } : {},
|
|
2539
|
+
...args.existing.turnStartMessageIndex !== void 0 ? { turnStartMessageIndex: args.existing.turnStartMessageIndex } : {},
|
|
2421
2540
|
...args.errorMessage ?? args.existing.errorMessage ? { errorMessage: args.errorMessage ?? args.existing.errorMessage } : {}
|
|
2422
2541
|
})
|
|
2423
2542
|
});
|
|
@@ -2431,6 +2550,7 @@ async function upsertAgentTurnSessionRecord(args) {
|
|
|
2431
2550
|
const commit = await commitMessages({
|
|
2432
2551
|
conversationId: args.conversationId,
|
|
2433
2552
|
messages: args.piMessages,
|
|
2553
|
+
requester: args.requester ?? existingRecord?.requester,
|
|
2434
2554
|
ttlMs
|
|
2435
2555
|
});
|
|
2436
2556
|
return await setStoredRecord({
|
|
@@ -2456,7 +2576,10 @@ async function upsertAgentTurnSessionRecord(args) {
|
|
|
2456
2576
|
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
2457
2577
|
...args.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.resumedFromSliceId } : {},
|
|
2458
2578
|
...args.surface ?? existingRecord?.surface ? { surface: args.surface ?? existingRecord?.surface } : {},
|
|
2459
|
-
...args.traceId ?? existingRecord?.traceId ? { traceId: args.traceId ?? existingRecord?.traceId } : {}
|
|
2579
|
+
...args.traceId ?? existingRecord?.traceId ? { traceId: args.traceId ?? existingRecord?.traceId } : {},
|
|
2580
|
+
...(args.turnStartMessageIndex ?? existingRecord?.turnStartMessageIndex) !== void 0 ? {
|
|
2581
|
+
turnStartMessageIndex: args.turnStartMessageIndex ?? existingRecord?.turnStartMessageIndex
|
|
2582
|
+
} : {}
|
|
2460
2583
|
})
|
|
2461
2584
|
});
|
|
2462
2585
|
}
|
|
@@ -2467,32 +2590,34 @@ async function recordAgentTurnSessionSummary(args) {
|
|
|
2467
2590
|
);
|
|
2468
2591
|
const nowMs = Date.now();
|
|
2469
2592
|
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2593
|
+
const summary = {
|
|
2594
|
+
version: existing?.version ?? 0,
|
|
2595
|
+
...args.channelName ?? existing?.channelName ? { channelName: args.channelName ?? existing?.channelName } : {},
|
|
2596
|
+
conversationId: args.conversationId,
|
|
2597
|
+
sessionId: args.sessionId,
|
|
2598
|
+
sliceId: args.sliceId,
|
|
2599
|
+
startedAtMs: existing?.startedAtMs ?? args.startedAtMs ?? nowMs,
|
|
2600
|
+
lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
|
|
2601
|
+
state: args.state,
|
|
2602
|
+
updatedAtMs: nowMs,
|
|
2603
|
+
cumulativeDurationMs: toFiniteNonNegativeNumber(args.cumulativeDurationMs) ?? existing?.cumulativeDurationMs ?? 0,
|
|
2604
|
+
...args.cumulativeUsage ?? existing?.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage ?? existing?.cumulativeUsage } : {},
|
|
2605
|
+
...args.destination ?? existing?.destination ? { destination: args.destination ?? existing?.destination } : {},
|
|
2606
|
+
...args.requester ?? existing?.requester ? { requester: args.requester ?? existing?.requester } : {},
|
|
2607
|
+
...Array.isArray(args.loadedSkillNames) ? {
|
|
2608
|
+
loadedSkillNames: args.loadedSkillNames.filter(
|
|
2609
|
+
(value) => typeof value === "string"
|
|
2610
|
+
)
|
|
2611
|
+
} : existing?.loadedSkillNames ? { loadedSkillNames: existing.loadedSkillNames } : {},
|
|
2612
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
2613
|
+
...args.surface ?? existing?.surface ? { surface: args.surface ?? existing?.surface } : {},
|
|
2614
|
+
...args.traceId ?? existing?.traceId ? { traceId: args.traceId ?? existing?.traceId } : {}
|
|
2615
|
+
};
|
|
2616
|
+
await appendAgentTurnSessionSummary(summary, ttlMs);
|
|
2617
|
+
await recordConversationActivityBestEffort({
|
|
2618
|
+
nowMs,
|
|
2619
|
+
summary
|
|
2620
|
+
});
|
|
2496
2621
|
}
|
|
2497
2622
|
async function readAgentTurnSessionSummariesFromIndex(key2) {
|
|
2498
2623
|
const stateAdapter = getStateAdapter();
|
|
@@ -2513,9 +2638,6 @@ async function readAgentTurnSessionSummariesFromIndex(key2) {
|
|
|
2513
2638
|
(left, right) => right.updatedAtMs - left.updatedAtMs
|
|
2514
2639
|
);
|
|
2515
2640
|
}
|
|
2516
|
-
async function listAgentTurnSessionSummaries(limit = 50) {
|
|
2517
|
-
return (await readAgentTurnSessionSummariesFromIndex(AGENT_TURN_SESSION_INDEX_KEY)).slice(0, Math.max(0, Math.floor(limit)));
|
|
2518
|
-
}
|
|
2519
2641
|
async function listAgentTurnSessionSummariesForConversation(conversationId) {
|
|
2520
2642
|
const summaries = await readAgentTurnSessionSummariesFromIndex(
|
|
2521
2643
|
agentTurnSessionConversationIndexKey(conversationId)
|
|
@@ -2554,249 +2676,19 @@ async function failAgentTurnSessionRecord(args) {
|
|
|
2554
2676
|
});
|
|
2555
2677
|
}
|
|
2556
2678
|
|
|
2557
|
-
// src/chat/sentry-links.ts
|
|
2558
|
-
function getSentryOrgSlug() {
|
|
2559
|
-
const slug = process.env.SENTRY_ORG_SLUG?.trim();
|
|
2560
|
-
return slug || void 0;
|
|
2561
|
-
}
|
|
2562
|
-
function isSentrySaasDsnHost(host) {
|
|
2563
|
-
return host === "sentry.io" || host.endsWith(".sentry.io");
|
|
2564
|
-
}
|
|
2565
|
-
function buildSentryWebBaseUrl(dsn) {
|
|
2566
|
-
if (isSentrySaasDsnHost(dsn.host)) {
|
|
2567
|
-
return "https://sentry.io";
|
|
2568
|
-
}
|
|
2569
|
-
const port = dsn.port ? `:${dsn.port}` : "";
|
|
2570
|
-
const path2 = dsn.path ? `/${dsn.path}` : "";
|
|
2571
|
-
return `${dsn.protocol}://${dsn.host}${port}${path2}`;
|
|
2572
|
-
}
|
|
2573
|
-
function buildSentryConversationUrl(conversationId) {
|
|
2574
|
-
const client = sentry_exports.getClient();
|
|
2575
|
-
const dsn = client?.getDsn();
|
|
2576
|
-
if (!dsn?.host || !dsn.projectId) {
|
|
2577
|
-
return void 0;
|
|
2578
|
-
}
|
|
2579
|
-
const orgSlug = getSentryOrgSlug();
|
|
2580
|
-
if (!orgSlug) {
|
|
2581
|
-
return void 0;
|
|
2582
|
-
}
|
|
2583
|
-
const encodedId = encodeURIComponent(conversationId);
|
|
2584
|
-
const params = new URLSearchParams();
|
|
2585
|
-
params.set("project", dsn.projectId);
|
|
2586
|
-
const path2 = `explore/conversations/${encodedId}/?${params.toString()}`;
|
|
2587
|
-
if (isSentrySaasDsnHost(dsn.host)) {
|
|
2588
|
-
return `https://${orgSlug}.sentry.io/${path2}`;
|
|
2589
|
-
}
|
|
2590
|
-
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path2}`;
|
|
2591
|
-
}
|
|
2592
|
-
function buildSentryTraceUrl(traceId) {
|
|
2593
|
-
const client = sentry_exports.getClient();
|
|
2594
|
-
const dsn = client?.getDsn();
|
|
2595
|
-
if (!dsn?.host || !dsn.projectId) {
|
|
2596
|
-
return void 0;
|
|
2597
|
-
}
|
|
2598
|
-
const orgSlug = getSentryOrgSlug();
|
|
2599
|
-
if (!orgSlug) {
|
|
2600
|
-
return void 0;
|
|
2601
|
-
}
|
|
2602
|
-
const encodedTraceId = encodeURIComponent(traceId);
|
|
2603
|
-
const params = new URLSearchParams();
|
|
2604
|
-
params.set("project", dsn.projectId);
|
|
2605
|
-
const path2 = `performance/trace/${encodedTraceId}/?${params.toString()}`;
|
|
2606
|
-
if (isSentrySaasDsnHost(dsn.host)) {
|
|
2607
|
-
return `https://${orgSlug}.sentry.io/${path2}`;
|
|
2608
|
-
}
|
|
2609
|
-
return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path2}`;
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
// src/chat/slack/conversation-context.ts
|
|
2613
|
-
function normalizeConversationName(type, channelName) {
|
|
2614
|
-
const trimmed = channelName?.trim();
|
|
2615
|
-
if (!trimmed) return void 0;
|
|
2616
|
-
if (type === "public_channel" || type === "private_channel" || type === "private_channel_or_group_dm") {
|
|
2617
|
-
return trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
|
|
2618
|
-
}
|
|
2619
|
-
return trimmed;
|
|
2620
|
-
}
|
|
2621
|
-
function typeFromSlackChannelType(channelType) {
|
|
2622
|
-
if (channelType === "channel") return "public_channel";
|
|
2623
|
-
if (channelType === "group") return "private_channel";
|
|
2624
|
-
if (channelType === "mpim") return "group_dm";
|
|
2625
|
-
if (channelType === "im") return "direct_message";
|
|
2626
|
-
return void 0;
|
|
2627
|
-
}
|
|
2628
|
-
function typeFromChannelId(channelId, channelName) {
|
|
2629
|
-
const normalized = normalizeSlackConversationId(channelId);
|
|
2630
|
-
if (!normalized) return void 0;
|
|
2631
|
-
if (normalized.startsWith("C")) return "public_channel";
|
|
2632
|
-
if (normalized.startsWith("D")) return "direct_message";
|
|
2633
|
-
if (normalized.startsWith("G")) {
|
|
2634
|
-
return channelName?.trim().startsWith("mpdm-") ? "group_dm" : "private_channel_or_group_dm";
|
|
2635
|
-
}
|
|
2636
|
-
return void 0;
|
|
2637
|
-
}
|
|
2638
|
-
function toSlackEventChannelType(channelType) {
|
|
2639
|
-
if (channelType === "channel" || channelType === "group" || channelType === "mpim" || channelType === "im") {
|
|
2640
|
-
return channelType;
|
|
2641
|
-
}
|
|
2642
|
-
return void 0;
|
|
2643
|
-
}
|
|
2644
|
-
function resolveSlackChannelTypeFromMessage(message) {
|
|
2645
|
-
const raw = message.raw;
|
|
2646
|
-
if (!raw || typeof raw !== "object") {
|
|
2647
|
-
return void 0;
|
|
2648
|
-
}
|
|
2649
|
-
const channelType = raw.channel_type;
|
|
2650
|
-
return typeof channelType === "string" ? toSlackEventChannelType(channelType.trim()) : void 0;
|
|
2651
|
-
}
|
|
2652
|
-
function resolveSlackConversationContext(input) {
|
|
2653
|
-
const type = typeFromSlackChannelType(input.channelType) ?? typeFromChannelId(input.channelId, input.channelName);
|
|
2654
|
-
if (!type) return void 0;
|
|
2655
|
-
const name = normalizeConversationName(type, input.channelName);
|
|
2656
|
-
return {
|
|
2657
|
-
type,
|
|
2658
|
-
...name ? { name } : {}
|
|
2659
|
-
};
|
|
2660
|
-
}
|
|
2661
|
-
function resolveSlackConversationContextFromThreadId(input) {
|
|
2662
|
-
const slackThread = parseSlackThreadId(input.threadId);
|
|
2663
|
-
return resolveSlackConversationContext({
|
|
2664
|
-
channelId: slackThread?.channelId,
|
|
2665
|
-
channelName: input.channelName
|
|
2666
|
-
});
|
|
2667
|
-
}
|
|
2668
|
-
function formatSlackConversationTypeLabel(type) {
|
|
2669
|
-
if (type === "public_channel") return "Public Channel";
|
|
2670
|
-
if (type === "private_channel") return "Private Channel";
|
|
2671
|
-
if (type === "group_dm") return "Group DM";
|
|
2672
|
-
if (type === "direct_message") return "Direct Message";
|
|
2673
|
-
return "Private Channel or Group DM";
|
|
2674
|
-
}
|
|
2675
|
-
function formatSlackConversationRedactedLabel(context) {
|
|
2676
|
-
if (!context) return void 0;
|
|
2677
|
-
return formatSlackConversationTypeLabel(context.type);
|
|
2678
|
-
}
|
|
2679
|
-
|
|
2680
|
-
// src/chat/state/conversation-details.ts
|
|
2681
|
-
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
|
|
2682
|
-
var CONVERSATION_PREFIX = "junior:conversation";
|
|
2683
|
-
var CONVERSATION_DETAILS_TTL_MS = THREAD_STATE_TTL_MS2;
|
|
2684
|
-
function conversationContextKey(conversationId) {
|
|
2685
|
-
return `${CONVERSATION_PREFIX}:${conversationId}:context`;
|
|
2686
|
-
}
|
|
2687
|
-
function conversationTitleKey(conversationId) {
|
|
2688
|
-
return `${CONVERSATION_PREFIX}:${conversationId}:title`;
|
|
2689
|
-
}
|
|
2690
|
-
function parseAgentTurnRequester2(value) {
|
|
2691
|
-
if (!isRecord(value)) return void 0;
|
|
2692
|
-
const requester = {
|
|
2693
|
-
...typeof value.email === "string" ? { email: value.email } : {},
|
|
2694
|
-
...typeof value.fullName === "string" ? { fullName: value.fullName } : {},
|
|
2695
|
-
...typeof value.slackUserId === "string" ? { slackUserId: value.slackUserId } : {},
|
|
2696
|
-
...typeof value.slackUserName === "string" ? { slackUserName: value.slackUserName } : {}
|
|
2697
|
-
};
|
|
2698
|
-
return Object.keys(requester).length > 0 ? requester : void 0;
|
|
2699
|
-
}
|
|
2700
|
-
function parseOriginSurface(value) {
|
|
2701
|
-
if (value === "slack" || value === "api" || value === "scheduler" || value === "internal") {
|
|
2702
|
-
return value;
|
|
2703
|
-
}
|
|
2704
|
-
return void 0;
|
|
2705
|
-
}
|
|
2706
|
-
function storedContextFromInput(context) {
|
|
2707
|
-
return {
|
|
2708
|
-
...context.channelName ? { channelName: context.channelName } : {},
|
|
2709
|
-
...context.originSurface ? { originSurface: context.originSurface } : {},
|
|
2710
|
-
...context.originRequester ? { originRequester: context.originRequester } : {},
|
|
2711
|
-
startedAtMs: context.startedAtMs
|
|
2712
|
-
};
|
|
2713
|
-
}
|
|
2714
|
-
function parseContext(value) {
|
|
2715
|
-
if (!isRecord(value)) return void 0;
|
|
2716
|
-
const startedAtMs = toOptionalNumber(value.startedAtMs);
|
|
2717
|
-
if (startedAtMs === void 0) return void 0;
|
|
2718
|
-
return {
|
|
2719
|
-
...typeof value.channelName === "string" && value.channelName.trim() ? { channelName: value.channelName.trim() } : {},
|
|
2720
|
-
...parseOriginSurface(value.originSurface) ? { originSurface: parseOriginSurface(value.originSurface) } : {},
|
|
2721
|
-
...parseAgentTurnRequester2(value.originRequester) ? { originRequester: parseAgentTurnRequester2(value.originRequester) } : {},
|
|
2722
|
-
startedAtMs
|
|
2723
|
-
};
|
|
2724
|
-
}
|
|
2725
|
-
function parseTitle(value) {
|
|
2726
|
-
if (!isRecord(value)) return void 0;
|
|
2727
|
-
const displayTitle = typeof value.displayTitle === "string" && value.displayTitle.trim() ? value.displayTitle.trim() : void 0;
|
|
2728
|
-
if (!displayTitle) return void 0;
|
|
2729
|
-
return {
|
|
2730
|
-
displayTitle,
|
|
2731
|
-
...typeof value.titleSourceMessageId === "string" ? { titleSourceMessageId: value.titleSourceMessageId } : {}
|
|
2732
|
-
};
|
|
2733
|
-
}
|
|
2734
|
-
async function initConversationContext(conversationId, context) {
|
|
2735
|
-
const stateAdapter = getStateAdapter();
|
|
2736
|
-
await stateAdapter.connect();
|
|
2737
|
-
const key2 = conversationContextKey(conversationId);
|
|
2738
|
-
const inserted = await stateAdapter.setIfNotExists(
|
|
2739
|
-
key2,
|
|
2740
|
-
storedContextFromInput(context),
|
|
2741
|
-
CONVERSATION_DETAILS_TTL_MS
|
|
2742
|
-
);
|
|
2743
|
-
if (inserted) return;
|
|
2744
|
-
const existing = parseContext(await stateAdapter.get(key2));
|
|
2745
|
-
if (!existing) {
|
|
2746
|
-
return;
|
|
2747
|
-
}
|
|
2748
|
-
await stateAdapter.set(key2, existing, CONVERSATION_DETAILS_TTL_MS);
|
|
2749
|
-
}
|
|
2750
|
-
async function setConversationTitle(conversationId, title) {
|
|
2751
|
-
const stateAdapter = getStateAdapter();
|
|
2752
|
-
await stateAdapter.connect();
|
|
2753
|
-
await stateAdapter.set(
|
|
2754
|
-
conversationTitleKey(conversationId),
|
|
2755
|
-
{
|
|
2756
|
-
displayTitle: title.displayTitle,
|
|
2757
|
-
...title.titleSourceMessageId ? { titleSourceMessageId: title.titleSourceMessageId } : {}
|
|
2758
|
-
},
|
|
2759
|
-
CONVERSATION_DETAILS_TTL_MS
|
|
2760
|
-
);
|
|
2761
|
-
}
|
|
2762
|
-
async function getConversationDetails(conversationId) {
|
|
2763
|
-
const stateAdapter = getStateAdapter();
|
|
2764
|
-
await stateAdapter.connect();
|
|
2765
|
-
const [rawContext, rawTitle] = await Promise.all([
|
|
2766
|
-
stateAdapter.get(conversationContextKey(conversationId)),
|
|
2767
|
-
stateAdapter.get(conversationTitleKey(conversationId))
|
|
2768
|
-
]);
|
|
2769
|
-
const context = parseContext(rawContext);
|
|
2770
|
-
const title = parseTitle(rawTitle);
|
|
2771
|
-
if (!context && !title) return void 0;
|
|
2772
|
-
return {
|
|
2773
|
-
conversationId,
|
|
2774
|
-
...title?.displayTitle ? { displayTitle: title.displayTitle } : {},
|
|
2775
|
-
...title?.titleSourceMessageId ? { titleSourceMessageId: title.titleSourceMessageId } : {},
|
|
2776
|
-
...context?.channelName ? { channelName: context.channelName } : {},
|
|
2777
|
-
...context?.originSurface ? { originSurface: context.originSurface } : {},
|
|
2778
|
-
...context?.originRequester ? { originRequester: context.originRequester } : {},
|
|
2779
|
-
...context?.startedAtMs !== void 0 ? { startedAtMs: context.startedAtMs } : {}
|
|
2780
|
-
};
|
|
2781
|
-
}
|
|
2782
|
-
async function getConversationDetailsForIds(conversationIds) {
|
|
2783
|
-
const uniqueIds = [...new Set(conversationIds)].filter(Boolean);
|
|
2784
|
-
const entries = await Promise.all(
|
|
2785
|
-
uniqueIds.map(async (id) => {
|
|
2786
|
-
const details = await getConversationDetails(id);
|
|
2787
|
-
return details ? [id, details] : void 0;
|
|
2788
|
-
})
|
|
2789
|
-
);
|
|
2790
|
-
const result = /* @__PURE__ */ new Map();
|
|
2791
|
-
for (const entry of entries) {
|
|
2792
|
-
if (entry) result.set(entry[0], entry[1]);
|
|
2793
|
-
}
|
|
2794
|
-
return result;
|
|
2795
|
-
}
|
|
2796
|
-
|
|
2797
2679
|
export {
|
|
2680
|
+
getInterruptionMarker,
|
|
2681
|
+
truncateStatusText,
|
|
2682
|
+
normalizeSlackStatusText,
|
|
2683
|
+
splitSlackReplyText,
|
|
2684
|
+
buildSlackOutputMessage,
|
|
2685
|
+
escapeXml,
|
|
2686
|
+
JUNIOR_PERSONALITY,
|
|
2687
|
+
buildSystemPrompt,
|
|
2688
|
+
buildTurnContextPrompt,
|
|
2798
2689
|
createAgentPluginLogger,
|
|
2799
2690
|
createPluginState,
|
|
2691
|
+
getSlackToolContext,
|
|
2800
2692
|
bindSlackDirectCredentialSubject,
|
|
2801
2693
|
verifySlackDirectCredentialSubject,
|
|
2802
2694
|
resolveChannelCapabilities,
|
|
@@ -2808,16 +2700,6 @@ export {
|
|
|
2808
2700
|
getAgentPluginSlackConversationLink,
|
|
2809
2701
|
getAgentPluginOperationalReports,
|
|
2810
2702
|
createAgentPluginHookRunner,
|
|
2811
|
-
GET,
|
|
2812
|
-
getInterruptionMarker,
|
|
2813
|
-
truncateStatusText,
|
|
2814
|
-
normalizeSlackStatusText,
|
|
2815
|
-
splitSlackReplyText,
|
|
2816
|
-
buildSlackOutputMessage,
|
|
2817
|
-
escapeXml,
|
|
2818
|
-
JUNIOR_PERSONALITY,
|
|
2819
|
-
buildSystemPrompt,
|
|
2820
|
-
buildTurnContextPrompt,
|
|
2821
2703
|
loadProjection,
|
|
2822
2704
|
loadConnectedMcpProviders,
|
|
2823
2705
|
recordMcpProviderConnected,
|
|
@@ -2827,18 +2709,7 @@ export {
|
|
|
2827
2709
|
getAgentTurnSessionRecord,
|
|
2828
2710
|
upsertAgentTurnSessionRecord,
|
|
2829
2711
|
recordAgentTurnSessionSummary,
|
|
2830
|
-
listAgentTurnSessionSummaries,
|
|
2831
2712
|
listAgentTurnSessionSummariesForConversation,
|
|
2832
2713
|
abandonAgentTurnSessionRecord,
|
|
2833
|
-
failAgentTurnSessionRecord
|
|
2834
|
-
buildSentryConversationUrl,
|
|
2835
|
-
buildSentryTraceUrl,
|
|
2836
|
-
resolveSlackChannelTypeFromMessage,
|
|
2837
|
-
resolveSlackConversationContext,
|
|
2838
|
-
resolveSlackConversationContextFromThreadId,
|
|
2839
|
-
formatSlackConversationRedactedLabel,
|
|
2840
|
-
initConversationContext,
|
|
2841
|
-
setConversationTitle,
|
|
2842
|
-
getConversationDetails,
|
|
2843
|
-
getConversationDetailsForIds
|
|
2714
|
+
failAgentTurnSessionRecord
|
|
2844
2715
|
};
|