@geminixiang/mikan 1.0.0-beta.66 → 1.0.0-beta.68

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 (47) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/adapters/slack/auto-reply-context.d.ts +21 -0
  3. package/dist/adapters/slack/auto-reply-context.d.ts.map +1 -0
  4. package/dist/adapters/slack/auto-reply-context.js +28 -0
  5. package/dist/adapters/slack/auto-reply-context.js.map +1 -0
  6. package/dist/adapters/slack/bot.d.ts +16 -6
  7. package/dist/adapters/slack/bot.d.ts.map +1 -1
  8. package/dist/adapters/slack/bot.js +83 -30
  9. package/dist/adapters/slack/bot.js.map +1 -1
  10. package/dist/adapters/slack/jev-context.d.ts +30 -0
  11. package/dist/adapters/slack/jev-context.d.ts.map +1 -0
  12. package/dist/adapters/slack/jev-context.js +67 -0
  13. package/dist/adapters/slack/jev-context.js.map +1 -0
  14. package/dist/adapters/slack/task-intent.d.ts +32 -0
  15. package/dist/adapters/slack/task-intent.d.ts.map +1 -0
  16. package/dist/adapters/slack/task-intent.js +72 -0
  17. package/dist/adapters/slack/task-intent.js.map +1 -0
  18. package/dist/adapters/slack/task-status.d.ts.map +1 -1
  19. package/dist/adapters/slack/task-status.js +6 -0
  20. package/dist/adapters/slack/task-status.js.map +1 -1
  21. package/dist/harness/index.d.ts +1 -1
  22. package/dist/harness/index.d.ts.map +1 -1
  23. package/dist/harness/index.js.map +1 -1
  24. package/dist/harness/jev.d.ts +26 -6
  25. package/dist/harness/jev.d.ts.map +1 -1
  26. package/dist/harness/jev.js +17 -3
  27. package/dist/harness/jev.js.map +1 -1
  28. package/dist/harness/presenter.d.ts.map +1 -1
  29. package/dist/harness/presenter.js +4 -3
  30. package/dist/harness/presenter.js.map +1 -1
  31. package/dist/harness/prompt.d.ts.map +1 -1
  32. package/dist/harness/prompt.js +1 -0
  33. package/dist/harness/prompt.js.map +1 -1
  34. package/dist/harness/subagent-profiles.js +5 -5
  35. package/dist/harness/subagent-profiles.js.map +1 -1
  36. package/dist/harness/tools/index.d.ts.map +1 -1
  37. package/dist/harness/tools/index.js +3 -0
  38. package/dist/harness/tools/index.js.map +1 -1
  39. package/dist/harness/tools/jev.d.ts +21 -0
  40. package/dist/harness/tools/jev.d.ts.map +1 -0
  41. package/dist/harness/tools/jev.js +109 -0
  42. package/dist/harness/tools/jev.js.map +1 -0
  43. package/dist/sessions/session-store.d.ts +2 -0
  44. package/dist/sessions/session-store.d.ts.map +1 -1
  45. package/dist/sessions/session-store.js +1 -0
  46. package/dist/sessions/session-store.js.map +1 -1
  47. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -9,6 +9,27 @@ any release.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.0.0-beta.68]
