@geminixiang/mikan 1.0.0-beta.65 → 1.0.0-beta.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,21 @@ any release.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.0.0-beta.67]
13
+
14
+ ### Changed
15
+
16
+ - Slack: unaddressed thread replies in shared channels now pass through the same `/pi-auto-reply` gate as top-level messages instead of being dropped unconditionally. In `jev` mode, Jev scores the message together with its surrounding scope (the channel's recent top-level messages, or the thread's messages plus whether mikan has already replied there), so bare follow-ups like "then roll it back" are judged in context. `off` still requires a mention; `on` now admits thread replies too.
17
+ - Deploy docs: `/pi-auto-reply` slash command description lists `on | off | jev`.
18
+
19
+ ## [1.0.0-beta.66]
20
+
21
+ ### Added
22
+
23
+ - `/pi-auto-reply` gains a third state, `jev`: alongside manual `on`/`off`, a Slack channel can now delegate the "does this unaddressed message address mikan" decision to Jev (`typesafe/jev`), a fast typed-decision model, on a per-message basis. Any evaluation failure (including a missing `OPENROUTER_API_KEY`) fails closed to "not addressed" so a misconfiguration cannot make the bot noisy in a shared channel.
24
+ - `harness/jev.ts`: a standalone `evaluateWithJev` client for Jev, reached through OpenRouter's `/api/alpha/decisions` REST API using the same `OPENROUTER_API_KEY` pi-ai's `openrouter` chat provider reads. Jev returns typed decisions (boolean probability, choice, score) instead of generated text, so it is a plain function call sites use directly rather than a selectable chat provider in `models.ts`'s catalog.
25
+ - Jev-mode auto-reply decisions (channel, probability, addressed, truncated message text) are logged at info level for observability.
26
+
12
27
  ## [1.0.0-beta.65]
13
28
 
