@geminixiang/mikan 1.0.0-beta.67 → 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 +14 -0
  2. package/dist/adapters/slack/auto-reply-context.d.ts +7 -5
  3. package/dist/adapters/slack/auto-reply-context.d.ts.map +1 -1
  4. package/dist/adapters/slack/auto-reply-context.js +11 -53
  5. package/dist/adapters/slack/auto-reply-context.js.map +1 -1
  6. package/dist/adapters/slack/bot.d.ts +8 -0
  7. package/dist/adapters/slack/bot.d.ts.map +1 -1
  8. package/dist/adapters/slack/bot.js +65 -11
  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,20 @@ 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
+
12
26
  ## [1.0.0-beta.67]
13
27
 
14
28
  ### Changed
@@ -1,11 +1,11 @@
1
+ import { type SlackNameResolver } from "./jev-context.js";
1
2
  /** 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
+ 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.";
3
4
  /**
4
5
  * 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.
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
9
  */
10
10
  export declare function buildAutoReplyState(conversationDir: string, event: {
11
11
  ts: string;
@@ -15,5 +15,7 @@ export declare function buildAutoReplyState(conversationDir: string, event: {
15
15
  }, options?: {
16
16
  speaker?: string;
17
17
  limit?: number;
18
+ resolveName?: SlackNameResolver;
19
+ botUserId?: string | null;
18
20
  }): string;
19
21
  //# sourceMappingURL=auto-reply-context.d.ts.map
@@ -1 +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"}
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"}
@@ -1,70 +1,28 @@
1
- import { join } from "node:path";
2
- import { isRecord, readTextFileIfExists } from "../../file-guards.js";
1
+ import { formatRecentScope, humanizeMentions, readRecentScope, } from "./jev-context.js";
3
2
  /** 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
- }
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.";
45
4
  /**
46
5
  * 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.
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.
51
9
  */
52
10
  export function buildAutoReplyState(conversationDir, event, options = {}) {
53
- const recent = readRecentScope(conversationDir, event, options.limit ?? 12);
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 });
54
14
  const header = event.thread_ts
55
15
  ? `Slack thread reply. mikan has ${recent.some((l) => l.isMikan) ? "already replied" : "not replied"} in this thread.`
56
16
  : "Slack channel top-level message.";
57
- const context = recent.length
58
- ? recent.map((l) => `- ${l.speaker}: ${l.text}`).join("\n")
59
- : "(none)";
60
17
  return [
61
18
  header,
19
+ "The AI assistant is named mikan; mentions of other people appear as @Name.",
62
20
  "",
63
21
  "Recent messages (oldest first):",
64
- context,
22
+ formatRecentScope(recent),
65
23
  "",
66
24
  `NEW message from ${options.speaker ?? event.user}:`,
67
- event.text,
25
+ humanize(event.text),
68
26
  ].join("\n");
69
27
  }
70
28
  //# sourceMappingURL=auto-reply-context.js.map
@@ -1 +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"]}
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,6 +185,13 @@ 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
@@ -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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"}
@@ -22,8 +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
26
  import { buildAutoReplyState, JEV_ADDRESSED_INSTRUCTIONS } from "./auto-reply-context.js";
27
+ import { buildTaskIntentState, classifyTaskIntent } from "./task-intent.js";
27
28
  import { StreamStartLimiter } from "./stream-limits.js";
28
29
  const SLACK_EVENT_ANCHOR_TEXT = "Working on it...";
29
30
  function hasMessageContent(message, includeBlocks = true) {
@@ -798,6 +799,16 @@ export class SlackMessagingBot {
798
799
  isTaskThread(channel, root) {
799
800
  return readTaskRoots(this.conversationDir(channel)).has(root);
800
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
+ }
801
812
  hasKnownThreadSession(conversationId, sessionKey) {
802
813
  return hasMaterializedChatSession({
803
814
  conversationDir: this.conversationDir(conversationId),
@@ -1258,6 +1269,13 @@ export class SlackMessagingBot {
1258
1269
  ack();
1259
1270
  return false;
1260
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
+ */
1261
1279
  async deliverTaskUpdate(event, attachmentsPromise) {
1262
1280
  if (matchMagicWord(event.text) === "stop" || event.text.startsWith("/"))
1263
1281
  return false;
@@ -1267,11 +1285,24 @@ export class SlackMessagingBot {
1267
1285
  ? this.postInThread(event.channel, event.thread_ts, text)
1268
1286
  : this.postMessage(event.channel, text);
1269
1287
  try {
1270
- if (!event.attachments?.length && isTaskStatusQuestion(event.text)) {
1271
- const tasks = await context.responder.getTaskStatus(event.thread_ts ? event.sessionKey : undefined);
1272
- if (!tasks.length)
1273
- return false;
1274
- 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") {
1275
1306
  const selection = event.thread_ts ? tasks : active.length ? active : tasks.slice(0, 1);
1276
1307
  const text = selection.length > 1
1277
1308
  ? "目前有多個任務在處理,請到你想查詢的任務對話串詢問,避免弄錯。"
@@ -1280,9 +1311,24 @@ export class SlackMessagingBot {
1280
1311
  this.logBotResponse(event.channel, text, ts, event.thread_ts);
1281
1312
  return true;
1282
1313
  }
1283
- if (event.thread_ts && (await this.handler.steer?.(context.message))) {
1284
- await post("收到補充,會在下一個處理步驟納入;目前的操作不會立即中斷。");
1285
- 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
+ }
1286
1332
  }
1287
1333
  }
1288
1334
  catch (error) {
@@ -1309,6 +1355,8 @@ export class SlackMessagingBot {
1309
1355
  const user = this.users.get(event.user);
1310
1356
  const state = buildAutoReplyState(this.conversationDir(event.channel), event, {
1311
1357
  speaker: user?.displayName ?? user?.userName,
1358
+ resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
1359
+ botUserId: this.botUserId,
1312
1360
  });
1313
1361
  const result = await evaluateWithJev(state, {
1314
1362
  addressed: { type: "boolean", instructions: JEV_ADDRESSED_INSTRUCTIONS },
@@ -1380,8 +1428,14 @@ export class SlackMessagingBot {
1380
1428
  }
1381
1429
  const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
1382
1430
  slackEvent.sessionKey = activeSessionKey;
1383
- const taskThread = isDM && !!e.thread_ts && this.isTaskThread(e.channel, e.thread_ts);
1384
- 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));
1385
1439
  if (taskControl) {
1386
1440
  ack();
1387
1441
  if (await this.deliverTaskUpdate(slackEvent, attachmentsPromise))