13
+
14
+ ### Added
15
+
16
+ - `jev` agent tool: the model can now call Jev directly with a `state` (text or JSON) and any number of boolean / choice / score `questions`, mirroring the decisions API one-to-one — structured instructions and criteria, per-option probabilities, confidence, and score legends all pass through. Available to the main agent and to the `worker`, `software-engineer`, `data-scientist`, and `summarizer` subagent profiles. A missing `OPENROUTER_API_KEY` is reported as a tool error so the model judges for itself.
17
+ - Slack: DM messages in a task thread (and top-level DMs while a task is running) are classified by Jev as `status`, `steer`, or `request` before spending a model turn. Status questions are answered from recorded task state; steering is delivered into the running task; anything else starts a normal run. The regex status matcher remains as the fallback.
18
+ - `task_status` reports `queued` for tasks that were admitted but have not started executing yet.
19
+
20
+ ### Changed
21
+
22
+ - `harness/jev.ts` accepts structured JSON for state, instructions, and criteria, and returns `confidence` and `legend` on choice / score answers.
23
+ - Jev auto-reply and task-intent state renders `<@U…>` mentions as `@Name` (the bot as `@mikan`) so Jev can tell a message addressed to someone else from one addressed to mikan.
24
+ - Tool progress lines for `jev` calls are prefixed with `jev ·` so users can see which steps came from a calibrated judgment.
25
+
26
+ ## [1.0.0-beta.67]
27
+
28
+ ### Changed
29
+
30
+ - 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.
31
+ - Deploy docs: `/pi-auto-reply` slash command description lists `on | off | jev`.
32
+
12
33
  ## [1.0.0-beta.66]
13
34
 
14
35
  ### Added
