@ycodium-ai/plugin-im 0.2.3250 → 0.2.3254

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/README.md CHANGED
@@ -260,9 +260,9 @@ is sent to the chat. `chat_type` (1 single, 2 group) comes from the last inbound
260
260
  conversation and is left out when none has arrived since the plugin started, as plain text does.
261
261
  A picture goes as `image` only when it is PNG, JPEG or GIF within 10 MB (`imageMimeTypes`).
262
262
 
263
- Chat commands: `/stop`, `/continue`, `/status`, `/new`, `/sessions`, `/resume`, `/history [count]`, `/model`,
264
- `/plan`, `/repo`, `/group`, `/team`, `/thread`, `/help`, `/panel`. In a chat with no project
265
- `/sessions`, `/resume` and the team commands answer with the project picker. `/sessions` and `/resume` only list and
263
+ Chat commands: `/stop`, `/continue`, `/status`, `/version`, `/new`, `/sessions`, `/resume`, `/history [count]`, `/model`,
264
+ `/reasoning`, `/plan`, `/repo`, `/group`, `/team`, `/thread`, `/help`, `/panel`. In a chat with no project
265
+ `/sessions`, `/resume` and the team commands answer with the project picker. `/version` still answers. `/sessions` and `/resume` only list and
266
266
  switch threads this plugin opened, since the host lets a plugin send only into
267
267
  its own threads. `/continue` calls `threads.turnQueue.resume` for the chat's thread
268
268
  when its queue is paused (a `/stop` or a failure pauses it), which is how WeCom
@@ -270,8 +270,12 @@ resumes a paused queue from the chat; it is not `/resume`, which already switche
270
270
 
271
271
  `/history [count]` previews the current session's recent user and assistant messages in private chats only (default 3, maximum 5), oldest first. It does not start a turn or change the session. Images and files appear as placeholders; long text is split for the platform, with a notice when the preview text is incomplete. Type the command in the chat; panels and native command menus do not list it. With the reply language set to Chinese, `会话历史 [数量]` works too.
272
272
 
273
+ `/version` answers with one line, the plugin name and its version. It takes no arguments; if anything follows it, the reply is a usage reminder. It works in private chats and groups, does not read the session, and does not start a turn. Type it; panels and native command menus do not list it. There is no Chinese alias.
274
+
273
275
  On Lark, Telegram, and the WeCom smart bot, `/panel` sends a command panel, and `/help` stays text and ends with a pointer to it. WeCom App does not implement the panel surface, so `/panel` answers with the text list and `/help` does not mention `/panel`. Lark cards and Telegram inline keyboards show all command buttons. The WeCom smart bot uses a template card from `src/wecomPanel.ts`; its main menu is limited to six buttons. Commands that do not fit are listed in `/help` and can be typed into the chat. Commands that take an argument (a thread, a project, a model) open a second view on the same message with a Back button. These lists let you reach every option by paging: Lark and Telegram show eight items per page, and WeCom shows three, leaving room for Back, Previous, and Next. A tap on the WeCom card updates that same card; when the update cannot be applied, `src/panelController.ts` posts a new card and the old one stops accepting taps. A busy placeholder is not drawn on that card: it stays as it is until the next interactive view. Telegram buttons carry only a short `pn:<version>:<index>` token (`callback_data` is capped at 64 bytes); the action stays in memory and a tap on an older keyboard version counts as expired. Each card keeps its own view stack in memory (`src/panelController.ts`), so after a restart an old card only answers that it has expired. A tap runs the same handler as the typed command and waits in the same per-chat queue as typed messages. Another platform opts in by implementing the optional `panel` surface on its adapter (`src/adapter.ts`).
274
276
 
277
+ `/reasoning` shows the current model's reasoning efforts; `/reasoning <value>` or `/reasoning <number>` switches it. Numbers follow the available values at the time you send the command, so send `/reasoning` first to check the choices. See [Model and Plan Mode](../../docs/user/im-bots.md#model-and-plan-mode) for details.
278
+
275
279
  `/team` uses the chat's selected project. It reads the roster from the `team` plugin and
276
280
  each text message invokes `startGoal` with a stable request ID derived from the bot,
277
281
  platform, chat, and inbound message ID. A successful goal binds the chat to the lead's
