@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.
- package/CHANGELOG.md +21 -0
- package/dist/adapters/slack/auto-reply-context.d.ts +21 -0
- package/dist/adapters/slack/auto-reply-context.d.ts.map +1 -0
- package/dist/adapters/slack/auto-reply-context.js +28 -0
- package/dist/adapters/slack/auto-reply-context.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +16 -6
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +83 -30
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/jev-context.d.ts +30 -0
- package/dist/adapters/slack/jev-context.d.ts.map +1 -0
- package/dist/adapters/slack/jev-context.js +67 -0
- package/dist/adapters/slack/jev-context.js.map +1 -0
- package/dist/adapters/slack/task-intent.d.ts +32 -0
- package/dist/adapters/slack/task-intent.d.ts.map +1 -0
- package/dist/adapters/slack/task-intent.js +72 -0
- package/dist/adapters/slack/task-intent.js.map +1 -0
- package/dist/adapters/slack/task-status.d.ts.map +1 -1
- package/dist/adapters/slack/task-status.js +6 -0
- package/dist/adapters/slack/task-status.js.map +1 -1
- package/dist/harness/index.d.ts +1 -1
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/jev.d.ts +26 -6
- package/dist/harness/jev.d.ts.map +1 -1
- package/dist/harness/jev.js +17 -3
- package/dist/harness/jev.js.map +1 -1
- package/dist/harness/presenter.d.ts.map +1 -1
- package/dist/harness/presenter.js +4 -3
- package/dist/harness/presenter.js.map +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +1 -0
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/subagent-profiles.js +5 -5
- package/dist/harness/subagent-profiles.js.map +1 -1
- package/dist/harness/tools/index.d.ts.map +1 -1
- package/dist/harness/tools/index.js +3 -0
- package/dist/harness/tools/index.js.map +1 -1
- package/dist/harness/tools/jev.d.ts +21 -0
- package/dist/harness/tools/jev.d.ts.map +1 -0
- package/dist/harness/tools/jev.js +109 -0
- package/dist/harness/tools/jev.js.map +1 -0
- package/dist/sessions/session-store.d.ts +2 -0
- package/dist/sessions/session-store.d.ts.map +1 -1
- package/dist/sessions/session-store.js +1 -0
- package/dist/sessions/session-store.js.map +1 -1
- 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
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
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;
|
|
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;AAyEzD,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,oBAAoB;IAa5B,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;IAmCzB;;;;;;OAMG;YACW,iBAAiB;IAsE/B;;;;;;;;;;OAUG;YACW,oBAAoB;YAqCpB,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"}
|
|
@@ -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,
|
|
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
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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 (
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
|
1296
|
-
*
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1299
|
-
*
|
|
1300
|
-
*
|
|
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
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
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
|
-
|
|
1388
|
-
|
|
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));
|