@@ -0,0 +1,21 @@
1
+ import { type SlackNameResolver } from "./jev-context.js";
2
+ /** The one question Jev answers for an unaddressed shared-channel message. */
3
+ 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? A message that @-mentions a specific person other than mikan is directed at that person.";
4
+ /**
5
+ * Build the shared `state` Jev scores when deciding whether an unaddressed
6
+ * shared-channel message is meant for mikan: the surrounding scope plus, for
7
+ * a thread, whether mikan has already taken part in it. Mentions are shown
8
+ * as names so "@someone else" reads as such.
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
+ resolveName?: SlackNameResolver;
19
+ botUserId?: string | null;
20
+ }): string;
21
+ //# 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":"AAAA,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AAE1B,8EAA8E;AAC9E,eAAO,MAAM,0BAA0B,sWAC8T,CAAC;AAEtW;;;;;GAKG;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;IACP,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,GACL,MAAM,CAiBR"}
@@ -0,0 +1,28 @@
1
+ import { formatRecentScope, humanizeMentions, readRecentScope, } from "./jev-context.js";
2
+ /** The one question Jev answers for an unaddressed shared-channel message. */
3
+ 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? A message that @-mentions a specific person other than mikan is directed at that person.";
4
+ /**
5
+ * Build the shared `state` Jev scores when deciding whether an unaddressed
6
+ * shared-channel message is meant for mikan: the surrounding scope plus, for
7
+ * a thread, whether mikan has already taken part in it. Mentions are shown
8
+ * as names so "@someone else" reads as such.
9
+ */
10
+ export function buildAutoReplyState(conversationDir, event, options = {}) {
11
+ const resolve = options.resolveName ?? (() => undefined);
12
+ const humanize = (text) => humanizeMentions(text, resolve, options.botUserId ?? null);
13
+ const recent = readRecentScope(conversationDir, event, { limit: options.limit, humanize });
14
+ const header = event.thread_ts
15
+ ? `Slack thread reply. mikan has ${recent.some((l) => l.isMikan) ? "already replied" : "not replied"} in this thread.`
16
+ : "Slack channel top-level message.";
17
+ return [
18
+ header,
19
+ "The AI assistant is named mikan; mentions of other people appear as @Name.",
20
+ "",
21
+ "Recent messages (oldest first):",
22
+ formatRecentScope(recent),
23
+ "",
24
+ `NEW message from ${options.speaker ?? event.user}:`,
25
+ humanize(event.text),
26
+ ].join("\n");
27
+ }
28
+ //# 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":"AAAA,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,GAEhB,MAAM,kBAAkB,CAAC;AAE1B,8EAA8E;AAC9E,MAAM,CAAC,MAAM,0BAA0B,GACrC,mWAAmW,CAAC;AAEtW;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,eAAuB,EACvB,KAAqE,EACrE,OAAO,GAKH,EAAE;IAEN,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;IAC9F,MAAM,MAAM,GAAG,eAAe,CAAC,eAAe,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC3F,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS;QAC5B,CAAC,CAAC,iCAAiC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,aAAa,kBAAkB;QACtH,CAAC,CAAC,kCAAkC,CAAC;IACvC,OAAO;QACL,MAAM;QACN,4EAA4E;QAC5E,EAAE;QACF,iCAAiC;QACjC,iBAAiB,CAAC,MAAM,CAAC;QACzB,EAAE;QACF,oBAAoB,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG;QACpD,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC","sourcesContent":["import {\n formatRecentScope,\n humanizeMentions,\n readRecentScope,\n type SlackNameResolver,\n} from \"./jev-context.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? A message that @-mentions a specific person other than mikan is directed at that person.\";\n\n/**\n * Build the shared `state` Jev scores when deciding whether an unaddressed\n * shared-channel message is meant for mikan: the surrounding scope plus, for\n * a thread, whether mikan has already taken part in it. Mentions are shown\n * as names so \"@someone else\" reads as such.\n */\nexport function buildAutoReplyState(\n conversationDir: string,\n event: { ts: string; thread_ts?: string; user: string; text: string },\n options: {\n speaker?: string;\n limit?: number;\n resolveName?: SlackNameResolver;\n botUserId?: string | null;\n } = {},\n): string {\n const resolve = options.resolveName ?? (() => undefined);\n const humanize = (text: string) => humanizeMentions(text, resolve, options.botUserId ?? null);\n const recent = readRecentScope(conversationDir, event, { limit: options.limit, humanize });\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 return [\n header,\n \"The AI assistant is named mikan; mentions of other people appear as @Name.\",\n \"\",\n \"Recent messages (oldest first):\",\n formatRecentScope(recent),\n \"\",\n `NEW message from ${options.speaker ?? event.user}:`,\n humanize(event.text),\n ].join(\"\\n\");\n}\n"]}
@@ -146,6 +146,7 @@ export declare class SlackMessagingBot implements MessagingBot {
146
146
  private getQueue;
147
147
  private resolveQueueKey;
148
148
  private isTaskThread;
149
+ private hasRunningTaskThread;
149
150
  private hasKnownThreadSession;
150
151
  /**
151
152
  * Slack's own conversation vocabulary for this channel, from the metadata
@@ -184,15 +185,24 @@ export declare class SlackMessagingBot implements MessagingBot {
184
185
  private setupEventHandlers;
185
186
  private handleAppMention;
186
187
  private admitHumanMessage;
188
+ /**
189
+ * Handle a DM message that may be about a background task without spending
190
+ * a model turn: a pure status question is answered from observation, and a
191
+ * supplement is steered into the running task. Jev decides which (falling
192
+ * back to the regex status shortcut); anything else returns false so the
193
+ * message becomes an ordinary conversation turn.
194
+ */
187
195
  private deliverTaskUpdate;
188
196
  /**
189
197
  * Ask Jev a single typed yes/no question about whether this unaddressed
190
- * shared-channel message addresses mikan. Only called in `jev` auto-reply
191
- * mode, and only awaited by the caller in that mode, so `on`/`off` stay
192
- * synchronous (magic-word `stop` handling depends on that). Any failure
193
- * (including a missing `OPENROUTER_API_KEY`) fails closed to "not
194
- * addressed" so a misconfiguration cannot make the bot noisy in a shared
195
- * channel.
198
+ * shared-channel message (top-level or thread reply) addresses mikan. The
199
+ * scored state carries the surrounding scope from log.jsonl, since a bare
200
+ * thread reply cannot be judged from its own text. Only called in `jev`
201
+ * auto-reply mode, and only awaited by the caller in that mode, so
202
+ * `on`/`off` stay synchronous (magic-word `stop` handling depends on that).
203
+ * Any failure (including a missing `OPENROUTER_API_KEY`) fails closed to
204
+ * "not addressed" so a misconfiguration cannot make the bot noisy in a
205
+ * shared channel.
196
206
  */
197
207
  private evaluateJevAddressed;
198
208
  private handleMessageEvent;
@@ -1 +1 @@
1
- {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/bot.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,YAAY,EAEjB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAI1B,KAAK,aAAa,EAElB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAA8C,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAOnG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,KAAK,EAGV,YAAY,EAEZ,SAAS,EACV,MAAM,YAAY,CAAC;AAiBpB,OAAO,EASL,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AAMxB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAuEzD,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAwB,GAAG,MAAM,EAAE,CAOrF;AAuDD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5D;AAMD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAoBtE,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,SAAS,CAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAY;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoC;IAClE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,SAAS,CAAuB;IAExC,OAAO,CAAC,KAAK,CAAgC;IAC7C,OAAO,CAAC,QAAQ,CAAmC;IACnD,gFAAgF;IAChF,OAAO,CAAC,gBAAgB,CAAiC;IACzD;;;;;OAKG;IACH,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,cAAc,CAA+B;IAErD,gDAAgD;IAChD,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,aAAa;IAoGrB,YACE,OAAO,EAAE,qBAAqB,EAC9B,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,SAAS,CAAA;KAAE,EAiBrE;IAED,iBAAiB,CAAC,SAAS,EAAE,cAAc,GAAG,IAAI,CAEjD;IAMK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAyB3B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ1B;IAED,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAE7C;IAED,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEtD;IAED,WAAW,IAAI,SAAS,EAAE,CAEzB;IAED,cAAc,IAAI,YAAY,EAAE,CAE/B;IAED,OAAO,CAAC,eAAe;IASvB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAIjB,WAAW,CACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAWjB;IAEK,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYlF;IAEK,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAUf;IAEK,mBAAmB,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAEf;IAEK,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAWjB;IAEK,mBAAmB,CACvB,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,IAAI,CAAC,CAIf;IAEK,WAAW,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErF;IAEK,qBAAqB,CACzB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAA;KAAE,GACtC,OAAO,CAAC,IAAI,CAAC,CAUf;IAEK,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAS5D;IAED;;;;;;OAMG;IACG,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,sBAAsB,EAAE,CAAC,CA8CnC;IAED,oEAAoE;IAC9D,SAAS,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAQ7C;IAEK,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW5E;IAED;;;;OAIG;IACH,qBAAqB,IAAI,OAAO,CAE/B;IAED;;;;;;;;;;;;;OAaG;IACG,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC,CAgBjB;IAED,+DAA+D;IACzD,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlF;YAEa,iBAAiB;IAYzB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlE;IAEK,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9D;IAMD,sEAAsE;IAChE,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBzF;IAED,uDAAuD;IACjD,4BAA4B,CAChC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,eAAe,EAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CAUf;IAED,mFAAmF;IAC7E,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQvF;IAED,kFAAkF;IAClF,OAAO,CAAC,YAAY;IAUpB;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAWpC;;;;;OAKG;IACH,OAAO,CAAC,8BAA8B;IA2BhC,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAIjB;IAEK,kBAAkB,CACtB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,MAAM,CAAC,CAEjB;IAEK,UAAU,CACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAYf;IAED,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9C;IAED,cAAc,CACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,IAAI,CAWN;IAED,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAkB3E;IAED,gBAAgB,IAAI,aAAa,CAiBhC;IAMD;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CA4E9C;IAMD,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,qBAAqB;IAO7B;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAWtB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAkB5B,OAAO,CAAC,yBAAyB;IAmDjC,OAAO,CAAC,kBAAkB;IA4D1B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IA+C3B,OAAO,CAAC,aAAa;IA6BrB,OAAO,CAAC,qBAAqB;IAqE7B,OAAO,CAAC,sBAAsB;IA6D9B;;;;;OAKG;YACW,iBAAiB;IAyB/B,OAAO,CAAC,kBAAkB;YA8CZ,gBAAgB;IAkE9B,OAAO,CAAC,iBAAiB;YAmCX,iBAAiB;IAsC/B;;;;;;;;OAQG;YACW,oBAAoB;YAmCpB,kBAAkB;YA2GlB,kBAAkB;IAyChC,OAAO,CAAC,mBAAmB;YA0Bb,iBAAiB;IAmD/B,OAAO,CAAC,sBAAsB;IAkF9B;;;;OAIG;YACW,kBAAkB;IA+BhC,6EAA6E;YAC/D,iBAAiB;IAmB/B;;OAEG;YACW,cAAc;YA4Bd,8BAA8B;YAwC9B,qBAAqB;IAqBnC,OAAO,CAAC,qBAAqB;YAcf,eAAe;YAuEf,mBAAmB;YAsCnB,UAAU;YA0BV,aAAa;CAmD5B"}
1
+ {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/bot.ts"],"names":[],"mappings":"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"}
@@ -22,7 +22,9 @@ import { isSlackThreadSessionKey, planSlackAdapterSession, planSlackEventAnchorR
22
22
  import { reportUserFacingError } from "../../observability/index.js";
23
23
  import { recordSlackUpdate } from "./update-diagnostics.js";
24
24
  import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
25
- import { querySlackTasks, isTaskStatusQuestion, formatTaskStatus, readTaskRoots, } from "./task-status.js";
25
+ import { querySlackTasks, formatTaskStatus, readTaskRoots, isTaskStatusQuestion, } from "./task-status.js";
26
+ import { buildAutoReplyState, JEV_ADDRESSED_INSTRUCTIONS } from "./auto-reply-context.js";
27
+ import { buildTaskIntentState, classifyTaskIntent } from "./task-intent.js";
26
28
  import { StreamStartLimiter } from "./stream-limits.js";
27
29
  const SLACK_EVENT_ANCHOR_TEXT = "Working on it...";
28
30
  function hasMessageContent(message, includeBlocks = true) {
@@ -797,6 +799,16 @@ export class SlackMessagingBot {
797
799
  isTaskThread(channel, root) {
798
800
  return readTaskRoots(this.conversationDir(channel)).has(root);
799
801
  }
802
+ hasRunningTaskThread(channel) {
803
+ const roots = readTaskRoots(this.conversationDir(channel));
804
+ if (!roots.size)
805
+ return false;
806
+ return this.handler
807
+ .getRunningSessions()
808
+ .some((s) => s.address.platform === "slack" &&
809
+ s.address.conversationId === channel &&
810
+ [...roots.keys()].some((root) => resolveSlackSessionKey(channel, root) === s.sessionKey));
811
+ }
800
812
  hasKnownThreadSession(conversationId, sessionKey) {
801
813
  return hasMaterializedChatSession({
802
814
  conversationDir: this.conversationDir(conversationId),
@@ -1257,6 +1269,13 @@ export class SlackMessagingBot {
1257
1269
  ack();
1258
1270
  return false;
1259
1271
  }
1272
+ /**
1273
+ * Handle a DM message that may be about a background task without spending
1274
+ * a model turn: a pure status question is answered from observation, and a
1275
+ * supplement is steered into the running task. Jev decides which (falling
1276
+ * back to the regex status shortcut); anything else returns false so the
1277
+ * message becomes an ordinary conversation turn.
1278
+ */
1260
1279
  async deliverTaskUpdate(event, attachmentsPromise) {
1261
1280
  if (matchMagicWord(event.text) === "stop" || event.text.startsWith("/"))
1262
1281
  return false;
@@ -1266,11 +1285,24 @@ export class SlackMessagingBot {
1266
1285
  ? this.postInThread(event.channel, event.thread_ts, text)
1267
1286
  : this.postMessage(event.channel, text);
1268
1287
  try {
1269
- if (!event.attachments?.length && isTaskStatusQuestion(event.text)) {
1270
- const tasks = await context.responder.getTaskStatus(event.thread_ts ? event.sessionKey : undefined);
1271
- if (!tasks.length)
1272
- return false;
1273
- const active = tasks.filter((t) => t.status === "running" || t.status === "stopping");
1288
+ const tasks = await context.responder.getTaskStatus(event.thread_ts ? event.sessionKey : undefined);
1289
+ if (!tasks.length)
1290
+ return false;
1291
+ const active = tasks.filter((t) => t.status === "running" || t.status === "stopping");
1292
+ const user = this.users.get(event.user);
1293
+ // A message carrying files is never a pure status question; in a task
1294
+ // thread it is a supplement, at top level an ordinary turn.
1295
+ const intent = event.attachments?.length
1296
+ ? event.thread_ts
1297
+ ? "steer"
1298
+ : "request"
1299
+ : await classifyTaskIntent(buildTaskIntentState(this.conversationDir(event.channel), event, {
1300
+ speaker: user?.displayName ?? user?.userName,
1301
+ tasks,
1302
+ resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
1303
+ botUserId: this.botUserId,
1304
+ }), event.text, { conversationId: event.channel, inTaskThread: !!event.thread_ts });
1305
+ if (intent === "status") {
1274
1306
  const selection = event.thread_ts ? tasks : active.length ? active : tasks.slice(0, 1);
1275
1307
  const text = selection.length > 1
1276
1308
  ? "目前有多個任務在處理,請到你想查詢的任務對話串詢問,避免弄錯。"
@@ -1279,9 +1311,24 @@ export class SlackMessagingBot {
1279
1311
  this.logBotResponse(event.channel, text, ts, event.thread_ts);
1280
1312
  return true;
1281
1313
  }
1282
- if (event.thread_ts && (await this.handler.steer?.(context.message))) {
1283
- await post("收到補充,會在下一個處理步驟納入;目前的操作不會立即中斷。");
1284
- return true;
1314
+ if (intent === "steer") {
1315
+ // In a thread the message already targets that task; at top level it
1316
+ // can only be steered when exactly one task is running.
1317
+ const target = event.thread_ts
1318
+ ? context.message
1319
+ : active.length === 1
1320
+ ? {
1321
+ ...context.message,
1322
+ sessionKey: active[0].sessionKey,
1323
+ threadTs: active[0].threadTs,
1324
+ }
1325
+ : undefined;
1326
+ if (target && (await this.handler.steer?.(target))) {
1327
+ const text = "收到補充,會在下一個處理步驟納入;目前的操作不會立即中斷。";
1328
+ const ts = await post(text);
1329
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
1330
+ return true;
1331
+ }
1285
1332
  }
1286
1333
  }
1287
1334
  catch (error) {
@@ -1292,22 +1339,27 @@ export class SlackMessagingBot {
1292
1339
  }
1293
1340
  /**
1294
1341
  * Ask Jev a single typed yes/no question about whether this unaddressed
1295
- * shared-channel message addresses mikan. Only called in `jev` auto-reply
1296
- * mode, and only awaited by the caller in that mode, so `on`/`off` stay
1297
- * synchronous (magic-word `stop` handling depends on that). Any failure
1298
- * (including a missing `OPENROUTER_API_KEY`) fails closed to "not
1299
- * addressed" so a misconfiguration cannot make the bot noisy in a shared
1300
- * channel.
1342
+ * shared-channel message (top-level or thread reply) addresses mikan. The
1343
+ * scored state carries the surrounding scope from log.jsonl, since a bare
1344
+ * thread reply cannot be judged from its own text. Only called in `jev`
1345
+ * auto-reply mode, and only awaited by the caller in that mode, so
1346
+ * `on`/`off` stay synchronous (magic-word `stop` handling depends on that).
1347
+ * Any failure (including a missing `OPENROUTER_API_KEY`) fails closed to
1348
+ * "not addressed" so a misconfiguration cannot make the bot noisy in a
1349
+ * shared channel.
1301
1350
  */
1302
1351
  async evaluateJevAddressed(event) {
1303
1352
  if (!event.text.trim() || matchMagicWord(event.text) === "stop")
1304
1353
  return false;
1305
1354
  try {
1306
- const result = await evaluateWithJev(event.text, {
1307
- addressed: {
1308
- type: "boolean",
1309
- instructions: "Does this Slack channel message address, ask, or request the mikan AI assistant to do something, rather than being casual conversation between humans that needs no reply?",
1310
- },
1355
+ const user = this.users.get(event.user);
1356
+ const state = buildAutoReplyState(this.conversationDir(event.channel), event, {
1357
+ speaker: user?.displayName ?? user?.userName,
1358
+ resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
1359
+ botUserId: this.botUserId,
1360
+ });
1361
+ const result = await evaluateWithJev(state, {
1362
+ addressed: { type: "boolean", instructions: JEV_ADDRESSED_INSTRUCTIONS },
1311
1363
  });
1312
1364
  const probability = result.answers.addressed.probability;
1313
1365
  log.logInfo(`[${event.channel}] jev auto-reply: probability=${probability.toFixed(2)} addressed=${probability > 0.5} text="${event.text.slice(0, 80)}"`);
@@ -1344,7 +1396,6 @@ export class SlackMessagingBot {
1344
1396
  ack();
1345
1397
  return;
1346
1398
  }
1347
- const isThreadReply = !!e.thread_ts;
1348
1399
  const sessionKey = isDM ? resolveSlackSessionKey(e.channel, e.thread_ts) : undefined;
1349
1400
  // Name the conversation from the first thing the person said. Guarded on
1350
1401
  // the assistant registry, so classic DM threads are untouched, and it
@@ -1375,22 +1426,24 @@ export class SlackMessagingBot {
1375
1426
  ack();
1376
1427
  return;
1377
1428
  }
1378
- if (!isDM && isThreadReply && matchMagicWord(slackEvent.text) !== "stop") {
1379
- void attachmentsPromise.catch((err) => {
1380
- log.logWarning("Failed to log Slack message", String(err));
1381
- });
1382
- ack();
1383
- return;
1384
- }
1385
1429
  const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
1386
1430
  slackEvent.sessionKey = activeSessionKey;
1387
- const taskThread = isDM && !!e.thread_ts && this.isTaskThread(e.channel, e.thread_ts);
1388
- const taskControl = taskThread || (isDM && !e.thread_ts && isTaskStatusQuestion(slackEvent.text));
1431
+ // Task threads always go through the task gate. Top-level DMs do only
1432
+ // while a task is running (so supplements can be steered) or when the
1433
+ // regex already recognises a status question about a finished one, so
1434
+ // idle DMs never pay a Jev round-trip.
1435
+ const taskControl = isDM &&
1436
+ (e.thread_ts
1437
+ ? this.isTaskThread(e.channel, e.thread_ts)
1438
+ : this.hasRunningTaskThread(e.channel) || isTaskStatusQuestion(slackEvent.text));
1389
1439
  if (taskControl) {
1390
1440
  ack();
1391
1441
  if (await this.deliverTaskUpdate(slackEvent, attachmentsPromise))
1392
1442
  return;
1393
1443
  }
1444
+ // Shared-channel messages without a mention, top-level or in a thread,
1445
+ // all go through the same auto-reply gate; jev mode sees the surrounding
1446
+ // scope so thread follow-ups are judged in context rather than dropped.
1394
1447
  const autoReplyMode = isDM
1395
1448
  ? "off"
1396
1449
  : slackConversationAutoReplyMode(this.workspace.office(slackEvent.address));