@sentry/junior 0.72.0 → 0.74.0

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