jeopi-cli 16.6.11 → 16.6.13

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.
@@ -8,14 +8,30 @@ export interface MessengerMessage {
8
8
  messageId: string;
9
9
  text: string;
10
10
  threadId?: string;
11
+ parentChannelId?: string;
11
12
  direct: boolean;
12
13
  mentioned: boolean;
14
+ attachments?: { url: string; filename: string; size: number }[];
13
15
  }
14
16
 
17
+ /** Report a short, human-readable description of the work in progress. */
18
+ export type MessengerActivityReporter = (text: string) => void;
19
+
15
20
  export interface MessengerAdapter {
16
21
  platform: MessengerPlatform;
17
22
  start(onMessage: (message: MessengerMessage) => Promise<void>, signal: AbortSignal): Promise<void>;
18
23
  send(message: MessengerMessage, text: string, signal?: AbortSignal): Promise<void>;
24
+ prepare?(message: MessengerMessage, signal?: AbortSignal): Promise<MessengerMessage>;
25
+ status?(
26
+ message: MessengerMessage,
27
+ status: "processing" | "success" | "failure",
28
+ signal?: AbortSignal,
29
+ ): Promise<void>;
30
+ /**
31
+ * Show `text` (one sanitized line) as the request's single in-progress status, editing it in place on
32
+ * later calls; `undefined` removes it. The gateway serializes and throttles calls per request.
33
+ */
34
+ activity?(message: MessengerMessage, text: string | undefined, signal?: AbortSignal): Promise<void>;
19
35
  }
20
36
 