14
29
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"auto-reply.d.ts","sourceRoot":"","sources":["../../../src/adapters/commands/auto-reply.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjE,qBAAa,uBAAwB,YAAW,cAAc;IACtD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAiCzD;CACF"}
1
+ {"version":3,"file":"auto-reply.d.ts","sourceRoot":"","sources":["../../../src/adapters/commands/auto-reply.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAoBjE,qBAAa,uBAAwB,YAAW,cAAc;IACtD,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA6BzD;CACF"}
@@ -1,7 +1,20 @@
1
- import { setSlackConversationAutoReply, slackConversationAutoReplyEnabled, } from "../../settings/index.js";
1
+ import { setSlackConversationAutoReply, slackConversationAutoReplyMode, } from "../../settings/index.js";
2
2
  import { slashForms, matchCommand } from "./manifest.js";
3
3
  import { replySummary } from "./utils.js";
4
4
  const AUTO_REPLY_COMMANDS = slashForms("autoreply");
5
+ function isAutoReplyMode(value) {
6
+ return value === "on" || value === "off" || value === "jev";
7
+ }
8
+ function describeAutoReplyMode(mode) {
9
+ switch (mode) {
10
+ case "on":
11
+ return "Enabled. New messages in this channel will trigger mikan without a mention.";
12
+ case "jev":
13
+ return "Jev-assisted. Jev decides per message whether it addresses mikan.";
14
+ case "off":
15
+ return "Disabled. New messages in this channel must address mikan explicitly.";
16
+ }
17
+ }
5
18
  export class AutoReplyCommandHandler {
6
19
  async tryHandle(context) {
7
20
  const matched = matchCommand(context.commandText, AUTO_REPLY_COMMANDS);
@@ -20,18 +33,14 @@ export class AutoReplyCommandHandler {
20
33
  return true;
21
34
  }
22
35
  const value = matched.args[0]?.toLowerCase();
23
- if ((value !== "on" && value !== "off") || matched.args.length !== 1) {
24
- await replySummary(context, "Auto-reply", ["Usage: `/pi-auto-reply <on|off>`"]);
36
+ if (!isAutoReplyMode(value) || matched.args.length !== 1) {
37
+ await replySummary(context, "Auto-reply", ["Usage: `/pi-auto-reply <on|off|jev>`"]);
25
38
  return true;
26
39
  }
27
40
  const office = context.services.workspace.office(context.address);
28
- setSlackConversationAutoReply(office, value === "on");
29
- const enabled = slackConversationAutoReplyEnabled(office);
30
- await replySummary(context, "Auto-reply", [
31
- enabled
32
- ? "Enabled. New messages in this channel will trigger mikan without a mention."
33
- : "Disabled. New messages in this channel must address mikan explicitly.",
34
- ]);
41
+ setSlackConversationAutoReply(office, value);
42
+ const mode = slackConversationAutoReplyMode(office);
43
+ await replySummary(context, "Auto-reply", [describeAutoReplyMode(mode)]);
35
44
  return true;
36
45
  }
37
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auto-reply.js","sourceRoot":"","sources":["../../../src/adapters/commands/auto-reply.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,iCAAiC,GAClC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,mBAAmB,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;AAEpD,MAAM,OAAO,uBAAuB;IAClC,KAAK,CAAC,SAAS,CAAC,OAAuB;QACrC,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;QACvE,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAE3B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,MAAM,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE;gBACxC,kDAAkD;aACnD,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;YAChC,MAAM,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE;gBACxC,0FAA0F;aAC3F,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrE,MAAM,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,kCAAkC,CAAC,CAAC,CAAC;YAChF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClE,6BAA6B,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC;QACtD,MAAM,OAAO,GAAG,iCAAiC,CAAC,MAAM,CAAC,CAAC;QAC1D,MAAM,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE;YACxC,OAAO;gBACL,CAAC,CAAC,6EAA6E;gBAC/E,CAAC,CAAC,uEAAuE;SAC5E,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["import {\n setSlackConversationAutoReply,\n slackConversationAutoReplyEnabled,\n} from \"../../settings/index.js\";\nimport { slashForms, matchCommand } from \"./manifest.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replySummary } from \"./utils.js\";\n\nconst AUTO_REPLY_COMMANDS = slashForms(\"autoreply\");\n\nexport class AutoReplyCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const matched = matchCommand(context.commandText, AUTO_REPLY_COMMANDS);\n if (!matched) return false;\n\n if (context.platform !== \"slack\") {\n await replySummary(context, \"Auto-reply\", [\n \"Auto-reply is currently available on Slack only.\",\n ]);\n return true;\n }\n\n if (context.privateConversation) {\n await replySummary(context, \"Auto-reply\", [\n \"Auto-reply only applies to shared channels. Direct messages already reply automatically.\",\n ]);\n return true;\n }\n\n const value = matched.args[0]?.toLowerCase();\n if ((value !== \"on\" && value !== \"off\") || matched.args.length !== 1) {\n await replySummary(context, \"Auto-reply\", [\"Usage: `/pi-auto-reply <on|off>`\"]);\n return true;\n }\n\n const office = context.services.workspace.office(context.address);\n setSlackConversationAutoReply(office, value === \"on\");\n const enabled = slackConversationAutoReplyEnabled(office);\n await replySummary(context, \"Auto-reply\", [\n enabled\n ? \"Enabled. New messages in this channel will trigger mikan without a mention.\"\n : \"Disabled. New messages in this channel must address mikan explicitly.\",\n ]);\n return true;\n }\n}\n"]}
1
+ {"version":3,"file":"auto-reply.js","sourceRoot":"","sources":["../../../src/adapters/commands/auto-reply.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,8BAA8B,GAE/B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,mBAAmB,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;AAEpD,SAAS,eAAe,CAAC,KAAyB;IAChD,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,CAAC;AAC9D,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAwB;IACrD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,IAAI;YACP,OAAO,6EAA6E,CAAC;QACvF,KAAK,KAAK;YACR,OAAO,mEAAmE,CAAC;QAC7E,KAAK,KAAK;YACR,OAAO,uEAAuE,CAAC;IACnF,CAAC;AACH,CAAC;AAED,MAAM,OAAO,uBAAuB;IAClC,KAAK,CAAC,SAAS,CAAC,OAAuB;QACrC,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;QACvE,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAE3B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,MAAM,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE;gBACxC,kDAAkD;aACnD,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;YAChC,MAAM,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE;gBACxC,0FAA0F;aAC3F,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QAC7C,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzD,MAAM,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,sCAAsC,CAAC,CAAC,CAAC;YACpF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAClE,6BAA6B,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC7C,MAAM,IAAI,GAAG,8BAA8B,CAAC,MAAM,CAAC,CAAC;QACpD,MAAM,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC;IACd,CAAC;CACF","sourcesContent":["import {\n setSlackConversationAutoReply,\n slackConversationAutoReplyMode,\n type SlackAutoReplyMode,\n} from \"../../settings/index.js\";\nimport { slashForms, matchCommand } from \"./manifest.js\";\nimport type { CommandContext, CommandHandler } from \"./types.js\";\nimport { replySummary } from \"./utils.js\";\n\nconst AUTO_REPLY_COMMANDS = slashForms(\"autoreply\");\n\nfunction isAutoReplyMode(value: string | undefined): value is SlackAutoReplyMode {\n return value === \"on\" || value === \"off\" || value === \"jev\";\n}\n\nfunction describeAutoReplyMode(mode: SlackAutoReplyMode): string {\n switch (mode) {\n case \"on\":\n return \"Enabled. New messages in this channel will trigger mikan without a mention.\";\n case \"jev\":\n return \"Jev-assisted. Jev decides per message whether it addresses mikan.\";\n case \"off\":\n return \"Disabled. New messages in this channel must address mikan explicitly.\";\n }\n}\n\nexport class AutoReplyCommandHandler implements CommandHandler {\n async tryHandle(context: CommandContext): Promise<boolean> {\n const matched = matchCommand(context.commandText, AUTO_REPLY_COMMANDS);\n if (!matched) return false;\n\n if (context.platform !== \"slack\") {\n await replySummary(context, \"Auto-reply\", [\n \"Auto-reply is currently available on Slack only.\",\n ]);\n return true;\n }\n\n if (context.privateConversation) {\n await replySummary(context, \"Auto-reply\", [\n \"Auto-reply only applies to shared channels. Direct messages already reply automatically.\",\n ]);\n return true;\n }\n\n const value = matched.args[0]?.toLowerCase();\n if (!isAutoReplyMode(value) || matched.args.length !== 1) {\n await replySummary(context, \"Auto-reply\", [\"Usage: `/pi-auto-reply <on|off|jev>`\"]);\n return true;\n }\n\n const office = context.services.workspace.office(context.address);\n setSlackConversationAutoReply(office, value);\n const mode = slackConversationAutoReplyMode(office);\n await replySummary(context, \"Auto-reply\", [describeAutoReplyMode(mode)]);\n return true;\n }\n}\n"]}
@@ -68,10 +68,10 @@ export const COMMAND_MANIFEST = [
68
68
  {
69
69
  name: "autoreply",
70
70
  aliases: ["auto-reply"],
71
- description: "Enable or disable replies without mentions",
71
+ description: "Enable, disable, or Jev-assist replies without mentions",
72
72
  arg: {
73
73
  name: "state",
74
- description: "on or off",
74
+ description: "on, off, or jev",
75
75
  required: true,
76
76
  },
77
77
  slackCommand: "/pi-auto-reply",
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/adapters/commands/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,sDAAsD;AACtD,MAAM,CAAC,MAAM,gBAAgB,GAAoC;IAC/D;QACE,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,yCAAyC;QACtD,YAAY,EAAE,WAAW;QACzB,UAAU,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE;QACvD,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,EAAE;KACjB;IACD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,4CAA4C;QACzD,IAAI,EAAE,IAAI;QACV,YAAY,EAAE,aAAa;QAC3B,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAC5B,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,EAAE;KACjB;IACD;QACE,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,sCAAsC;QACnD,GAAG,EAAE;YACH,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,iEAAiE;YAC9E,QAAQ,EAAE,KAAK;SAChB;QACD,YAAY,EAAE,WAAW;QACzB,UAAU,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;QACjC,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,EAAE;KACjB;IACD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,qCAAqC;QAClD,GAAG,EAAE;YACH,IAAI,EAAE,QAAQ;YACd,mEAAmE;YACnE,0DAA0D;YAC1D,sEAAsE;YACtE,kEAAkE;YAClE,mEAAmE;YACnE,uBAAuB;YACvB,WAAW,EAAE,yDAAyD;YACtE,QAAQ,EAAE,KAAK;SAChB;QACD,YAAY,EAAE,aAAa;QAC3B,UAAU,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;QACjC,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,EAAE,WAAW,EAAE,8BAA8B,EAAE;QAC7D,eAAe,EAAE,IAAI;KACtB;IACD;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,CAAC,YAAY,CAAC;QACvB,WAAW,EAAE,4CAA4C;QACzD,GAAG,EAAE;YACH,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,WAAW;YACxB,QAAQ,EAAE,IAAI;SACf;QACD,YAAY,EAAE,gBAAgB;QAC9B,UAAU,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;KAClC;IACD;QACE,IAAI,EAAE,MAAM;QACZ,WAAW,EAAE,+BAA+B;QAC5C,SAAS,EAAE,IAAI;QACf,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,EAAE,WAAW,EAAE,2BAA2B,EAAE;KAC3D;IACD;QACE,IAAI,EAAE,KAAK;QACX,WAAW,EAAE,4CAA4C;QACzD,YAAY,EAAE,SAAS;QACvB,UAAU,EAAE,EAAE;QACd,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,EAAE;QAChB,eAAe,EAAE,IAAI;KACtB;IACD;QACE,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,uBAAuB;QACpC,YAAY,EAAE,WAAW;QACzB,UAAU,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAC5B,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,EAAE;QAChB,eAAe,EAAE,IAAI;KACtB;CACF,CAAC;AAEF,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC5E,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;QAClE,IAAI,QAAQ,EAAE;QACd,OAAO,QAAQ,EAAE;KAClB,CAAC,CAAC;AACL,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB;IACjC,OAAO,gBAAgB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5E,OAAO,EAAE,KAAK,CAAC,IAAI;QACnB,WAAW,EAAE,KAAK,CAAC,YAAY,EAAE,WAAW,IAAI,KAAK,CAAC,WAAW;KAClE,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,OAA2B,EAC3B,OAAoC;IAEpC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,KAAK,GAAG,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1E,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,EAAa,CAAC;IAC/C,OAAO,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,aAAa,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;AAEpG,MAAM,oBAAoB,GAAG,IAAI,MAAM,CACrC,qBAAqB,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,sBAAsB,EAClE,GAAG,CACJ,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC","sourcesContent":["/**\n * Platform-agnostic command manifest — the single inventory that platform\n * adapters derive their native slash-command registration and routing from,\n * and that `isCommandText` and the handler grammars mirror.\n *\n * Adding a command means adding a manifest entry plus a handler in this\n * directory; adapters pick up registration and routing from the entry instead\n * of hand-restating the inventory (where a missed spot used to mean a silent\n * no-response).\n *\n * Per CONTEXT.md, slash commands are a minimal chat control surface: detailed\n * or infrequent settings belong in Admin, and bare commands are limited to\n * `session`. `stop` is listed for platform registration/UI only — it is a\n * magic word owned by conversation intake, not a command handler.\n */\n\nimport type { CommandManifestEntry } from \"./types.js\";\n\nexport type { CommandManifestEntry, SlackSlashRoute } from \"./types.js\";\n\n/** Entry order is the Telegram command-menu order. */\nexport const COMMAND_MANIFEST: readonly CommandManifestEntry[] = [\n {\n name: \"login\",\n description: \"Store credentials in your private vault\",\n slackCommand: \"/pi-login\",\n slackRoute: { includeText: true, privateCommand: true },\n discord: true,\n telegramMenu: {},\n },\n {\n name: \"session\",\n description: \"Open the current session in the web viewer\",\n bare: true,\n slackCommand: \"/pi-session\",\n slackRoute: { thread: true },\n discord: true,\n telegramMenu: {},\n },\n {\n name: \"model\",\n description: \"Switch this conversation's LLM model\",\n arg: {\n name: \"model\",\n description: \"provider/model[:thinking], e.g. anthropic/claude-sonnet-4-6:off\",\n required: false,\n },\n slackCommand: \"/pi-model\",\n slackRoute: { includeText: true },\n discord: true,\n telegramMenu: {},\n },\n {\n name: \"sandbox\",\n description: \"Show sandbox status or boost limits\",\n arg: {\n name: \"action\",\n // Discord caps option descriptions at 100 characters (registration\n // fails otherwise); commands.test.ts enforces the budget.\n // The `door` action still works but is deliberately undocumented: the\n // workspace posture follows the platform's own channel visibility\n // automatically, and overriding it is an admin escape hatch, not a\n // user-facing feature.\n description: \"'boost' temporarily applies the configured boost limits\",\n required: false,\n },\n slackCommand: \"/pi-sandbox\",\n slackRoute: { includeText: true },\n discord: true,\n telegramMenu: { description: \"Show or boost sandbox limits\" },\n telegramCommand: true,\n },\n {\n name: \"autoreply\",\n aliases: [\"auto-reply\"],\n description: \"Enable or disable replies without mentions\",\n arg: {\n name: \"state\",\n description: \"on or off\",\n required: true,\n },\n slackCommand: \"/pi-auto-reply\",\n slackRoute: { includeText: true },\n },\n {\n name: \"stop\",\n description: \"Stop the current conversation\",\n magicWord: true,\n discord: true,\n telegramMenu: { description: \"Stop ongoing conversation\" },\n },\n {\n name: \"new\",\n description: \"Reset conversation history and start fresh\",\n slackCommand: \"/pi-new\",\n slackRoute: {},\n discord: true,\n telegramMenu: {},\n telegramCommand: true,\n },\n {\n name: \"admin\",\n description: \"Open the admin portal\",\n slackCommand: \"/pi-admin\",\n slackRoute: { thread: true },\n discord: true,\n telegramMenu: {},\n telegramCommand: true,\n },\n];\n\nexport function commandManifestEntry(name: string): CommandManifestEntry {\n const entry = COMMAND_MANIFEST.find((candidate) => candidate.name === name);\n if (!entry) throw new Error(`Unknown command in manifest: ${name}`);\n return entry;\n}\n\n/** Slash spellings of a command: `/<name>` and `/pi-<name>` for the name and each alias. */\nexport function slashForms(name: string): readonly string[] {\n const entry = commandManifestEntry(name);\n return [entry.name, ...(entry.aliases ?? [])].flatMap((spelling) => [\n `/${spelling}`,\n `/pi-${spelling}`,\n ]);\n}\n\n/** Every accepted spelling: the bare name (when the command is bare) plus slash forms. */\nexport function commandForms(name: string): readonly string[] {\n const entry = commandManifestEntry(name);\n return [...(entry.bare ? [entry.name] : []), ...slashForms(name)];\n}\n\n/** Telegram `setMyCommands` payload (menu registration; routing is separate). */\nexport function telegramCommandMenu(): { command: string; description: string }[] {\n return COMMAND_MANIFEST.filter((entry) => entry.telegramMenu).map((entry) => ({\n command: entry.name,\n description: entry.telegramMenu?.description ?? entry.description,\n }));\n}\n\nexport function matchCommand<Command extends string>(\n text: string,\n aliases: readonly Command[],\n options?: { stripMention?: boolean },\n): { command: Command; args: string[] } | null {\n const tokens = text.trim().split(/\\s+/).filter(Boolean);\n const first = tokens[0];\n if (first === undefined) return null;\n\n const token = options?.stripMention ? first.replace(/@\\w+$/i, \"\") : first;\n const command = token.toLowerCase() as Command;\n return aliases.includes(command) ? { command, args: tokens.slice(1) } : null;\n}\n\n/**\n * Chat-command text recognition, derived from the command manifest so the\n * inventory has a single source of truth. Session resume uses it to keep\n * command messages out of replayed history.\n *\n * The `pi-[\\w-]+` alternative is a catch-all for platform-prefixed command\n * names (including ones a platform registers but this build no longer\n * handles), independent of the manifest.\n */\nconst COMMAND_NAMES = COMMAND_MANIFEST.flatMap((entry) => [entry.name].concat(entry.aliases ?? []));\n\nconst COMMAND_TEXT_PATTERN = new RegExp(\n `^\\\\/(?:pi-[\\\\w-]+|${COMMAND_NAMES.join(\"|\")})(?:@\\\\w+)?(?:\\\\s|$)`,\n \"i\",\n);\n\nexport function isCommandText(text: string): boolean {\n return COMMAND_TEXT_PATTERN.test(text.trim());\n}\n"]}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/adapters/commands/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Platform-agnostic command manifest — the single inventory that platform\n * adapters derive their native slash-command registration and routing from,\n * and that `isCommandText` and the handler grammars mirror.\n *\n * Adding a command means adding a manifest entry plus a handler in this\n * directory; adapters pick up registration and routing from the entry instead\n * of hand-restating the inventory (where a missed spot used to mean a silent\n * no-response).\n *\n * Per CONTEXT.md, slash commands are a minimal chat control surface: detailed\n * or infrequent settings belong in Admin, and bare commands are limited to\n * `session`. `stop` is listed for platform registration/UI only — it is a\n * magic word owned by conversation intake, not a command handler.\n */\n\nimport type { CommandManifestEntry } from \"./types.js\";\n\nexport type { CommandManifestEntry, SlackSlashRoute } from \"./types.js\";\n\n/** Entry order is the Telegram command-menu order. */\nexport const COMMAND_MANIFEST: readonly CommandManifestEntry[] = [\n {\n name: \"login\",\n description: \"Store credentials in your private vault\",\n slackCommand: \"/pi-login\",\n slackRoute: { includeText: true, privateCommand: true },\n discord: true,\n telegramMenu: {},\n },\n {\n name: \"session\",\n description: \"Open the current session in the web viewer\",\n bare: true,\n slackCommand: \"/pi-session\",\n slackRoute: { thread: true },\n discord: true,\n telegramMenu: {},\n },\n {\n name: \"model\",\n description: \"Switch this conversation's LLM model\",\n arg: {\n name: \"model\",\n description: \"provider/model[:thinking], e.g. anthropic/claude-sonnet-4-6:off\",\n required: false,\n },\n slackCommand: \"/pi-model\",\n slackRoute: { includeText: true },\n discord: true,\n telegramMenu: {},\n },\n {\n name: \"sandbox\",\n description: \"Show sandbox status or boost limits\",\n arg: {\n name: \"action\",\n // Discord caps option descriptions at 100 characters (registration\n // fails otherwise); commands.test.ts enforces the budget.\n // The `door` action still works but is deliberately undocumented: the\n // workspace posture follows the platform's own channel visibility\n // automatically, and overriding it is an admin escape hatch, not a\n // user-facing feature.\n description: \"'boost' temporarily applies the configured boost limits\",\n required: false,\n },\n slackCommand: \"/pi-sandbox\",\n slackRoute: { includeText: true },\n discord: true,\n telegramMenu: { description: \"Show or boost sandbox limits\" },\n telegramCommand: true,\n },\n {\n name: \"autoreply\",\n aliases: [\"auto-reply\"],\n description: \"Enable, disable, or Jev-assist replies without mentions\",\n arg: {\n name: \"state\",\n description: \"on, off, or jev\",\n required: true,\n },\n slackCommand: \"/pi-auto-reply\",\n slackRoute: { includeText: true },\n },\n {\n name: \"stop\",\n description: \"Stop the current conversation\",\n magicWord: true,\n discord: true,\n telegramMenu: { description: \"Stop ongoing conversation\" },\n },\n {\n name: \"new\",\n description: \"Reset conversation history and start fresh\",\n slackCommand: \"/pi-new\",\n slackRoute: {},\n discord: true,\n telegramMenu: {},\n telegramCommand: true,\n },\n {\n name: \"admin\",\n description: \"Open the admin portal\",\n slackCommand: \"/pi-admin\",\n slackRoute: { thread: true },\n discord: true,\n telegramMenu: {},\n telegramCommand: true,\n },\n];\n\nexport function commandManifestEntry(name: string): CommandManifestEntry {\n const entry = COMMAND_MANIFEST.find((candidate) => candidate.name === name);\n if (!entry) throw new Error(`Unknown command in manifest: ${name}`);\n return entry;\n}\n\n/** Slash spellings of a command: `/<name>` and `/pi-<name>` for the name and each alias. */\nexport function slashForms(name: string): readonly string[] {\n const entry = commandManifestEntry(name);\n return [entry.name, ...(entry.aliases ?? [])].flatMap((spelling) => [\n `/${spelling}`,\n `/pi-${spelling}`,\n ]);\n}\n\n/** Every accepted spelling: the bare name (when the command is bare) plus slash forms. */\nexport function commandForms(name: string): readonly string[] {\n const entry = commandManifestEntry(name);\n return [...(entry.bare ? [entry.name] : []), ...slashForms(name)];\n}\n\n/** Telegram `setMyCommands` payload (menu registration; routing is separate). */\nexport function telegramCommandMenu(): { command: string; description: string }[] {\n return COMMAND_MANIFEST.filter((entry) => entry.telegramMenu).map((entry) => ({\n command: entry.name,\n description: entry.telegramMenu?.description ?? entry.description,\n }));\n}\n\nexport function matchCommand<Command extends string>(\n text: string,\n aliases: readonly Command[],\n options?: { stripMention?: boolean },\n): { command: Command; args: string[] } | null {\n const tokens = text.trim().split(/\\s+/).filter(Boolean);\n const first = tokens[0];\n if (first === undefined) return null;\n\n const token = options?.stripMention ? first.replace(/@\\w+$/i, \"\") : first;\n const command = token.toLowerCase() as Command;\n return aliases.includes(command) ? { command, args: tokens.slice(1) } : null;\n}\n\n/**\n * Chat-command text recognition, derived from the command manifest so the\n * inventory has a single source of truth. Session resume uses it to keep\n * command messages out of replayed history.\n *\n * The `pi-[\\w-]+` alternative is a catch-all for platform-prefixed command\n * names (including ones a platform registers but this build no longer\n * handles), independent of the manifest.\n */\nconst COMMAND_NAMES = COMMAND_MANIFEST.flatMap((entry) => [entry.name].concat(entry.aliases ?? []));\n\nconst COMMAND_TEXT_PATTERN = new RegExp(\n `^\\\\/(?:pi-[\\\\w-]+|${COMMAND_NAMES.join(\"|\")})(?:@\\\\w+)?(?:\\\\s|$)`,\n \"i\",\n);\n\nexport function isCommandText(text: string): boolean {\n return COMMAND_TEXT_PATTERN.test(text.trim());\n}\n"]}
@@ -0,0 +1,19 @@
1
+ /** The one question Jev answers for an unaddressed shared-channel message. */
2
+ export declare const JEV_ADDRESSED_INSTRUCTIONS = "Given the recent Slack conversation, does the NEW message address, ask, or request the mikan AI assistant to do something (including continuing something mikan was already helping with), rather than being conversation between humans that needs no reply from mikan?";
3
+ /**
4
+ * Build the shared `state` Jev scores when deciding whether an unaddressed
5
+ * shared-channel message is meant for mikan. A single line like "好啊" is
6
+ * unjudgeable on its own, so the state carries the surrounding scope: the
7
+ * channel's recent top-level messages, or the thread's messages plus whether
8
+ * mikan has already taken part in it.
9
+ */
10
+ export declare function buildAutoReplyState(conversationDir: string, event: {
11
+ ts: string;
12
+ thread_ts?: string;
13
+ user: string;
14
+ text: string;
15
+ }, options?: {
16
+ speaker?: string;
17
+ limit?: number;
18
+ }): string;
19
+ //# sourceMappingURL=auto-reply-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-reply-context.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/auto-reply-context.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,eAAO,MAAM,0BAA0B,6QACqO,CAAC;AAmD7Q;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACrE,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACjD,MAAM,CAiBR"}
@@ -0,0 +1,70 @@
1
+ import { join } from "node:path";
2
+ import { isRecord, readTextFileIfExists } from "../../file-guards.js";
3
+ /** The one question Jev answers for an unaddressed shared-channel message. */
4
+ export const JEV_ADDRESSED_INSTRUCTIONS = "Given the recent Slack conversation, does the NEW message address, ask, or request the mikan AI assistant to do something (including continuing something mikan was already helping with), rather than being conversation between humans that needs no reply from mikan?";
5
+ /** Recent messages in the same scope as `event` (channel top level or its thread), oldest first. */
6
+ function readRecentScope(conversationDir, event, limit) {
7
+ const raw = readTextFileIfExists(join(conversationDir, "log.jsonl"));
8
+ if (raw === undefined)
9
+ return [];
10
+ const lines = [];
11
+ for (const line of raw.split("\n")) {
12
+ if (!line)
13
+ continue;
14
+ let entry;
15
+ try {
16
+ entry = JSON.parse(line);
17
+ }
18
+ catch {
19
+ continue;
20
+ }
21
+ if (!isRecord(entry) || typeof entry.ts !== "string" || typeof entry.text !== "string") {
22
+ continue;
23
+ }
24
+ const threadTs = typeof entry.threadTs === "string" ? entry.threadTs : undefined;
25
+ // Thread replies belong to their thread; everything else is the channel's top level.
26
+ const inScope = event.thread_ts
27
+ ? threadTs === event.thread_ts || entry.ts === event.thread_ts
28
+ : threadTs === undefined;
29
+ if (!inScope || entry.ts === event.ts)
30
+ continue;
31
+ const isMikan = entry.isMessagingBot === true && entry.user === "bot";
32
+ const speaker = isMikan
33
+ ? "mikan"
34
+ : String(entry.displayName ?? entry.userName ?? entry.user ?? "unknown");
35
+ const previous = lines.at(-1);
36
+ // Streamed bot responses are logged in chunks sharing one ts; merge them.
37
+ if (previous && previous.isMikan && isMikan && previous.ts === entry.ts) {
38
+ previous.text += entry.text;
39
+ continue;
40
+ }
41
+ lines.push({ ts: entry.ts, threadTs, speaker, text: entry.text, isMikan });
42
+ }
43
+ return lines.slice(-limit);
44
+ }
45
+ /**
46
+ * Build the shared `state` Jev scores when deciding whether an unaddressed
47
+ * shared-channel message is meant for mikan. A single line like "好啊" is
48
+ * unjudgeable on its own, so the state carries the surrounding scope: the
49
+ * channel's recent top-level messages, or the thread's messages plus whether
50
+ * mikan has already taken part in it.
51
+ */
52
+ export function buildAutoReplyState(conversationDir, event, options = {}) {
53
+ const recent = readRecentScope(conversationDir, event, options.limit ?? 12);
54
+ const header = event.thread_ts
55
+ ? `Slack thread reply. mikan has ${recent.some((l) => l.isMikan) ? "already replied" : "not replied"} in this thread.`
56
+ : "Slack channel top-level message.";
57
+ const context = recent.length
58
+ ? recent.map((l) => `- ${l.speaker}: ${l.text}`).join("\n")
59
+ : "(none)";
60
+ return [
61
+ header,
62
+ "",
63
+ "Recent messages (oldest first):",
64
+ context,
65
+ "",
66
+ `NEW message from ${options.speaker ?? event.user}:`,
67
+ event.text,
68
+ ].join("\n");
69
+ }
70
+ //# sourceMappingURL=auto-reply-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-reply-context.js","sourceRoot":"","sources":["../../../src/adapters/slack/auto-reply-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport { isRecord, readTextFileIfExists } from \"../../file-guards.js\";\n\n/** The one question Jev answers for an unaddressed shared-channel message. */\nexport const JEV_ADDRESSED_INSTRUCTIONS =\n \"Given the recent Slack conversation, does the NEW message address, ask, or request the mikan AI assistant to do something (including continuing something mikan was already helping with), rather than being conversation between humans that needs no reply from mikan?\";\n\ninterface LogLine {\n ts: string;\n threadTs?: string;\n speaker: string;\n text: string;\n isMikan: boolean;\n}\n\n/** Recent messages in the same scope as `event` (channel top level or its thread), oldest first. */\nfunction readRecentScope(\n conversationDir: string,\n event: { ts: string; thread_ts?: string },\n limit: number,\n): LogLine[] {\n const raw = readTextFileIfExists(join(conversationDir, \"log.jsonl\"));\n if (raw === undefined) return [];\n const lines: LogLine[] = [];\n for (const line of raw.split(\"\\n\")) {\n if (!line) continue;\n let entry: unknown;\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n if (!isRecord(entry) || typeof entry.ts !== \"string\" || typeof entry.text !== \"string\") {\n continue;\n }\n const threadTs = typeof entry.threadTs === \"string\" ? entry.threadTs : undefined;\n // Thread replies belong to their thread; everything else is the channel's top level.\n const inScope = event.thread_ts\n ? threadTs === event.thread_ts || entry.ts === event.thread_ts\n : threadTs === undefined;\n if (!inScope || entry.ts === event.ts) continue;\n const isMikan = entry.isMessagingBot === true && entry.user === \"bot\";\n const speaker = isMikan\n ? \"mikan\"\n : String(entry.displayName ?? entry.userName ?? entry.user ?? \"unknown\");\n const previous = lines.at(-1);\n // Streamed bot responses are logged in chunks sharing one ts; merge them.\n if (previous && previous.isMikan && isMikan && previous.ts === entry.ts) {\n previous.text += entry.text;\n continue;\n }\n lines.push({ ts: entry.ts, threadTs, speaker, text: entry.text, isMikan });\n }\n return lines.slice(-limit);\n}\n\n/**\n * Build the shared `state` Jev scores when deciding whether an unaddressed\n * shared-channel message is meant for mikan. A single line like \"好啊\" is\n * unjudgeable on its own, so the state carries the surrounding scope: the\n * channel's recent top-level messages, or the thread's messages plus whether\n * mikan has already taken part in it.\n */\nexport function buildAutoReplyState(\n conversationDir: string,\n event: { ts: string; thread_ts?: string; user: string; text: string },\n options: { speaker?: string; limit?: number } = {},\n): string {\n const recent = readRecentScope(conversationDir, event, options.limit ?? 12);\n const header = event.thread_ts\n ? `Slack thread reply. mikan has ${recent.some((l) => l.isMikan) ? \"already replied\" : \"not replied\"} in this thread.`\n : \"Slack channel top-level message.\";\n const context = recent.length\n ? recent.map((l) => `- ${l.speaker}: ${l.text}`).join(\"\\n\")\n : \"(none)\";\n return [\n header,\n \"\",\n \"Recent messages (oldest first):\",\n context,\n \"\",\n `NEW message from ${options.speaker ?? event.user}:`,\n event.text,\n ].join(\"\\n\");\n}\n"]}
@@ -185,6 +185,18 @@ export declare class SlackMessagingBot implements MessagingBot {
185
185
  private handleAppMention;
186
186
  private admitHumanMessage;
187
187
  private deliverTaskUpdate;
188
+ /**
189
+ * Ask Jev a single typed yes/no question about whether this unaddressed
190
+ * shared-channel message (top-level or thread reply) addresses mikan. The
191
+ * scored state carries the surrounding scope from log.jsonl, since a bare
192
+ * thread reply cannot be judged from its own text. Only called in `jev`
193
+ * auto-reply mode, and only awaited by the caller in that mode, so
194
+ * `on`/`off` stay synchronous (magic-word `stop` handling depends on that).
195
+ * Any failure (including a missing `OPENROUTER_API_KEY`) fails closed to
196
+ * "not addressed" so a misconfiguration cannot make the bot noisy in a
197
+ * shared channel.
198
+ */
199
+ private evaluateJevAddressed;
188
200
  private handleMessageEvent;
189
201
  private handleSlashCommand;
190
202
  private handleAppHomeOpened;
@@ -1 +1 @@
1
- {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/bot.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/bot.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,8 @@ import { basename, join } from "node:path";
6
6
  import { createConversationEvent, createConversationMessage, } from "../index.js";
7
7
  import { createOfficeAddress, listRegisteredOffices } from "../../office/index.js";
8
8
  import { COMMAND_MANIFEST } from "../commands/manifest.js";
9
- import { slackConversationAutoReplyEnabled, resolveConversationSettings, } from "../../settings/index.js";
9
+ import { slackConversationAutoReplyMode, resolveConversationSettings, } from "../../settings/index.js";
10
+ import { evaluateWithJev, JevNotConfiguredError } from "../../harness/index.js";
10
11
  import * as log from "../../log.js";
11
12
  import { isRecord, readTextFileIfExists } from "../../file-guards.js";
12
13
  import { PRODUCT_NAME, formatForceStopped } from "../messages.js";
@@ -22,6 +23,7 @@ import { reportUserFacingError } from "../../observability/index.js";
22
23
  import { recordSlackUpdate } from "./update-diagnostics.js";
23
24
  import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
24
25
  import { querySlackTasks, isTaskStatusQuestion, formatTaskStatus, readTaskRoots, } from "./task-status.js";
26
+ import { buildAutoReplyState, JEV_ADDRESSED_INSTRUCTIONS } from "./auto-reply-context.js";
25
27
  import { StreamStartLimiter } from "./stream-limits.js";
26
28
  const SLACK_EVENT_ANCHOR_TEXT = "Working on it...";
27
29
  function hasMessageContent(message, includeBlocks = true) {
@@ -1289,6 +1291,47 @@ export class SlackMessagingBot {
1289
1291
  }
1290
1292
  return false;
1291
1293
  }
1294
+ /**
1295
+ * Ask Jev a single typed yes/no question about whether this unaddressed
1296
+ * shared-channel message (top-level or thread reply) addresses mikan. The
1297
+ * scored state carries the surrounding scope from log.jsonl, since a bare
1298
+ * thread reply cannot be judged from its own text. Only called in `jev`
1299
+ * auto-reply mode, and only awaited by the caller in that mode, so
1300
+ * `on`/`off` stay synchronous (magic-word `stop` handling depends on that).
1301
+ * Any failure (including a missing `OPENROUTER_API_KEY`) fails closed to
1302
+ * "not addressed" so a misconfiguration cannot make the bot noisy in a
1303
+ * shared channel.
1304
+ */
1305
+ async evaluateJevAddressed(event) {
1306
+ if (!event.text.trim() || matchMagicWord(event.text) === "stop")
1307
+ return false;
1308
+ try {
1309
+ const user = this.users.get(event.user);
1310
+ const state = buildAutoReplyState(this.conversationDir(event.channel), event, {
1311
+ speaker: user?.displayName ?? user?.userName,
1312
+ });
1313
+ const result = await evaluateWithJev(state, {
1314
+ addressed: { type: "boolean", instructions: JEV_ADDRESSED_INSTRUCTIONS },
1315
+ });
1316
+ const probability = result.answers.addressed.probability;
1317
+ log.logInfo(`[${event.channel}] jev auto-reply: probability=${probability.toFixed(2)} addressed=${probability > 0.5} text="${event.text.slice(0, 80)}"`);
1318
+ return probability > 0.5;
1319
+ }
1320
+ catch (err) {
1321
+ if (err instanceof JevNotConfiguredError) {
1322
+ log.logWarning("Slack auto-reply jev mode requires OPENROUTER_API_KEY; treating message as unaddressed", String(err));
1323
+ return false;
1324
+ }
1325
+ reportUserFacingError(err, {
1326
+ domain: "chat_platform",
1327
+ surface: "slack_auto_reply_jev",
1328
+ operation: "evaluate",
1329
+ severity: "warning",
1330
+ context: { conversationId: event.conversationId },
1331
+ });
1332
+ return false;
1333
+ }
1334
+ }
1292
1335
  async handleMessageEvent({ event, ack, }) {
1293
1336
  const e = event;
1294
1337
  if (!this.admitHumanMessage(e, ack))
@@ -1305,7 +1348,6 @@ export class SlackMessagingBot {
1305
1348
  ack();
1306
1349
  return;
1307
1350
  }
1308
- const isThreadReply = !!e.thread_ts;
1309
1351
  const sessionKey = isDM ? resolveSlackSessionKey(e.channel, e.thread_ts) : undefined;
1310
1352
  // Name the conversation from the first thing the person said. Guarded on
1311
1353
  // the assistant registry, so classic DM threads are untouched, and it
@@ -1336,13 +1378,6 @@ export class SlackMessagingBot {
1336
1378
  ack();
1337
1379
  return;
1338
1380
  }
1339
- if (!isDM && isThreadReply && matchMagicWord(slackEvent.text) !== "stop") {
1340
- void attachmentsPromise.catch((err) => {
1341
- log.logWarning("Failed to log Slack message", String(err));
1342
- });
1343
- ack();
1344
- return;
1345
- }
1346
1381
  const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
1347
1382
  slackEvent.sessionKey = activeSessionKey;
1348
1383
  const taskThread = isDM && !!e.thread_ts && this.isTaskThread(e.channel, e.thread_ts);
@@ -1352,7 +1387,17 @@ export class SlackMessagingBot {
1352
1387
  if (await this.deliverTaskUpdate(slackEvent, attachmentsPromise))
1353
1388
  return;
1354
1389
  }
1355
- const autoReply = !isDM && slackConversationAutoReplyEnabled(this.workspace.office(slackEvent.address));
1390
+ // Shared-channel messages without a mention, top-level or in a thread,
1391
+ // all go through the same auto-reply gate; jev mode sees the surrounding
1392
+ // scope so thread follow-ups are judged in context rather than dropped.
1393
+ const autoReplyMode = isDM
1394
+ ? "off"
1395
+ : slackConversationAutoReplyMode(this.workspace.office(slackEvent.address));
1396
+ const autoReply = autoReplyMode === "on"
1397
+ ? true
1398
+ : autoReplyMode === "jev"
1399
+ ? await this.evaluateJevAddressed(slackEvent)
1400
+ : false;
1356
1401
  const intake = this.processSlackMessageIntake({
1357
1402
  event: slackEvent,
1358
1403
  attachmentsPromise,