package/dist/index.js CHANGED
@@ -126699,7 +126699,18 @@ var readChatPatch = (data) => {
126699
126699
  else if (isRecord(data["model"])) {
126700
126700
  const instanceId = nonEmpty3(data["model"]["instanceId"]);
126701
126701
  const model = nonEmpty3(data["model"]["model"]);
126702
- if (instanceId !== null && model !== null) patch.model = { instanceId, model };
126702
+ if (instanceId !== null && model !== null) {
126703
+ const options = data["model"]["options"];
126704
+ patch.model = {
126705
+ instanceId,
126706
+ model,
126707
+ ...Array.isArray(options) ? {
126708
+ options: options.flatMap(
126709
+ (entry) => isRecord(entry) && nonEmpty3(entry["id"]) !== null && (typeof entry["value"] === "string" || typeof entry["value"] === "boolean") ? [{ id: String(entry["id"]), value: entry["value"] }] : []
126710
+ )
126711
+ } : {}
126712
+ };
126713
+ }
126703
126714
  }
126704
126715
  if (data["preset"] === null) patch.preset = null;
126705
126716
  else if (nonEmpty3(data["preset"]) !== null) patch.preset = nonEmpty3(data["preset"]);
@@ -128340,7 +128351,7 @@ var makeChatSetup = (core, deps) => {
128340
128351
  };
128341
128352
 
128342
128353
  // src/commands.ts
128343
- import { threadOrNull as threadOrNull2 } from "@ycodium-ai/plugin-api";
128354
+ import { threadOrNull as threadOrNull3 } from "@ycodium-ai/plugin-api";
128344
128355
 
128345
128356
  // ../../node_modules/.pnpm/effect@4.0.0-beta.103_patch_hash=78523ded7c3dfc3efd3cec0247f4960d00eaa3d3c96a2077457694a4d8b453c6/node_modules/effect/dist/Pipeable.js
128346
128357
  var pipeArguments = (self2, args2) => {
@@ -133557,7 +133568,10 @@ var PlanCommandMode = Literals(["toggle", "on", "off", "invalid"]);
133557
133568
  var ParsedChatCommand = Union2([
133558
133569
  SimpleChatCommand,
133559
133570
  Struct({ kind: Literal2("sessions"), arg: optionalKey2(String4) }),
133560
- Struct({ kind: Literals(["resume", "model", "repo"]), arg: String4 }),
133571
+ Struct({
133572
+ kind: Literals(["resume", "model", "reasoning", "repo", "version"]),
133573
+ arg: String4
133574
+ }),
133561
133575
  Struct({ kind: Literal2("history"), count: NullOr(Number5) }),
133562
133576
  Struct({
133563
133577
  kind: Literal2("preset"),
@@ -133603,6 +133617,8 @@ var parseChatCommand = (input) => {
133603
133617
  if (/^\/stop$/i.test(trimmed)) return { kind: "stop" };
133604
133618
  if (/^\/continue$/i.test(trimmed)) return { kind: "continue" };
133605
133619
  if (/^\/status$/i.test(trimmed)) return { kind: "status" };
133620
+ const versionArg = trimmed.match(/^\/version(?:\s+([\s\S]*))?$/iu);
133621
+ if (versionArg !== null) return { kind: "version", arg: (versionArg[1] ?? "").trim() };
133606
133622
  if (/^\/new$/i.test(trimmed)) return { kind: "new" };
133607
133623
  if (/^\/help$/i.test(trimmed)) return { kind: "help" };
133608
133624
  if (/^\/panel$/i.test(trimmed)) return { kind: "panel" };
@@ -133626,6 +133642,13 @@ var parseChatCommand = (input) => {
133626
133642
  }
133627
133643
  const model = modelArgument(input.text, input.chinese);
133628
133644
  if (model !== void 0) return { kind: "model", arg: model };
133645
+ const reasoning = commandArgument(
133646
+ trimmed,
133647
+ input.chinese,
133648
+ /^\/reasoning(?:\s+([\s\S]*))?$/i,
133649
+ /^思考强度(?:\s+([\s\S]*))?$/
133650
+ );
133651
+ if (reasoning !== void 0) return { kind: "reasoning", arg: reasoning };
133629
133652
  const preset = trimmed.match(/^\/preset(?:\s+([\s\S]*))?$/iu);
133630
133653
  if (preset !== null)
133631
133654
  return {
@@ -133656,12 +133679,14 @@ var helpText = (t, options = {}) => [
133656
133679
  t("help.stop"),
133657
133680
  t("help.continue"),
133658
133681
  t("help.status"),
133682
+ t("help.version"),
133659
133683
  t("help.new"),
133660
133684
  t("help.sessions"),
133661
133685
  t("help.resume"),
133662
133686
  t("help.history", HISTORY_COUNTS),
133663
133687
  t("help.syncNote"),
133664
133688
  t("help.model"),
133689
+ t("help.reasoning"),
133665
133690
  t("help.preset"),
133666
133691
  t("help.plan"),
133667
133692
  t("help.repo"),
@@ -133841,6 +133866,276 @@ ${body}`);
133841
133866
  };
133842
133867
  };
133843
133868
 
133869
+ // src/reasoningCommand.ts
133870
+ import { modelSelectionStringOption as modelSelectionStringOption2 } from "@ycodium-ai/plugin-api/executor";
133871
+
133872
+ // src/currentModel.ts
133873
+ import { threadOrNull as threadOrNull2 } from "@ycodium-ai/plugin-api";
133874
+
133875
+ // src/botModel.ts
133876
+ var resolveDefaultModel = async (bot, chatModel, projectDefault, providers, knownInstances) => {
133877
+ if (chatModel !== null) return chatModel;
133878
+ if (bot.defaultModel !== void 0) {
133879
+ const instances = knownInstances === void 0 ? await providers.list({}).catch(() => null) : knownInstances;
133880
+ if (!isModelCatalogComplete(instances) || modelCatalogHas(modelChoices(instances ?? []), bot.defaultModel)) {
133881
+ return bot.defaultModel;
133882
+ }
133883
+ }
133884
+ return projectDefault;
133885
+ };
133886
+
133887
+ // src/threadUsable.ts
133888
+ var threadUsable = (binding, thread) => thread !== null && thread.archivedAt == null && thread.projectId === binding.projectId;
133889
+
133890
+ // src/currentModel.ts
133891
+ var makeCurrentModelResolver = (core) => {
133892
+ const { host } = core;
133893
+ return async (binding, {
133894
+ instances,
133895
+ onReadError = (error) => {
133896
+ throw error;
133897
+ }
133898
+ } = {}) => {
133899
+ const chat = chatOf(core.state, chatKeyOf(binding));
133900
+ if (chat.threadId !== null) {
133901
+ const thread = await threadOrNull2(host.threads, chat.threadId, { archived: "include" }).catch(
133902
+ onReadError
133903
+ );
133904
+ if (threadUsable(binding, thread))
133905
+ return { selection: thread.modelSelection, threadId: chat.threadId };
133906
+ }
133907
+ if (chat.model !== null) return { selection: chat.model, threadId: null };
133908
+ const project = binding.projectId === "" ? null : await host.projects.get({ projectId: binding.projectId }).catch(onReadError);
133909
+ return {
133910
+ selection: await resolveDefaultModel(
133911
+ binding,
133912
+ chat.model,
133913
+ project?.defaultModelSelection ?? null,
133914
+ host.providers.instances,
133915
+ instances
133916
+ ),
133917
+ threadId: null
133918
+ };
133919
+ };
133920
+ };
133921
+ var saveModelSelection = async (core, binding, threadId, model) => {
133922
+ if (threadId !== null) {
133923
+ try {
133924
+ await core.host.threads.update({
133925
+ requestId: crypto.randomUUID(),
133926
+ threadId,
133927
+ modelSelection: model
133928
+ });
133929
+ } catch (error) {
133930
+ core.host.log.warn("chat could not switch the thread model", {
133931
+ error: describeError(error)
133932
+ });
133933
+ return false;
133934
+ }
133935
+ }
133936
+ core.commit("chat-updated", { chatKey: chatKeyOf(binding), model });
133937
+ return true;
133938
+ };
133939
+
133940
+ // src/reasoningOption.ts
133941
+ import { modelOptionLabelKind, modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
133942
+ var isReasoningOption = (id) => modelOptionLabelKind(id) !== void 0;
133943
+ var reasoningOption = (model) => model.options.find((option2) => isReasoningOption(option2.id) && option2.values.length > 0);
133944
+ var selectedReasoning = (selection) => {
133945
+ const option2 = selection?.options?.find((entry) => isReasoningOption(entry.id));
133946
+ if (option2 === void 0) return void 0;
133947
+ const value2 = modelSelectionStringOption(selection, option2.id);
133948
+ return value2 === void 0 ? void 0 : { id: option2.id, value: value2 };
133949
+ };
133950
+
133951
+ // src/reasoningCommand.ts
133952
+ var isDefaultArgument = (arg, chinese) => arg.toLowerCase() === "default" || chinese && arg === "\u9ED8\u8BA4";
133953
+ var makeReasoningCommand = (core, currentModel) => {
133954
+ const { host, t, deliver } = core;
133955
+ const readOptions = async (binding) => {
133956
+ const resolved = await currentModel(binding);
133957
+ const selection = resolved.selection;
133958
+ const catalog = selection === null ? null : await host.providers.models.list({ instanceId: selection.instanceId });
133959
+ const model = catalog?.models.find((entry) => entry.slug === selection?.model);
133960
+ return { ...resolved, option: model === void 0 ? void 0 : reasoningOption(model) };
133961
+ };
133962
+ return async (binding, arg) => {
133963
+ const resolved = await readOptions(binding).catch(() => null);
133964
+ if (resolved === null) {
133965
+ host.log.warn("chat could not read reasoning options");
133966
+ await deliver(binding, t("reasoning.readFailed"));
133967
+ return;
133968
+ }
133969
+ const { selection, threadId, option: option2 } = resolved;
133970
+ if (selection === null) {
133971
+ await deliver(binding, t("reasoning.noModel"));
133972
+ return;
133973
+ }
133974
+ const wanted = arg.trim();
133975
+ if (isDefaultArgument(wanted, t.language === "zh-CN")) {
133976
+ if (selectedReasoning(selection) === void 0) {
133977
+ await deliver(binding, t(threadId === null ? "reasoning.resetNext" : "reasoning.resetNow"));
133978
+ return;
133979
+ }
133980
+ const next2 = {
133981
+ ...selection,
133982
+ options: (selection.options ?? []).filter((entry) => !isReasoningOption(entry.id))
133983
+ };
133984
+ if (!await saveModelSelection(core, binding, threadId, next2)) {
133985
+ await deliver(binding, t("reasoning.setFailed"));
133986
+ return;
133987
+ }
133988
+ await deliver(binding, t(threadId === null ? "reasoning.resetNext" : "reasoning.resetNow"));
133989
+ return;
133990
+ }
133991
+ if (option2 === void 0) {
133992
+ await deliver(binding, t("reasoning.unsupported"));
133993
+ return;
133994
+ }
133995
+ if (arg === "") {
133996
+ const current = modelSelectionStringOption2(selection, option2.id);
133997
+ const listed = current !== void 0 && option2.values.includes(current);
133998
+ const title = current === void 0 ? "reasoning.listTitleDefault" : listed ? "reasoning.listTitle" : "reasoning.listTitlePlain";
133999
+ const text3 = [
134000
+ ...current === void 0 ? [] : [t(listed ? "reasoning.current" : "reasoning.currentUnlisted", { value: current })],
134001
+ ...formatChatChoices(
134002
+ option2.values,
134003
+ (entry) => entry,
134004
+ (entry) => entry === current,
134005
+ {
134006
+ title: t(title),
134007
+ more: (count) => t("reasoning.more", { count }),
134008
+ footer: t("reasoning.usage")
134009
+ }
134010
+ )
134011
+ ].join("\n").trim();
134012
+ await deliver(binding, text3);
134013
+ return;
134014
+ }
134015
+ const value2 = option2.values.find((entry) => entry === wanted) ?? pickChatChoice(wanted, option2.values, (entry, needle) => entry.toLowerCase() === needle);
134016
+ if (value2 === null) {
134017
+ const shown = option2.values.slice(0, CHAT_CHOICE_LIST_MAX);
134018
+ await deliver(
134019
+ binding,
134020
+ [
134021
+ /^\d+$/.test(wanted) ? t("reasoning.numberOutOfRange", { number: wanted, max: shown.length }) : t("reasoning.notFound", { query: arg }),
134022
+ t("reasoning.usage"),
134023
+ t("reasoning.choices", { values: shown.join(" / ") }),
134024
+ ...option2.values.length > shown.length ? [t("reasoning.more", { count: option2.values.length - shown.length })] : []
134025
+ ].join("\n")
134026
+ );
134027
+ return;
134028
+ }
134029
+ const next = {
134030
+ ...selection,
134031
+ options: [
134032
+ ...(selection.options ?? []).filter((entry) => !isReasoningOption(entry.id)),
134033
+ { id: option2.id, value: value2 }
134034
+ ]
134035
+ };
134036
+ if (!await saveModelSelection(core, binding, threadId, next)) {
134037
+ await deliver(binding, t("reasoning.setFailed"));
134038
+ return;
134039
+ }
134040
+ await deliver(
134041
+ binding,
134042
+ t(threadId === null ? "reasoning.setNext" : "reasoning.setNow", { value: value2 })
134043
+ );
134044
+ };
134045
+ };
134046
+
134047
+ // package.json
134048
+ var package_default = {
134049
+ name: "@ycodium-ai/plugin-im",
134050
+ version: "0.2.3254",
134051
+ description: "Talk to your agents from Telegram, WeCom, Lark, or WeChat (experimental).",
134052
+ keywords: [
134053
+ "im",
134054
+ "lark",
134055
+ "telegram",
134056
+ "wechat",
134057
+ "wecom",
134058
+ "wecom-app",
134059
+ "ycodium",
134060
+ "ycodium-plugin"
134061
+ ],
134062
+ license: "UNLICENSED",
134063
+ files: [
134064
+ "dist",
134065
+ "assets",
134066
+ "locales"
134067
+ ],
134068
+ type: "module",
134069
+ publishConfig: {
134070
+ access: "public"
134071
+ },
134072
+ scripts: {
134073
+ build: "node build.mjs",
134074
+ prepack: "pnpm run build",
134075
+ typecheck: "tsgo --noEmit && tsgo --noEmit -p tsconfig.ui.json",
134076
+ test: "vp test run"
134077
+ },
134078
+ devDependencies: {
134079
+ "@effect/vitest": "catalog:",
134080
+ "@larksuiteoapi/node-sdk": "^1.73.0",
134081
+ "@types/markdown-it": "^14.2.0",
134082
+ "@types/react": "~19.2.14",
134083
+ "@ycodium-ai/plugin-api": "workspace:*",
134084
+ effect: "catalog:",
134085
+ esbuild: "catalog:",
134086
+ "markdown-it": "^15.0.2",
134087
+ react: "19.2.6",
134088
+ "vite-plus": "catalog:",
134089
+ "ycodium-react-test-harness": "workspace:*"
134090
+ },
134091
+ ycodium: {
134092
+ plugin: "./dist/index.js",
134093
+ displayName: "Chat bridge",
134094
+ description: "Talk to your agents from Telegram, WeCom, Lark, or WeChat (experimental).",
134095
+ icon: "messageCircle",
134096
+ permissions: [
134097
+ {
134098
+ kind: "sessionProjection"
134099
+ },
134100
+ {
134101
+ kind: "threads.user"
134102
+ },
134103
+ {
134104
+ kind: "workspace.read"
134105
+ },
134106
+ {
134107
+ kind: "commands.dispatch",
134108
+ commands: [
134109
+ "thread.create",
134110
+ "thread.meta.update",
134111
+ "thread.external-messages.append",
134112
+ "thread.turn.start",
134113
+ "thread.turn.interrupt",
134114
+ "thread.approval.respond",
134115
+ "thread.user-input.respond",
134116
+ "thread.turn-intent.run",
134117
+ "thread.turn-intent.cancel",
134118
+ "thread.turn-queue.resume"
134119
+ ]
134120
+ },
134121
+ {
134122
+ kind: "ui.seats",
134123
+ seats: [
134124
+ "settings.section"
134125
+ ]
134126
+ }
134127
+ ],
134128
+ ui: {
134129
+ assets: "./assets",
134130
+ entry: "ui.js"
134131
+ }
134132
+ }
134133
+ };
134134
+
134135
+ // src/versionCommand.ts
134136
+ var pluginVersion = package_default.version;
134137
+ var versionReply = (t, arg) => arg === "" ? t("version.current", { version: pluginVersion }) : t("version.usage");
134138
+
133844
134139
  // src/commands.ts
133845
134140
  var NEEDS_PROJECT_COMMANDS = /* @__PURE__ */ new Set(["sessions", "resume", "selectTeam", "selectThread"]);
133846
134141
  var makeCommands = (core, deps) => {
@@ -133851,6 +134146,7 @@ var makeCommands = (core, deps) => {
133851
134146
  const { handleModel, handlePlan } = deps.modelPlan;
133852
134147
  const { openPanel } = deps;
133853
134148
  const handleHistory = makeHistoryCommand(core);
134149
+ const handleReasoning = makeReasoningCommand(core, deps.modelPlan.resolveCurrentModel);
133854
134150
  const targetOf = (binding) => chatOf(core.state, chatKeyOf(binding)).target;
133855
134151
  const executeCommand = async (binding, command, senderId = null, chatKind = void 0) => {
133856
134152
  const chatKey = chatKeyOf(binding);
@@ -133912,6 +134208,9 @@ var makeCommands = (core, deps) => {
133912
134208
  case "model":
133913
134209
  await handleModel(binding, command.arg);
133914
134210
  return;
134211
+ case "reasoning":
134212
+ await handleReasoning(binding, command.arg);
134213
+ return;
133915
134214
  case "preset":
133916
134215
  await deps.handlePreset(binding, command);
133917
134216
  return;
@@ -133922,6 +134221,9 @@ var makeCommands = (core, deps) => {
133922
134221
  bindChat(chatKey, null);
133923
134222
  await deliver(binding, t("command.newSession"));
133924
134223
  return;
134224
+ case "version":
134225
+ await deliver(binding, versionReply(t, command.arg));
134226
+ return;
133925
134227
  case "status": {
133926
134228
  const threadId = chatOf(core.state, chatKey).threadId ?? void 0;
133927
134229
  const routed = targetOf(binding);
@@ -133939,7 +134241,7 @@ var makeCommands = (core, deps) => {
133939
134241
  );
133940
134242
  return;
133941
134243
  }
133942
- const thread = await threadOrNull2(host.threads, threadId);
134244
+ const thread = await threadOrNull3(host.threads, threadId);
133943
134245
  const running = isSessionRunning(thread?.session?.status);
133944
134246
  await deliver(
133945
134247
  binding,
@@ -133989,7 +134291,7 @@ var makeCommands = (core, deps) => {
133989
134291
  await deliver(binding, t("command.stopNoSession"));
133990
134292
  return;
133991
134293
  }
133992
- const thread = await threadOrNull2(host.threads, threadId);
134294
+ const thread = await threadOrNull3(host.threads, threadId);
133993
134295
  const activeTurnId = thread?.session?.activeTurnId ?? null;
133994
134296
  if (activeTurnId === null) {
133995
134297
  await deliver(binding, t("command.stopNoTurn"));
@@ -135428,7 +135730,7 @@ var makeImCore = (input) => {
135428
135730
  };
135429
135731
 
135430
135732
  // src/conclusionDelivery.ts
135431
- import { threadOrNull as threadOrNull3 } from "@ycodium-ai/plugin-api";
135733
+ import { threadOrNull as threadOrNull4 } from "@ycodium-ai/plugin-api";
135432
135734
 
135433
135735
  // src/abortableSleep.ts
135434
135736
  var abortableSleep = (ms, signal) => new Promise((resolve2, reject) => {
@@ -135556,13 +135858,13 @@ ${text3}` : text3;
135556
135858
  const sleep4 = (ms) => abortableSleep(ms, stopped.signal).catch(() => void 0);
135557
135859
  const retryFor = (threadId, owed = ALWAYS_OWED) => ({
135558
135860
  sleep: sleep4,
135559
- shouldContinue: async () => !disposed && owed() && await threadOrNull3(core.host.threads, threadId) !== null
135861
+ shouldContinue: async () => !disposed && owed() && await threadOrNull4(core.host.threads, threadId) !== null
135560
135862
  });
135561
135863
  return {
135562
135864
  completed,
135563
135865
  retryFor,
135564
135866
  prepareConclusion: async (threadId, turnId, successful) => {
135565
- const thread = await threadOrNull3(core.host.threads, threadId);
135867
+ const thread = await threadOrNull4(core.host.threads, threadId);
135566
135868
  const final = thread === null ? void 0 : await latestMessage(
135567
135869
  core.host.threads,
135568
135870
  threadId,
@@ -146251,19 +146553,25 @@ var en_default = {
146251
146553
  project: "Project: {{title}}",
146252
146554
  noProject: "No project picked yet. Send /repo to pick one."
146253
146555
  },
146556
+ version: {
146557
+ current: "Ycodium Chat bridge v{{version}}",
146558
+ usage: "Usage: /version (no arguments)."
146559
+ },
146254
146560
  help: {
146255
146561
  title: "Commands:",
146256
146562
  continue: "\xB7 /continue \u2014 resume sending after /stop or a failure paused it; the waiting messages go out",
146257
146563
  stop: "\xB7 /stop \u2014 stop the turn that is running; the session stays",
146258
146564
  status: "\xB7 /status \u2014 what is going on right now",
146565
+ version: "\xB7 /version \u2014 the chat bridge's version (no arguments)",
146259
146566
  new: "\xB7 /new \u2014 forget the current session; the next message starts a new one",
146260
146567
  team: "\xB7 /team \u2014 give this project's team a goal with each message; use /repo to pick the project first",
146261
146568
  thread: "\xB7 /thread \u2014 go back to a normal session",
146262
- aliases: "Chinese shortcuts (\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u4F1A\u8BDD\u5386\u53F2 / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
146569
+ aliases: "Chinese shortcuts (\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u4F1A\u8BDD\u5386\u53F2 / \u6362\u6A21\u578B / \u601D\u8003\u5F3A\u5EA6 / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD) work when the reply language is Chinese.",
146263
146570
  sessions: "\xB7 /sessions \u2014 list this project's sessions; add a project number from /repo to list the ones this bot opened in another project",
146264
146571
  resume: "\xB7 /resume number-or-id-prefix \u2014 switch sessions; one from another project switches the project too",
146265
146572
  history: "\xB7 /history [count] \u2014 preview recent messages in the current session, in private chats only (default {{defaultCount}}, maximum {{maxCount}})",
146266
146573
  model: "\xB7 /model \u2014 see the model this chat uses; /model number or /model instance/model switches it",
146574
+ reasoning: "\xB7 /reasoning \u2014 see the reasoning effort for this chat's current model; /reasoning value or /reasoning number switches it; /reasoning default resets it to the model default",
146267
146575
  preset: "\xB7 /preset \u2014 see the preset this chat uses for new sessions; /preset number or /preset name-or-id switches it; /preset none clears the chat choice",
146268
146576
  plan: "\xB7 /plan \u2014 toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it",
146269
146577
  panel: "Send /panel to open the command panel with a button for every command.",
@@ -146271,6 +146579,28 @@ var en_default = {
146271
146579
  group: "\xB7 /group name \u2014 Lark only: the bot creates a group and adds you (bots on other platforms cannot)",
146272
146580
  syncNote: "\xB7 After a private chat attaches a session, web user text and final assistant text sync here (groups never sync)."
146273
146581
  },
146582
+ reasoning: {
146583
+ current: "Current reasoning effort: {{value}}",
146584
+ currentUnlisted: "Current reasoning effort: {{value}} (no longer in the list; it stays in effect until you change it)",
146585
+ resetNow: "Reasoning effort reset to the model default. The current session uses it from the next turn; new sessions in this chat use it too.",
146586
+ resetNext: "Reasoning effort reset to the model default. New sessions in this chat will use it.",
146587
+ notFound: '"{{query}}" is not an available reasoning effort.',
146588
+ numberOutOfRange: "Number {{number}} is out of range; choose 1\u2013{{max}}.",
146589
+ listTitle: "Available reasoning efforts (\u2605 current):",
146590
+ listTitleDefault: "Available reasoning efforts (none chosen; the model default is used):",
146591
+ more: "{{count}} more not shown.",
146592
+ usage: "Usage: /reasoning \u2014 see the reasoning effort for this chat's current model; /reasoning value or /reasoning number switches it; /reasoning default resets it to the model default.",
146593
+ choices: "Choices: {{values}}.",
146594
+ unsupported: "The current model does not support switching the reasoning effort.",
146595
+ readFailed: "Could not read the reasoning options. Try again in a moment.",
146596
+ setFailed: "Switching the reasoning effort failed. Try again in a moment.",
146597
+ setNow: "Reasoning effort set to {{value}}. The current session uses it from the next turn; new sessions in this chat use it too.",
146598
+ setNext: "Reasoning effort set to {{value}}. New sessions in this chat will use it.",
146599
+ cleared: "The new model does not support the previous reasoning effort ({{value}}). It was cleared; the model default will be used.",
146600
+ noModel: "This chat has no model yet. Pick one with /model first.",
146601
+ switchReadFailed: "Could not read the new model's reasoning efforts, so the model was not switched. Try again in a moment.",
146602
+ listTitlePlain: "Available reasoning efforts:"
146603
+ },
146274
146604
  history: {
146275
146605
  usage: "Usage: /history [count] \u2014 1 to {{maxCount}} (larger numbers count as {{maxCount}}), default {{defaultCount}}.",
146276
146606
  directOnly: "In a group this would show the session to everyone. Send /history in a private chat with the bot.",
@@ -147176,19 +147506,25 @@ var zh_CN_default = {
147176
147506
  project: "\u9879\u76EE\uFF1A{{title}}",
147177
147507
  noProject: "\u8FD8\u6CA1\u9009\u9879\u76EE\u3002\u53D1 /repo \u9009\u4E00\u4E2A\u3002"
147178
147508
  },
147509
+ version: {
147510
+ current: "Ycodium \u804A\u5929\u6865 v{{version}}",
147511
+ usage: "\u7528\u6CD5\uFF1A/version\uFF08\u4E0D\u5E26\u53C2\u6570\uFF09\u3002"
147512
+ },
147179
147513
  help: {
147180
147514
  title: "\u80FD\u7528\u7684\u547D\u4EE4\uFF1A",
147181
147515
  continue: "\xB7 /continue \u2014\u2014 /stop \u6216\u51FA\u9519\u6682\u505C\u4E86\u53D1\u9001\u4E4B\u540E\uFF0C\u7528\u5B83\u6062\u590D\uFF0C\u6392\u7740\u7684\u6D88\u606F\u7EE7\u7EED\u53D1\u51FA",
147182
147516
  stop: "\xB7 /stop \u2014\u2014 \u505C\u6389\u6B63\u5728\u8DD1\u7684\u8FD9\u4E00\u8F6E\uFF0C\u4F1A\u8BDD\u7559\u7740",
147183
147517
  status: "\xB7 /status \u2014\u2014 \u73B0\u5728\u5728\u5E72\u4EC0\u4E48",
147518
+ version: "\xB7 /version \u2014\u2014 \u804A\u5929\u6865\u7684\u7248\u672C\uFF08\u4E0D\u5E26\u53C2\u6570\uFF09",
147184
147519
  new: "\xB7 /new \u2014\u2014 \u5FD8\u6389\u5F53\u524D\u4F1A\u8BDD\uFF0C\u4E0B\u4E00\u53E5\u5F00\u65B0\u7684",
147185
147520
  team: "\xB7 /team \u2014\u2014 \u6BCF\u6761\u6D88\u606F\u90FD\u7ED9\u8FD9\u4E2A\u9879\u76EE\u7684\u56E2\u961F\u5F00\u4E00\u4E2A\u76EE\u6807\uFF1B\u5148\u7528 /repo \u9009\u9879\u76EE",
147186
147521
  thread: "\xB7 /thread \u2014\u2014 \u5207\u56DE\u666E\u901A\u4F1A\u8BDD",
147187
- aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u4F1A\u8BDD\u5386\u53F2 / \u6362\u6A21\u578B / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
147522
+ aliases: "\u4E2D\u6587\u4E5F\u884C\uFF1A\u505C / \u7EE7\u7EED\u53D1\u9001 / \u72B6\u6001 / \u65B0\u5BF9\u8BDD / \u4F1A\u8BDD\u5217\u8868 / \u5207\u6362\u4F1A\u8BDD / \u4F1A\u8BDD\u5386\u53F2 / \u6362\u6A21\u578B / \u601D\u8003\u5F3A\u5EA6 / \u8BA1\u5212\u6A21\u5F0F / \u6362\u9879\u76EE / \u5EFA\u7FA4 / \u6362\u56E2\u961F / \u6362\u5BF9\u8BDD\u3002",
147188
147523
  sessions: "\xB7 /sessions \u2014\u2014 \u5217\u51FA\u672C\u9879\u76EE\u7684\u4F1A\u8BDD\uFF1B\u52A0 /repo \u91CC\u7684\u9879\u76EE\u7F16\u53F7\uFF0C\u5217\u51FA\u672C\u673A\u5668\u4EBA\u5728\u522B\u7684\u9879\u76EE\u5F00\u8FC7\u7684",
147189
147524
  resume: "\xB7 /resume \u7F16\u53F7\u6216 id \u5F00\u5934 \u2014\u2014 \u5207\u4F1A\u8BDD\uFF1B\u522B\u7684\u9879\u76EE\u7684\u4F1A\u8BDD\u4F1A\u8FDE\u540C\u9879\u76EE\u4E00\u8D77\u5207",
147190
147525
  history: "\xB7 /history [\u6570\u91CF] \u2014\u2014 \u4EC5\u79C1\u804A\uFF1A\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u6700\u8FD1\u7684\u6D88\u606F\uFF08\u9ED8\u8BA4 {{defaultCount}} \u6761\uFF0C\u6700\u591A {{maxCount}} \u6761\uFF09",
147191
147526
  model: "\xB7 /model \u2014\u2014 \u770B\u8FD9\u4E2A\u804A\u5929\u7528\u7684\u6A21\u578B\uFF1B/model \u7F16\u53F7 \u6216 /model \u5B9E\u4F8B/\u6A21\u578B \u5207\u6362",
147527
+ reasoning: "\xB7 /reasoning \u2014\u2014 \u770B\u8FD9\u4E2A\u804A\u5929\u5F53\u524D\u6A21\u578B\u7684\u601D\u8003\u5F3A\u5EA6\uFF1B/reasoning \u53D6\u503C \u6216 /reasoning \u7F16\u53F7 \u5207\u6362\uFF1B/reasoning default \u6062\u590D\u6A21\u578B\u9ED8\u8BA4",
147192
147528
  preset: "\xB7 /preset \u2014\u2014 \u770B\u8FD9\u4E2A\u804A\u5929\u65B0\u4F1A\u8BDD\u7528\u7684\u9884\u8BBE\uFF1B/preset \u7F16\u53F7 \u6216 /preset \u540D\u5B57\u6216 id \u5207\u6362\uFF1B/preset none \u6E05\u6389\u804A\u5929\u9009\u62E9",
147193
147529
  plan: "\xB7 /plan \u2014\u2014 \u5F00\u5173\u8BA1\u5212\u6A21\u5F0F\uFF08agent \u5148\u51FA\u8BA1\u5212\u3001\u4E0D\u52A8\u624B\uFF09\uFF1B/plan on\u3001/plan off \u660E\u786E\u8BBE\u5B9A",
147194
147530
  panel: "\u53D1 /panel \u6253\u5F00\u53EF\u70B9\u7684\u547D\u4EE4\u9762\u677F\u3002",
@@ -147196,6 +147532,28 @@ var zh_CN_default = {
147196
147532
  group: "\xB7 /group \u7FA4\u540D \u2014\u2014 \u4EC5\u98DE\u4E66\uFF1A\u673A\u5668\u4EBA\u5EFA\u4E00\u4E2A\u7FA4\u5E76\u628A\u4F60\u62C9\u8FDB\u53BB\uFF08\u522B\u7684\u5E73\u53F0\u7684\u673A\u5668\u4EBA\u5EFA\u4E0D\u4E86\u7FA4\uFF09",
147197
147533
  syncNote: "\xB7 \u79C1\u804A\u9644\u7740\u4F1A\u8BDD\u540E\uFF0C\u7F51\u9875\u4E0A\u7684\u7528\u6237\u6587\u5B57\u4E0E\u7EC8\u6001\u52A9\u624B\u6587\u5B57\u4F1A\u540C\u6B65\u5230\u6B64\uFF08\u7FA4\u804A\u4E0D\u540C\u6B65\uFF09\u3002"
147198
147534
  },
147535
+ reasoning: {
147536
+ current: "\u5F53\u524D\u601D\u8003\u5F3A\u5EA6\uFF1A{{value}}",
147537
+ currentUnlisted: "\u5F53\u524D\u601D\u8003\u5F3A\u5EA6\uFF1A{{value}}\uFF08\u5DF2\u4E0D\u5728\u53EF\u9009\u5217\u8868\uFF0C\u6362\u6389\u4E4B\u524D\u4ECD\u751F\u6548\uFF09",
147538
+ resetNow: "\u601D\u8003\u5F3A\u5EA6\u5DF2\u6062\u590D\u4E3A\u6A21\u578B\u9ED8\u8BA4\uFF0C\u5F53\u524D\u4F1A\u8BDD\u4ECE\u4E0B\u4E00\u8F6E\u8D77\u4F7F\u7528\u5B83\uFF0C\u8FD9\u4E2A\u804A\u5929\u7684\u65B0\u4F1A\u8BDD\u4E5F\u4F7F\u7528\u5B83\u3002",
147539
+ resetNext: "\u601D\u8003\u5F3A\u5EA6\u5DF2\u6062\u590D\u4E3A\u6A21\u578B\u9ED8\u8BA4\uFF0C\u8FD9\u4E2A\u804A\u5929\u7684\u65B0\u4F1A\u8BDD\u5C06\u4F7F\u7528\u5B83\u3002",
147540
+ notFound: "\u300C{{query}}\u300D\u4E0D\u662F\u53EF\u9009\u7684\u601D\u8003\u5F3A\u5EA6\u3002",
147541
+ numberOutOfRange: "\u7F16\u53F7 {{number}} \u8D85\u51FA\u8303\u56F4\uFF0C\u53EF\u9009 1\u2013{{max}}\u3002",
147542
+ listTitle: "\u53EF\u9009\u601D\u8003\u5F3A\u5EA6\uFF08\u2605 \u5F53\u524D\uFF09\uFF1A",
147543
+ listTitleDefault: "\u53EF\u9009\u601D\u8003\u5F3A\u5EA6\uFF08\u672A\u624B\u52A8\u9009\u62E9\uFF0C\u5F53\u524D\u7528\u6A21\u578B\u9ED8\u8BA4\uFF09\uFF1A",
147544
+ more: "\u8FD8\u6709 {{count}} \u9879\u672A\u5217\u51FA\u3002",
147545
+ usage: "\u7528\u6CD5\uFF1A/reasoning \u2014\u2014 \u770B\u8FD9\u4E2A\u804A\u5929\u5F53\u524D\u6A21\u578B\u7684\u601D\u8003\u5F3A\u5EA6\uFF1B/reasoning \u53D6\u503C \u6216 /reasoning \u7F16\u53F7 \u5207\u6362\uFF1B/reasoning default \u6062\u590D\u6A21\u578B\u9ED8\u8BA4\u3002",
147546
+ choices: "\u53EF\u9009\uFF1A{{values}}\u3002",
147547
+ unsupported: "\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u5207\u6362\u601D\u8003\u5F3A\u5EA6\u3002",
147548
+ readFailed: "\u6682\u65F6\u8BFB\u4E0D\u5230\u601D\u8003\u5F3A\u5EA6\u9009\u9879\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
147549
+ setFailed: "\u5207\u6362\u601D\u8003\u5F3A\u5EA6\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
147550
+ setNow: "\u601D\u8003\u5F3A\u5EA6\u5DF2\u8BBE\u4E3A {{value}}\uFF0C\u5F53\u524D\u4F1A\u8BDD\u4ECE\u4E0B\u4E00\u8F6E\u8D77\u4F7F\u7528\u5B83\uFF0C\u8FD9\u4E2A\u804A\u5929\u7684\u65B0\u4F1A\u8BDD\u4E5F\u4F7F\u7528\u5B83\u3002",
147551
+ setNext: "\u601D\u8003\u5F3A\u5EA6\u5DF2\u8BBE\u4E3A {{value}}\uFF0C\u8FD9\u4E2A\u804A\u5929\u7684\u65B0\u4F1A\u8BDD\u5C06\u4F7F\u7528\u5B83\u3002",
147552
+ cleared: "\u65B0\u6A21\u578B\u4E0D\u652F\u6301\u539F\u6765\u7684\u601D\u8003\u5F3A\u5EA6\uFF08{{value}}\uFF09\uFF0C\u5DF2\u6E05\u9664\uFF0C\u6539\u7528\u6A21\u578B\u9ED8\u8BA4\u3002",
147553
+ noModel: "\u8FD9\u4E2A\u804A\u5929\u8FD8\u6CA1\u6709\u6A21\u578B\uFF0C\u5148\u7528 /model \u9009\u4E00\u4E2A\u3002",
147554
+ switchReadFailed: "\u6682\u65F6\u8BFB\u4E0D\u5230\u65B0\u6A21\u578B\u7684\u601D\u8003\u5F3A\u5EA6\u9009\u9879\uFF0C\u6A21\u578B\u6CA1\u6709\u5207\u6362\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
147555
+ listTitlePlain: "\u53EF\u9009\u601D\u8003\u5F3A\u5EA6\uFF1A"
147556
+ },
147199
147557
  history: {
147200
147558
  usage: "\u7528\u6CD5\uFF1A/history [\u6570\u91CF] \u2014\u2014 1\u2013{{maxCount}} \u6761\uFF08\u8D85\u51FA\u6309 {{maxCount}} \u6761\u7B97\uFF09\uFF0C\u9ED8\u8BA4 {{defaultCount}} \u6761\u3002",
147201
147559
  directOnly: "\u5728\u7FA4\u91CC\u4F1A\u628A\u4F1A\u8BDD\u5185\u5BB9\u53D1\u7ED9\u6240\u6709\u4EBA\u3002\u5728\u4E0E\u673A\u5668\u4EBA\u7684\u79C1\u804A\u91CC\u53D1 /history\u3002",
@@ -148554,47 +148912,17 @@ var makeLarkAdapter = (deps) => {
148554
148912
  };
148555
148913
 
148556
148914
  // src/modelPlan.ts
148557
- import { threadOrNull as threadOrNull4 } from "@ycodium-ai/plugin-api";
148558
-
148559
- // src/botModel.ts
148560
- var resolveDefaultModel = async (bot, chatModel, projectDefault, providers, knownInstances) => {
148561
- if (chatModel !== null) return chatModel;
148562
- if (bot.defaultModel !== void 0) {
148563
- const instances = knownInstances === void 0 ? await providers.list({}).catch(() => null) : knownInstances;
148564
- if (!isModelCatalogComplete(instances) || modelCatalogHas(modelChoices(instances ?? []), bot.defaultModel)) {
148565
- return bot.defaultModel;
148566
- }
148567
- }
148568
- return projectDefault;
148569
- };
148570
-
148571
- // src/modelPlan.ts
148572
- var makeModelPlan = (core, deps) => {
148915
+ var makeModelPlan = (core) => {
148573
148916
  const { host, t, commit, deliver } = core;
148574
- const { threadStillUsable } = deps;
148917
+ const resolveCurrentModel = makeCurrentModelResolver(core);
148575
148918
  const currentModelOf = async (binding, instances) => {
148576
- const chat = chatOf(core.state, chatKeyOf(binding));
148577
- if (chat.threadId !== null) {
148578
- const thread = await threadOrNull4(host.threads, chat.threadId).catch(() => null);
148579
- if (thread !== null) {
148580
- return {
148581
- instanceId: thread.modelSelection.instanceId,
148582
- model: thread.modelSelection.model
148583
- };
148584
- }
148585
- }
148586
- if (chat.model !== null) return chat.model;
148587
- const project = binding.projectId === "" ? null : await host.projects.get({ projectId: binding.projectId }).catch(() => null);
148588
- return resolveDefaultModel(
148589
- binding,
148590
- chat.model,
148591
- project?.defaultModelSelection ?? null,
148592
- host.providers.instances,
148593
- instances
148594
- );
148919
+ const { selection } = await resolveCurrentModel(binding, {
148920
+ instances,
148921
+ onReadError: () => null
148922
+ });
148923
+ return selection;
148595
148924
  };
148596
148925
  const handleModel = async (binding, arg) => {
148597
- const chatKey = chatKeyOf(binding);
148598
148926
  const instances = await host.providers.instances.list({}).catch(() => null);
148599
148927
  const choices = instances === null ? [] : modelChoices(instances);
148600
148928
  if (arg === "") {
@@ -148610,25 +148938,42 @@ var makeModelPlan = (core, deps) => {
148610
148938
  await deliver(binding, t("model.notFound", { query: arg }));
148611
148939
  return;
148612
148940
  }
148613
- const selection = { instanceId: picked.instanceId, model: picked.model };
148614
- commit("chat-updated", { chatKey, model: selection });
148615
- const threadId = chatOf(core.state, chatKey).threadId;
148616
- if (threadId !== null && await threadStillUsable(binding, threadId)) {
148941
+ const { selection: current, threadId } = await resolveCurrentModel(binding, {
148942
+ instances,
148943
+ onReadError: () => null
148944
+ });
148945
+ const previous = selectedReasoning(current);
148946
+ let carried;
148947
+ if (previous !== void 0) {
148617
148948
  try {
148618
- await host.threads.update({
148619
- requestId: crypto.randomUUID(),
148620
- threadId,
148621
- modelSelection: selection
148622
- });
148623
- } catch (error) {
148624
- host.log.warn("chat could not switch the thread model", { error: describeError(error) });
148625
- await deliver(binding, t("model.setFailed"));
148949
+ const catalog = await host.providers.models.list({ instanceId: picked.instanceId });
148950
+ const model = catalog.models.find((entry) => entry.slug === picked.model);
148951
+ const option2 = model === void 0 ? void 0 : reasoningOption(model);
148952
+ if (option2 !== void 0 && option2.values.includes(previous.value))
148953
+ carried = { id: option2.id, value: previous.value };
148954
+ } catch {
148955
+ host.log.warn("chat could not read reasoning options for a model switch");
148956
+ await deliver(binding, t("reasoning.switchReadFailed"));
148626
148957
  return;
148627
148958
  }
148628
- await deliver(binding, t("model.setNow", { model: modelLabel(picked) }));
148959
+ }
148960
+ const selection = {
148961
+ instanceId: picked.instanceId,
148962
+ model: picked.model,
148963
+ ...carried === void 0 ? {} : { options: [carried] }
148964
+ };
148965
+ const cleared = previous !== void 0 && carried === void 0 ? [t("reasoning.cleared", { value: previous.value })] : [];
148966
+ if (!await saveModelSelection(core, binding, threadId, selection)) {
148967
+ await deliver(binding, t("model.setFailed"));
148629
148968
  return;
148630
148969
  }
148631
- await deliver(binding, t("model.setNext", { model: modelLabel(picked) }));
148970
+ await deliver(
148971
+ binding,
148972
+ [
148973
+ t(threadId === null ? "model.setNext" : "model.setNow", { model: modelLabel(picked) }),
148974
+ ...cleared
148975
+ ].join("\n")
148976
+ );
148632
148977
  };
148633
148978
  const handlePlan = async (binding, mode) => {
148634
148979
  if (mode === "invalid") {
@@ -148645,7 +148990,7 @@ var makeModelPlan = (core, deps) => {
148645
148990
  commit("chat-updated", { chatKey, plan: next });
148646
148991
  await deliver(binding, t(next ? "plan.on" : "plan.off"));
148647
148992
  };
148648
- return { currentModelOf, handleModel, handlePlan };
148993
+ return { currentModelOf, resolveCurrentModel, handleModel, handlePlan };
148649
148994
  };
148650
148995
 
148651
148996
  // src/notifications.ts
@@ -151843,7 +152188,7 @@ var makeTurns = (core, deps) => {
151843
152188
  const threadStillUsable = async (binding, threadId) => {
151844
152189
  try {
151845
152190
  const thread = await threadOrNull8(host.threads, threadId, { archived: "include" });
151846
- return thread !== null && thread.archivedAt == null && thread.projectId === binding.projectId;
152191
+ return threadUsable(binding, thread);
151847
152192
  } catch {
151848
152193
  return true;
151849
152194
  }
@@ -152108,7 +152453,6 @@ var makeTurns = (core, deps) => {
152108
152453
  const hasWaitingAttachments = (chatKey) => (waitingFor(chatKey, chatOf(core.state, chatKey).threadId)?.attachments.length ?? 0) > 0;
152109
152454
  return {
152110
152455
  stop: () => dispatchAbort.abort(),
152111
- threadStillUsable,
152112
152456
  ensureThread,
152113
152457
  startTurn,
152114
152458
  receiveAttachmentsOnly,
@@ -155597,7 +155941,7 @@ var makeWecomAppAdapter = (deps) => {
155597
155941
  // src/index.ts
155598
155942
  var definition = {
155599
155943
  id: "im",
155600
- apiVersion: 28,
155944
+ apiVersion: 30,
155601
155945
  // 第二个参数只给插件自己的测试夹具:换掉文件 IO 与派发重试的等待。宿主只传 ctx。
155602
155946
  activate: async (ctx, testing = {}) => {
155603
155947
  const { dispatchSleep } = testing;
@@ -155742,7 +156086,7 @@ var definition = {
155742
156086
  });
155743
156087
  const projectRows = makeProjectRows(core);
155744
156088
  const sessions = makeSessions(core, projectRows);
155745
- const modelPlan = makeModelPlan(core, turns);
156089
+ const modelPlan = makeModelPlan(core);
155746
156090
  const chatQueue = makeChatQueue(core);
155747
156091
  const panelWiring = makePanelWiring(core, {
155748
156092
  enqueueForChat: chatQueue.enqueueForChat,
package/locales/en.json CHANGED
@@ -340,19 +340,25 @@
340
340
  "project": "Project: {{title}}",
341
341
  "noProject": "No project picked yet. Send /repo to pick one."
342
342
  },
343
+ "version": {
344
+ "current": "Ycodium Chat bridge v{{version}}",
345
+ "usage": "Usage: /version (no arguments)."
346
+ },
343
347
  "help": {
344
348
  "title": "Commands:",
345
349
  "continue": "· /continue — resume sending after /stop or a failure paused it; the waiting messages go out",
346
350
  "stop": "· /stop — stop the turn that is running; the session stays",
347
351
  "status": "· /status — what is going on right now",
352
+ "version": "· /version — the chat bridge's version (no arguments)",
348
353
  "new": "· /new — forget the current session; the next message starts a new one",
349
354
  "team": "· /team — give this project's team a goal with each message; use /repo to pick the project first",
350
355
  "thread": "· /thread — go back to a normal session",
351
- "aliases": "Chinese shortcuts (停 / 继续发送 / 状态 / 新对话 / 会话列表 / 切换会话 / 会话历史 / 换模型 / 计划模式 / 换项目 / 建群 / 换团队 / 换对话) work when the reply language is Chinese.",
356
+ "aliases": "Chinese shortcuts (停 / 继续发送 / 状态 / 新对话 / 会话列表 / 切换会话 / 会话历史 / 换模型 / 思考强度 / 计划模式 / 换项目 / 建群 / 换团队 / 换对话) work when the reply language is Chinese.",
352
357
  "sessions": "· /sessions — list this project's sessions; add a project number from /repo to list the ones this bot opened in another project",
353
358
  "resume": "· /resume number-or-id-prefix — switch sessions; one from another project switches the project too",
354
359
  "history": "· /history [count] — preview recent messages in the current session, in private chats only (default {{defaultCount}}, maximum {{maxCount}})",
355
360
  "model": "· /model — see the model this chat uses; /model number or /model instance/model switches it",
361
+ "reasoning": "· /reasoning — see the reasoning effort for this chat's current model; /reasoning value or /reasoning number switches it; /reasoning default resets it to the model default",
356
362
  "preset": "· /preset — see the preset this chat uses for new sessions; /preset number or /preset name-or-id switches it; /preset none clears the chat choice",
357
363
  "plan": "· /plan — toggle plan mode (the agent proposes a plan instead of acting); /plan on, /plan off set it",
358
364
  "panel": "Send /panel to open the command panel with a button for every command.",
@@ -360,6 +366,28 @@
360
366
  "group": "· /group name — Lark only: the bot creates a group and adds you (bots on other platforms cannot)",
361
367
  "syncNote": "· After a private chat attaches a session, web user text and final assistant text sync here (groups never sync)."
362
368
  },
369
+ "reasoning": {
370
+ "current": "Current reasoning effort: {{value}}",
371
+ "currentUnlisted": "Current reasoning effort: {{value}} (no longer in the list; it stays in effect until you change it)",
372
+ "resetNow": "Reasoning effort reset to the model default. The current session uses it from the next turn; new sessions in this chat use it too.",
373
+ "resetNext": "Reasoning effort reset to the model default. New sessions in this chat will use it.",
374
+ "notFound": "\"{{query}}\" is not an available reasoning effort.",
375
+ "numberOutOfRange": "Number {{number}} is out of range; choose 1–{{max}}.",
376
+ "listTitle": "Available reasoning efforts (★ current):",
377
+ "listTitleDefault": "Available reasoning efforts (none chosen; the model default is used):",
378
+ "more": "{{count}} more not shown.",
379
+ "usage": "Usage: /reasoning — see the reasoning effort for this chat's current model; /reasoning value or /reasoning number switches it; /reasoning default resets it to the model default.",
380
+ "choices": "Choices: {{values}}.",
381
+ "unsupported": "The current model does not support switching the reasoning effort.",
382
+ "readFailed": "Could not read the reasoning options. Try again in a moment.",
383
+ "setFailed": "Switching the reasoning effort failed. Try again in a moment.",
384
+ "setNow": "Reasoning effort set to {{value}}. The current session uses it from the next turn; new sessions in this chat use it too.",
385
+ "setNext": "Reasoning effort set to {{value}}. New sessions in this chat will use it.",
386
+ "cleared": "The new model does not support the previous reasoning effort ({{value}}). It was cleared; the model default will be used.",
387
+ "noModel": "This chat has no model yet. Pick one with /model first.",
388
+ "switchReadFailed": "Could not read the new model's reasoning efforts, so the model was not switched. Try again in a moment.",
389
+ "listTitlePlain": "Available reasoning efforts:"
390
+ },
363
391
  "history": {
364
392
  "usage": "Usage: /history [count] — 1 to {{maxCount}} (larger numbers count as {{maxCount}}), default {{defaultCount}}.",
365
393
  "directOnly": "In a group this would show the session to everyone. Send /history in a private chat with the bot.",
@@ -340,19 +340,25 @@
340
340
  "project": "项目:{{title}}",
341
341
  "noProject": "还没选项目。发 /repo 选一个。"
342
342
  },
343
+ "version": {
344
+ "current": "Ycodium 聊天桥 v{{version}}",
345
+ "usage": "用法:/version(不带参数)。"
346
+ },
343
347
  "help": {
344
348
  "title": "能用的命令:",
345
349
  "continue": "· /continue —— /stop 或出错暂停了发送之后,用它恢复,排着的消息继续发出",
346
350
  "stop": "· /stop —— 停掉正在跑的这一轮,会话留着",
347
351
  "status": "· /status —— 现在在干什么",
352
+ "version": "· /version —— 聊天桥的版本(不带参数)",
348
353
  "new": "· /new —— 忘掉当前会话,下一句开新的",
349
354
  "team": "· /team —— 每条消息都给这个项目的团队开一个目标;先用 /repo 选项目",
350
355
  "thread": "· /thread —— 切回普通会话",
351
- "aliases": "中文也行:停 / 继续发送 / 状态 / 新对话 / 会话列表 / 切换会话 / 会话历史 / 换模型 / 计划模式 / 换项目 / 建群 / 换团队 / 换对话。",
356
+ "aliases": "中文也行:停 / 继续发送 / 状态 / 新对话 / 会话列表 / 切换会话 / 会话历史 / 换模型 / 思考强度 / 计划模式 / 换项目 / 建群 / 换团队 / 换对话。",
352
357
  "sessions": "· /sessions —— 列出本项目的会话;加 /repo 里的项目编号,列出本机器人在别的项目开过的",
353
358
  "resume": "· /resume 编号或 id 开头 —— 切会话;别的项目的会话会连同项目一起切",
354
359
  "history": "· /history [数量] —— 仅私聊:查看当前会话最近的消息(默认 {{defaultCount}} 条,最多 {{maxCount}} 条)",
355
360
  "model": "· /model —— 看这个聊天用的模型;/model 编号 或 /model 实例/模型 切换",
361
+ "reasoning": "· /reasoning —— 看这个聊天当前模型的思考强度;/reasoning 取值 或 /reasoning 编号 切换;/reasoning default 恢复模型默认",
356
362
  "preset": "· /preset —— 看这个聊天新会话用的预设;/preset 编号 或 /preset 名字或 id 切换;/preset none 清掉聊天选择",
357
363
  "plan": "· /plan —— 开关计划模式(agent 先出计划、不动手);/plan on、/plan off 明确设定",
358
364
  "panel": "发 /panel 打开可点的命令面板。",
@@ -360,6 +366,28 @@
360
366
  "group": "· /group 群名 —— 仅飞书:机器人建一个群并把你拉进去(别的平台的机器人建不了群)",
361
367
  "syncNote": "· 私聊附着会话后,网页上的用户文字与终态助手文字会同步到此(群聊不同步)。"
362
368
  },
369
+ "reasoning": {
370
+ "current": "当前思考强度:{{value}}",
371
+ "currentUnlisted": "当前思考强度:{{value}}(已不在可选列表,换掉之前仍生效)",
372
+ "resetNow": "思考强度已恢复为模型默认,当前会话从下一轮起使用它,这个聊天的新会话也使用它。",
373
+ "resetNext": "思考强度已恢复为模型默认,这个聊天的新会话将使用它。",
374
+ "notFound": "「{{query}}」不是可选的思考强度。",
375
+ "numberOutOfRange": "编号 {{number}} 超出范围,可选 1–{{max}}。",
376
+ "listTitle": "可选思考强度(★ 当前):",
377
+ "listTitleDefault": "可选思考强度(未手动选择,当前用模型默认):",
378
+ "more": "还有 {{count}} 项未列出。",
379
+ "usage": "用法:/reasoning —— 看这个聊天当前模型的思考强度;/reasoning 取值 或 /reasoning 编号 切换;/reasoning default 恢复模型默认。",
380
+ "choices": "可选:{{values}}。",
381
+ "unsupported": "当前模型不支持切换思考强度。",
382
+ "readFailed": "暂时读不到思考强度选项,请稍后重试。",
383
+ "setFailed": "切换思考强度失败,请稍后重试。",
384
+ "setNow": "思考强度已设为 {{value}},当前会话从下一轮起使用它,这个聊天的新会话也使用它。",
385
+ "setNext": "思考强度已设为 {{value}},这个聊天的新会话将使用它。",
386
+ "cleared": "新模型不支持原来的思考强度({{value}}),已清除,改用模型默认。",
387
+ "noModel": "这个聊天还没有模型,先用 /model 选一个。",
388
+ "switchReadFailed": "暂时读不到新模型的思考强度选项,模型没有切换,请稍后重试。",
389
+ "listTitlePlain": "可选思考强度:"
390
+ },
363
391
  "history": {
364
392
  "usage": "用法:/history [数量] —— 1–{{maxCount}} 条(超出按 {{maxCount}} 条算),默认 {{defaultCount}} 条。",
365
393
  "directOnly": "在群里会把会话内容发给所有人。在与机器人的私聊里发 /history。",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/plugin-im",
3
- "version": "0.2.3250",
3
+ "version": "0.2.3254",
4
4
  "description": "Talk to your agents from Telegram, WeCom, Lark, or WeChat (experimental).",
5
5
  "keywords": [
6
6
  "im",
@@ -33,7 +33,7 @@
33
33
  "react": "19.2.6",
34
34
  "vite-plus": "0.2.2",
35
35
  "ycodium-react-test-harness": "0.0.0",
36
- "@ycodium-ai/plugin-api": "0.2.3250"
36
+ "@ycodium-ai/plugin-api": "0.2.3254"
37
37
  },
38
38
  "ycodium": {
39
39
  "plugin": "./dist/index.js",