21
37
  export interface MessengerPlatformConfig {
@@ -23,6 +39,8 @@ export interface MessengerPlatformConfig {
23
39
  appTokenEnv?: string;
24
40
  allowedUserIds: string[];
25
41
  allowedChannelIds?: string[];
42
+ /** Dedicated operator-approved directory for Discord output files. */
43
+ artifactDir?: string;
26
44
  }
27
45
 
28
46
  export interface MessengerConfig {
@@ -37,7 +55,11 @@ export interface MessengerTransportDeps {
37
55
  WebSocket?: typeof WebSocket;
38
56
  }
39
57
 
40
- export type MessengerReply = (message: MessengerMessage, signal: AbortSignal) => Promise<string>;
58
+ export type MessengerReply = (
59
+ message: MessengerMessage,
60
+ signal: AbortSignal,
61
+ onActivity?: MessengerActivityReporter,
62
+ ) => Promise<string>;
41
63
  /** A partially initialized SDK must not be retried inside a live gateway. */
42
64
  export class MessengerFatalError extends Error {
43
65
  constructor() {
@@ -903,7 +903,19 @@ export class InputController {
903
903
  try {
904
904
  await this.ctx.withLocalSubmission(
905
905
  text,
906
- () => this.ctx.session.prompt(text, { streamingBehavior: "steer", images }),
906
+ () => {
907
+ const submit = () => this.ctx.session.prompt(text, { streamingBehavior: "steer", images });
908
+ const bridge = this.ctx.liveDiscordBridge;
909
+ return bridge?.active
910
+ ? bridge.withLocalTurn(text, onPreparedUserMessage =>
911
+ this.ctx.session.prompt(text, {
912
+ streamingBehavior: "steer",
913
+ images,
914
+ onPreparedUserMessage,
915
+ }),
916
+ )
917
+ : submit();
918
+ },
907
919
  {
908
920
  imageCount: images?.length ?? 0,
909
921
  },
@@ -1229,9 +1241,19 @@ export class InputController {
1229
1241
  // Not streaming — just submit normally
1230
1242
  this.ctx.editor.clearDraft(text);
1231
1243
  try {
1232
- await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, { images }), {
1233
- imageCount: images?.length ?? 0,
1234
- });
1244
+ await this.ctx.withLocalSubmission(
1245
+ text,
1246
+ () => {
1247
+ const submit = () => this.ctx.session.prompt(text, { images });
1248
+ const bridge = this.ctx.liveDiscordBridge;
1249
+ return bridge?.active
1250
+ ? bridge.withLocalTurn(text, onPreparedUserMessage =>
1251
+ this.ctx.session.prompt(text, { images, onPreparedUserMessage }),
1252
+ )
1253
+ : submit();
1254
+ },
1255
+ { imageCount: images?.length ?? 0 },
1256
+ );
1235
1257
  } catch (error) {
1236
1258
  restoreOnError(error);
1237
1259
  }
@@ -0,0 +1,4 @@
1
+ To deliver a requested file, write it inside {{directory}} and put [artifact:relative/path] on its own line in your final response. Only files in that directory can be uploaded; each must be at most 8 MiB. Never include credentials or secrets in output files.
2
+ {{#if inputs}}
3
+ Incoming files: {{inputs}}
4
+ {{/if}}
@@ -97,6 +97,7 @@ import type {
97
97
  ToolChoice,
98
98
  Usage,
99
99
  UsageReport,
100
+ UserMessage,
100
101
  } from "jeopi-ai";
101
102
  import {
102
103
  calculateRateLimitBackoffMs,
@@ -944,6 +945,8 @@ export interface PromptOptions {
944
945
  userInitiated?: boolean;
945
946
  /** Explicit billing/initiator attribution for the prompt. Defaults to user prompts as `user` and synthetic prompts as `agent`. */
946
947
  attribution?: MessageAttribution;
948
+ /** Observe the exact user message prepared for this submission, including queued messages. */
949
+ onPreparedUserMessage?: (message: UserMessage) => void;
947
950
  /** Skip pre-send compaction checks for this prompt (internal use for maintenance flows). */
948
951
  skipCompactionCheck?: boolean;
949
952
  }
@@ -7539,9 +7542,9 @@ export class AgentSession {
7539
7542
  await this.sendCustomMessage(notice, { deliverAs: options.streamingBehavior });
7540
7543
  }
7541
7544
  if (options.streamingBehavior === "followUp") {
7542
- await this.#queueUserMessage(expandedText, options?.images, "followUp");
7545
+ await this.#queueUserMessage(expandedText, options?.images, "followUp", options?.onPreparedUserMessage);
7543
7546
  } else {
7544
- await this.#queueUserMessage(expandedText, options?.images, "steer");
7547
+ await this.#queueUserMessage(expandedText, options?.images, "steer", options?.onPreparedUserMessage);
7545
7548
  }
7546
7549
  return true;
7547
7550
  }
@@ -7568,6 +7571,7 @@ export class AgentSession {
7568
7571
  const message = options?.synthetic
7569
7572
  ? { role: "developer" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() }
7570
7573
  : { role: "user" as const, content: userContent, attribution: promptAttribution, timestamp: Date.now() };
7574
+ if (message.role === "user") options?.onPreparedUserMessage?.(message);
7571
7575
 
7572
7576
  const preludeMessages: AgentMessage[] = [];
7573
7577
  if (eagerTodoPrelude) {
@@ -8021,6 +8025,7 @@ export class AgentSession {
8021
8025
  text: string,
8022
8026
  images: ImageContent[] | undefined,
8023
8027
  mode: "steer" | "followUp",
8028
+ onPreparedUserMessage?: (message: UserMessage) => void,
8024
8029
  ): Promise<void> {
8025
8030
  // A queued user message (RPC/SDK/collab steer or follow-up, or a typed message
8026
8031
  // while streaming) is a deliberate resume; re-enable advisor auto-resume that
@@ -8038,21 +8043,20 @@ export class AgentSession {
8038
8043
  : undefined;
8039
8044
  if (mode === "followUp") {
8040
8045
  if (imageDescriptionNotice) this.agent.followUp(imageDescriptionNotice);
8041
- this.agent.followUp({
8042
- role: "user",
8043
- content,
8044
- attribution: "user",
8045
- timestamp: Date.now(),
8046
- });
8046
+ const message: UserMessage = { role: "user", content, attribution: "user", timestamp: Date.now() };
8047
+ onPreparedUserMessage?.(message);
8048
+ this.agent.followUp(message);
8047
8049
  } else {
8048
8050
  if (imageDescriptionNotice) this.agent.steer(imageDescriptionNotice);
8049
- this.agent.steer({
8051
+ const message: UserMessage = {
8050
8052
  role: "user",
8051
8053
  content,
8052
8054
  steering: true,
8053
8055
  attribution: "user",
8054
8056
  timestamp: Date.now(),
8055
- });
8057
+ };
8058
+ onPreparedUserMessage?.(message);
8059
+ this.agent.steer(message);
8056
8060
  }
8057
8061
  this.#scheduleIdleQueueDrain();
8058
8062
  }
@@ -661,9 +661,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
661
661
  {
662
662
  name: "discord",
663
663
  description: "Opt in to this session from an authorized Discord channel",
664
- inlineHint: "[live|stop|status] [configPath] [--allow-full-control]",
664
+ inlineHint: "[live|relay|stop|status] [configPath|channelId|off] [--allow-full-control]",
665
665
  subcommands: [
666
666
  { name: "live", description: "Start the live Discord bridge with a private config file" },
667
+ { name: "relay", description: "Relay local responses to an allowed channel, or turn relay off" },
667
668
  { name: "status", description: "Show live Discord bridge status" },
668
669
  { name: "stop", description: "Stop the live Discord bridge" },
669
670
  ],
@@ -682,10 +683,32 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
682
683
  const approvalMode = String(ctx.session.settings.get("tools.approvalMode"));
683
684
  const activeTools = ctx.session.getActiveToolNames();
684
685
  ctx.showStatus(
685
- `Discord live bridge is active with full host control acknowledged. Approval mode: ${approvalMode}; active tools: ${activeTools.length > 0 ? activeTools.join(", ") : "none"}.`,
686
+ `Discord live bridge is active with full host control acknowledged. Approval mode: ${approvalMode}; active tools: ${activeTools.length > 0 ? activeTools.join(", ") : "none"}. Local response relay: ${ctx.liveDiscordBridge.relayDestination ?? "off"}.`,
686
687
  );
687
688
  return;
688
689
  }
690
+ if (verb === "relay") {
691
+ const bridge = ctx.liveDiscordBridge;
692
+ if (!bridge?.active) {
693
+ ctx.showError("Start the Discord live bridge before enabling relay");
694
+ return;
695
+ }
696
+ if (!rest || /\s/.test(rest)) {
697
+ ctx.showError("Usage: /discord relay <allowed-channel-id|off>");
698
+ return;
699
+ }
700
+ try {
701
+ bridge.setRelayDestination(rest === "off" ? undefined : rest);
702
+ ctx.showStatus(
703
+ rest === "off"
704
+ ? "Discord local response relay is off"
705
+ : `Discord local responses will be sent to channel ${rest}; /discord relay off revokes relay.`,
706
+ );
707
+ } catch (error) {
708
+ ctx.showError(`Failed to configure Discord relay: ${errorMessage(error)}`);
709
+ }
710
+ return;
711
+ }
689
712
  if (verb === "stop") {
690
713
  const bridge = ctx.liveDiscordBridge;
691
714
  const start = liveDiscordStarts.get(ctx);