opencode-feishu-plugin 0.1.0 → 0.1.1
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.en.md +1 -1
- package/README.md +2 -2
- package/dist/index.js +1789 -1779
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { EventEmitter } from 'events';
|
|
|
11
11
|
import fs, { mkdirSync, createWriteStream, readFileSync, statSync, realpathSync } from 'fs';
|
|
12
12
|
import qs from 'querystring';
|
|
13
13
|
import { homedir } from 'os';
|
|
14
|
-
import {
|
|
14
|
+
import { readFile, readdir } from 'fs/promises';
|
|
15
15
|
|
|
16
16
|
var __create = Object.create;
|
|
17
17
|
var __defProp = Object.defineProperty;
|
|
@@ -146690,6 +146690,410 @@ function decideRoute(facts) {
|
|
|
146690
146690
|
if (facts.rootKnown) return { kind: "use-session", source: "root" };
|
|
146691
146691
|
return { kind: "create-in-thread" };
|
|
146692
146692
|
}
|
|
146693
|
+
var SYSTEM_DIRS = [
|
|
146694
|
+
"/etc",
|
|
146695
|
+
"/usr",
|
|
146696
|
+
"/bin",
|
|
146697
|
+
"/sbin",
|
|
146698
|
+
"/lib",
|
|
146699
|
+
"/lib64",
|
|
146700
|
+
"/boot",
|
|
146701
|
+
"/dev",
|
|
146702
|
+
"/proc",
|
|
146703
|
+
"/sys",
|
|
146704
|
+
"/run",
|
|
146705
|
+
"/root",
|
|
146706
|
+
"/var"
|
|
146707
|
+
];
|
|
146708
|
+
function isUnder(child, root) {
|
|
146709
|
+
if (!root || root === "/") return true;
|
|
146710
|
+
const normalizedRoot = root.endsWith(sep) ? root.slice(0, -1) : root;
|
|
146711
|
+
return child === normalizedRoot || child.startsWith(`${normalizedRoot}${sep}`);
|
|
146712
|
+
}
|
|
146713
|
+
function isSystemPath(path2) {
|
|
146714
|
+
return SYSTEM_DIRS.some((dir) => isUnder(path2, dir));
|
|
146715
|
+
}
|
|
146716
|
+
function validateDirectory(rawPath, allowedRoots, deps = {}) {
|
|
146717
|
+
const raw = rawPath.trim();
|
|
146718
|
+
const roots = allowedRoots.map((r) => resolve$1(r)).filter((r) => r && r !== "");
|
|
146719
|
+
if (roots.length === 0) {
|
|
146720
|
+
return err("empty", "\u672A\u914D\u7F6E\u5141\u8BB8\u7684\u5DE5\u4F5C\u76EE\u5F55\u6839\uFF08`allowedRoots`\uFF09\uFF0C\u65E0\u6CD5\u786E\u5B9A\u9ED8\u8BA4\u76EE\u5F55\u3002");
|
|
146721
|
+
}
|
|
146722
|
+
let target;
|
|
146723
|
+
if (!raw) {
|
|
146724
|
+
target = roots[0];
|
|
146725
|
+
} else {
|
|
146726
|
+
if (!isAbsolute(raw)) return err("not_absolute", "\u76EE\u5F55\u5FC5\u987B\u662F**\u7EDD\u5BF9\u8DEF\u5F84**\uFF08\u4EE5 `/` \u5F00\u5934\uFF09\u3002");
|
|
146727
|
+
target = resolve$1(raw);
|
|
146728
|
+
}
|
|
146729
|
+
const stat = deps.statSync ?? statSync;
|
|
146730
|
+
const realpath = deps.realpathSync ?? realpathSync;
|
|
146731
|
+
const mkdir = deps.mkdirSync ?? mkdirSync;
|
|
146732
|
+
const blocked = forbiddenReason(target);
|
|
146733
|
+
if (blocked) return blocked;
|
|
146734
|
+
if (!roots.some((root) => isUnder(target, root))) {
|
|
146735
|
+
return err("outside_allowed", outsideMessage(roots));
|
|
146736
|
+
}
|
|
146737
|
+
let exists2 = true;
|
|
146738
|
+
try {
|
|
146739
|
+
if (!stat(target).isDirectory()) return err("not_dir", `\u4E0D\u662F\u76EE\u5F55\uFF1A\`${target}\``);
|
|
146740
|
+
} catch {
|
|
146741
|
+
exists2 = false;
|
|
146742
|
+
}
|
|
146743
|
+
if (!exists2) {
|
|
146744
|
+
try {
|
|
146745
|
+
mkdir(target, { recursive: true });
|
|
146746
|
+
} catch {
|
|
146747
|
+
return err("create_failed", `\u76EE\u5F55\u4E0D\u5B58\u5728\u4E14\u65E0\u6CD5\u521B\u5EFA\uFF1A\`${target}\`\u3002\u8BF7\u68C0\u67E5\u8DEF\u5F84\u4E0E\u6743\u9650\u3002`);
|
|
146748
|
+
}
|
|
146749
|
+
}
|
|
146750
|
+
let real = target;
|
|
146751
|
+
try {
|
|
146752
|
+
real = realpath(target);
|
|
146753
|
+
} catch {
|
|
146754
|
+
real = target;
|
|
146755
|
+
}
|
|
146756
|
+
const blockedReal = forbiddenReason(real);
|
|
146757
|
+
if (blockedReal) return blockedReal;
|
|
146758
|
+
if (!roots.some((root) => isUnder(real, root))) {
|
|
146759
|
+
return err("outside_allowed", outsideMessage(roots));
|
|
146760
|
+
}
|
|
146761
|
+
return { ok: true, path: real };
|
|
146762
|
+
}
|
|
146763
|
+
function outsideMessage(roots) {
|
|
146764
|
+
return `\u76EE\u5F55\u4E0D\u5728\u5141\u8BB8\u8303\u56F4\u5185\u3002\u5141\u8BB8\u7684\u6839\u76EE\u5F55\uFF1A${roots.map((r) => `\`${r}\``).join("\u3001")}\u3002`;
|
|
146765
|
+
}
|
|
146766
|
+
function forbiddenReason(path2) {
|
|
146767
|
+
if (path2 === sep) return err("forbidden", "\u4E0D\u80FD\u4F7F\u7528\u6839\u76EE\u5F55 `/`\uFF0C\u8BF7\u9009\u62E9\u5177\u4F53\u7684\u9879\u76EE\u5B50\u76EE\u5F55\u3002");
|
|
146768
|
+
if (isSystemPath(path2)) return err("system", `\u7CFB\u7EDF\u76EE\u5F55\u4E0D\u53EF\u4F5C\u4E3A\u5DE5\u4F5C\u76EE\u5F55\uFF1A\`${path2}\`\u3002`);
|
|
146769
|
+
return void 0;
|
|
146770
|
+
}
|
|
146771
|
+
function err(reason, message) {
|
|
146772
|
+
return { ok: false, reason, message };
|
|
146773
|
+
}
|
|
146774
|
+
|
|
146775
|
+
// src/feishu/perm-presets.ts
|
|
146776
|
+
var SHELL_ACTIONS = ["shell", "bash"];
|
|
146777
|
+
var PERMISSION_PRESETS = [
|
|
146778
|
+
{ id: "readonly", icon: "\u{1F512}", label: "\u53EA\u8BFB", description: "\u7981\u6B62\u7F16\u8F91 / \u6267\u884C / \u5199\u5165\uFF0C\u6700\u5B89\u5168\u3002" },
|
|
146779
|
+
{ id: "edit", icon: "\u270F\uFE0F", label: "\u53EF\u7F16\u8F91", description: "\u5141\u8BB8\u6539\u6587\u4EF6\uFF1B\u6267\u884C\u547D\u4EE4\u9700\u4F60\u5BA1\u6279\u3002" },
|
|
146780
|
+
{
|
|
146781
|
+
id: "askHigh",
|
|
146782
|
+
icon: "\u26A0\uFE0F",
|
|
146783
|
+
label: "\u9AD8\u98CE\u9669\u5BA1\u6279",
|
|
146784
|
+
description: "\u7EE7\u627F\u9ED8\u8BA4\u89C4\u5219\uFF0C\u5BF9 shell / \u7F16\u8F91 / \u5916\u90E8\u76EE\u5F55\u9010\u6B21\u5BA1\u6279\u3002"
|
|
146785
|
+
},
|
|
146786
|
+
{ id: "trust", icon: "\u{1F513}", label: "\u5B8C\u5168\u4FE1\u4EFB", description: "\u653E\u884C\u5168\u90E8\u64CD\u4F5C\uFF0C\u8BF7\u8C28\u614E\u4F7F\u7528\u3002" }
|
|
146787
|
+
];
|
|
146788
|
+
var ASK_ACTIONS = {
|
|
146789
|
+
readonly: [],
|
|
146790
|
+
edit: ["shell", "bash"],
|
|
146791
|
+
askHigh: ["shell", "bash", "edit", "external_directory"],
|
|
146792
|
+
trust: []
|
|
146793
|
+
};
|
|
146794
|
+
var PRESET_IDS = PERMISSION_PRESETS.map((p) => p.id);
|
|
146795
|
+
function isPermissionPreset(value3) {
|
|
146796
|
+
return typeof value3 === "string" && PRESET_IDS.includes(value3);
|
|
146797
|
+
}
|
|
146798
|
+
function presetInfo(preset) {
|
|
146799
|
+
return PERMISSION_PRESETS.find((p) => p.id === preset) ?? PERMISSION_PRESETS[1];
|
|
146800
|
+
}
|
|
146801
|
+
function presetLabel(preset) {
|
|
146802
|
+
const info = presetInfo(preset);
|
|
146803
|
+
return `${info.icon} ${info.label}`;
|
|
146804
|
+
}
|
|
146805
|
+
function presetToRuleset(preset) {
|
|
146806
|
+
switch (preset) {
|
|
146807
|
+
case "readonly": {
|
|
146808
|
+
const actions = ["edit", "write", ...SHELL_ACTIONS];
|
|
146809
|
+
return actions.map((action) => ({ action, resource: "*", effect: "deny" }));
|
|
146810
|
+
}
|
|
146811
|
+
case "edit": {
|
|
146812
|
+
return [
|
|
146813
|
+
{ action: "edit", resource: "*", effect: "allow" },
|
|
146814
|
+
...SHELL_ACTIONS.map((action) => ({ action, resource: "*", effect: "ask" }))
|
|
146815
|
+
];
|
|
146816
|
+
}
|
|
146817
|
+
case "askHigh":
|
|
146818
|
+
return [];
|
|
146819
|
+
case "trust":
|
|
146820
|
+
return [{ action: "*", resource: "*", effect: "allow" }];
|
|
146821
|
+
default:
|
|
146822
|
+
return [];
|
|
146823
|
+
}
|
|
146824
|
+
}
|
|
146825
|
+
function presetGateMode(preset) {
|
|
146826
|
+
return preset === "readonly" || preset === "trust" ? "off" : "gate";
|
|
146827
|
+
}
|
|
146828
|
+
function presetAskActions(preset) {
|
|
146829
|
+
return ASK_ACTIONS[preset] ?? [];
|
|
146830
|
+
}
|
|
146831
|
+
|
|
146832
|
+
// src/feishu/wizard.ts
|
|
146833
|
+
var WIZARD_KEY_PREFIX = "feishu:v2:setup:";
|
|
146834
|
+
var STEPS = ["dir", "model", "perm", "confirm"];
|
|
146835
|
+
function wizardStart(title, anchorMessageId) {
|
|
146836
|
+
return {
|
|
146837
|
+
step: "dir",
|
|
146838
|
+
...title && title.trim() ? { title: title.trim() } : {},
|
|
146839
|
+
...anchorMessageId ? { anchorMessageId } : {}
|
|
146840
|
+
};
|
|
146841
|
+
}
|
|
146842
|
+
function reduceWizard(state, action) {
|
|
146843
|
+
switch (action.type) {
|
|
146844
|
+
case "start":
|
|
146845
|
+
return wizardStart(action.title, action.anchorMessageId);
|
|
146846
|
+
case "setDir":
|
|
146847
|
+
return state ? { ...state, dir: action.dir, step: "model" } : void 0;
|
|
146848
|
+
case "setModel":
|
|
146849
|
+
return state ? { ...state, model: action.model, step: "perm" } : void 0;
|
|
146850
|
+
case "setPerm":
|
|
146851
|
+
return state ? { ...state, perm: action.perm, step: "confirm" } : void 0;
|
|
146852
|
+
case "setPage":
|
|
146853
|
+
return state ? { ...state, page: Math.max(0, Math.floor(action.page)) } : void 0;
|
|
146854
|
+
case "cancel":
|
|
146855
|
+
return void 0;
|
|
146856
|
+
default:
|
|
146857
|
+
return state;
|
|
146858
|
+
}
|
|
146859
|
+
}
|
|
146860
|
+
function asString3(value3) {
|
|
146861
|
+
return typeof value3 === "string" ? value3 : "";
|
|
146862
|
+
}
|
|
146863
|
+
function parseModel(value3) {
|
|
146864
|
+
if (typeof value3 !== "object" || value3 === null) return void 0;
|
|
146865
|
+
const rec = value3;
|
|
146866
|
+
const providerID = asString3(rec.providerID);
|
|
146867
|
+
const id2 = asString3(rec.id);
|
|
146868
|
+
if (!providerID || !id2) return void 0;
|
|
146869
|
+
const name = asString3(rec.name);
|
|
146870
|
+
return { providerID, id: id2, ...name ? { name } : {} };
|
|
146871
|
+
}
|
|
146872
|
+
function parseWizardState(value3) {
|
|
146873
|
+
if (typeof value3 !== "object" || value3 === null) return void 0;
|
|
146874
|
+
const rec = value3;
|
|
146875
|
+
const step = asString3(rec.step);
|
|
146876
|
+
if (!STEPS.includes(step)) return void 0;
|
|
146877
|
+
const dir = asString3(rec.dir);
|
|
146878
|
+
const title = asString3(rec.title);
|
|
146879
|
+
const anchorMessageId = asString3(rec.anchorMessageId);
|
|
146880
|
+
const model = parseModel(rec.model);
|
|
146881
|
+
const perm = isPermissionPreset(rec.perm) ? rec.perm : void 0;
|
|
146882
|
+
const page = typeof rec.page === "number" && Number.isFinite(rec.page) ? Math.max(0, Math.floor(rec.page)) : void 0;
|
|
146883
|
+
return {
|
|
146884
|
+
step,
|
|
146885
|
+
...dir ? { dir } : {},
|
|
146886
|
+
...model ? { model } : {},
|
|
146887
|
+
...perm ? { perm } : {},
|
|
146888
|
+
...title ? { title } : {},
|
|
146889
|
+
...page !== void 0 ? { page } : {},
|
|
146890
|
+
...anchorMessageId ? { anchorMessageId } : {}
|
|
146891
|
+
};
|
|
146892
|
+
}
|
|
146893
|
+
var WizardStore = class {
|
|
146894
|
+
constructor(storage, log2) {
|
|
146895
|
+
this.storage = storage;
|
|
146896
|
+
this.log = log2;
|
|
146897
|
+
}
|
|
146898
|
+
storage;
|
|
146899
|
+
log;
|
|
146900
|
+
key(chatId) {
|
|
146901
|
+
return `${WIZARD_KEY_PREFIX}${chatId}`;
|
|
146902
|
+
}
|
|
146903
|
+
async get(chatId) {
|
|
146904
|
+
if (!chatId) return void 0;
|
|
146905
|
+
try {
|
|
146906
|
+
return parseWizardState(await this.storage.get(this.key(chatId)));
|
|
146907
|
+
} catch (err2) {
|
|
146908
|
+
this.log.warn("\u5411\u5BFC\u72B6\u6001\u8BFB\u53D6\u5931\u8D25", { chatId, error: errorMessage(err2) });
|
|
146909
|
+
return void 0;
|
|
146910
|
+
}
|
|
146911
|
+
}
|
|
146912
|
+
async set(chatId, state) {
|
|
146913
|
+
if (!chatId) return;
|
|
146914
|
+
try {
|
|
146915
|
+
if (!state) {
|
|
146916
|
+
await this.storage.remove(this.key(chatId));
|
|
146917
|
+
return;
|
|
146918
|
+
}
|
|
146919
|
+
await this.storage.set(this.key(chatId), state);
|
|
146920
|
+
} catch (err2) {
|
|
146921
|
+
this.log.warn("\u5411\u5BFC\u72B6\u6001\u5199\u5165\u5931\u8D25", { chatId, error: errorMessage(err2) });
|
|
146922
|
+
}
|
|
146923
|
+
}
|
|
146924
|
+
/** 应用一个动作并持久化;返回新状态(取消后为 undefined)。 */
|
|
146925
|
+
async apply(chatId, action) {
|
|
146926
|
+
const current = action.type === "start" ? void 0 : await this.get(chatId);
|
|
146927
|
+
const next = reduceWizard(current, action);
|
|
146928
|
+
await this.set(chatId, next);
|
|
146929
|
+
return next;
|
|
146930
|
+
}
|
|
146931
|
+
async start(chatId, title, anchorMessageId) {
|
|
146932
|
+
const state = wizardStart(title, anchorMessageId);
|
|
146933
|
+
await this.set(chatId, state);
|
|
146934
|
+
return state;
|
|
146935
|
+
}
|
|
146936
|
+
async cancel(chatId) {
|
|
146937
|
+
await this.set(chatId, void 0);
|
|
146938
|
+
}
|
|
146939
|
+
};
|
|
146940
|
+
|
|
146941
|
+
// src/feishu/recent.ts
|
|
146942
|
+
var RECENT_DIRS_KEY = "feishu:v2:recent:dirs";
|
|
146943
|
+
var RECENT_MODELS_KEY = "feishu:v2:recent:models";
|
|
146944
|
+
function isRecord5(value3) {
|
|
146945
|
+
return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
|
|
146946
|
+
}
|
|
146947
|
+
function modelKey(model) {
|
|
146948
|
+
return `${model.providerID}/${model.id}`;
|
|
146949
|
+
}
|
|
146950
|
+
function parseModel2(value3) {
|
|
146951
|
+
if (!isRecord5(value3)) return void 0;
|
|
146952
|
+
const providerID = typeof value3.providerID === "string" ? value3.providerID : "";
|
|
146953
|
+
const id2 = typeof value3.id === "string" ? value3.id : "";
|
|
146954
|
+
if (!providerID || !id2) return void 0;
|
|
146955
|
+
const name = typeof value3.name === "string" ? value3.name : "";
|
|
146956
|
+
return { providerID, id: id2, ...name ? { name } : {} };
|
|
146957
|
+
}
|
|
146958
|
+
var RecentStore = class {
|
|
146959
|
+
constructor(storage, log2, limits) {
|
|
146960
|
+
this.storage = storage;
|
|
146961
|
+
this.log = log2;
|
|
146962
|
+
this.limits = limits;
|
|
146963
|
+
}
|
|
146964
|
+
storage;
|
|
146965
|
+
log;
|
|
146966
|
+
limits;
|
|
146967
|
+
async listDirs() {
|
|
146968
|
+
const raw = await this.read(RECENT_DIRS_KEY);
|
|
146969
|
+
return Array.isArray(raw) ? raw.filter((v) => typeof v === "string") : [];
|
|
146970
|
+
}
|
|
146971
|
+
async addDir(dir) {
|
|
146972
|
+
const value3 = dir.trim();
|
|
146973
|
+
if (!value3) return;
|
|
146974
|
+
const list = [value3, ...(await this.listDirs()).filter((d) => d !== value3)];
|
|
146975
|
+
await this.write(RECENT_DIRS_KEY, list.slice(0, Math.max(1, this.limits.dirs)));
|
|
146976
|
+
}
|
|
146977
|
+
async listModels() {
|
|
146978
|
+
const raw = await this.read(RECENT_MODELS_KEY);
|
|
146979
|
+
if (!Array.isArray(raw)) return [];
|
|
146980
|
+
return raw.map(parseModel2).filter((m) => Boolean(m));
|
|
146981
|
+
}
|
|
146982
|
+
async addModel(model) {
|
|
146983
|
+
if (!model.providerID || !model.id) return;
|
|
146984
|
+
const key = modelKey(model);
|
|
146985
|
+
const list = [model, ...(await this.listModels()).filter((m) => modelKey(m) !== key)];
|
|
146986
|
+
await this.write(RECENT_MODELS_KEY, list.slice(0, Math.max(1, this.limits.models)));
|
|
146987
|
+
}
|
|
146988
|
+
async read(key) {
|
|
146989
|
+
try {
|
|
146990
|
+
return await this.storage.get(key);
|
|
146991
|
+
} catch (err2) {
|
|
146992
|
+
this.log.warn("\u6700\u8FD1\u8BB0\u5F55\u8BFB\u53D6\u5931\u8D25", { key, error: errorMessage(err2) });
|
|
146993
|
+
return void 0;
|
|
146994
|
+
}
|
|
146995
|
+
}
|
|
146996
|
+
async write(key, value3) {
|
|
146997
|
+
try {
|
|
146998
|
+
await this.storage.set(key, value3);
|
|
146999
|
+
} catch (err2) {
|
|
147000
|
+
this.log.warn("\u6700\u8FD1\u8BB0\u5F55\u5199\u5165\u5931\u8D25", { key, error: errorMessage(err2) });
|
|
147001
|
+
}
|
|
147002
|
+
}
|
|
147003
|
+
};
|
|
147004
|
+
|
|
147005
|
+
// src/feishu/models.ts
|
|
147006
|
+
function isRecord6(value3) {
|
|
147007
|
+
return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
|
|
147008
|
+
}
|
|
147009
|
+
function str3(value3) {
|
|
147010
|
+
return typeof value3 === "string" ? value3 : "";
|
|
147011
|
+
}
|
|
147012
|
+
function normalizeModelList(raw) {
|
|
147013
|
+
const arr = Array.isArray(raw) ? raw : isRecord6(raw) && Array.isArray(raw.data) ? raw.data : [];
|
|
147014
|
+
const out = [];
|
|
147015
|
+
const seen = /* @__PURE__ */ new Set();
|
|
147016
|
+
for (const item of arr) {
|
|
147017
|
+
if (!isRecord6(item)) continue;
|
|
147018
|
+
if (item.enabled === false) continue;
|
|
147019
|
+
const providerID = str3(item.providerID);
|
|
147020
|
+
const id2 = str3(item.id) || str3(item.modelID);
|
|
147021
|
+
if (!providerID || !id2) continue;
|
|
147022
|
+
const key = `${providerID}/${id2}`;
|
|
147023
|
+
if (seen.has(key)) continue;
|
|
147024
|
+
seen.add(key);
|
|
147025
|
+
const time = isRecord6(item.time) ? item.time : {};
|
|
147026
|
+
const released = typeof time.released === "number" && Number.isFinite(time.released) ? time.released : void 0;
|
|
147027
|
+
const status = str3(item.status) || void 0;
|
|
147028
|
+
out.push({
|
|
147029
|
+
providerID,
|
|
147030
|
+
id: id2,
|
|
147031
|
+
name: str3(item.name) || id2,
|
|
147032
|
+
...released !== void 0 ? { released } : {},
|
|
147033
|
+
...status ? { status } : {}
|
|
147034
|
+
});
|
|
147035
|
+
}
|
|
147036
|
+
return out;
|
|
147037
|
+
}
|
|
147038
|
+
function modelLabel(model) {
|
|
147039
|
+
return model.name && model.name.trim() ? model.name.trim() : `${model.providerID}/${model.id}`;
|
|
147040
|
+
}
|
|
147041
|
+
function sameModel(a, b) {
|
|
147042
|
+
if (!a || !b) return false;
|
|
147043
|
+
return a.providerID === b.providerID && a.id === b.id;
|
|
147044
|
+
}
|
|
147045
|
+
function extractSessionModel(raw) {
|
|
147046
|
+
if (!isRecord6(raw)) return void 0;
|
|
147047
|
+
const candidate = isRecord6(raw.model) ? raw.model : raw;
|
|
147048
|
+
const providerID = str3(candidate.providerID);
|
|
147049
|
+
const id2 = str3(candidate.id);
|
|
147050
|
+
if (!providerID || !id2) return void 0;
|
|
147051
|
+
return { providerID, id: id2 };
|
|
147052
|
+
}
|
|
147053
|
+
function matchesQuery(model, query) {
|
|
147054
|
+
const haystack = `${model.name} ${model.id} ${model.providerID} ${model.providerID}/${model.id}`.toLowerCase();
|
|
147055
|
+
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
147056
|
+
return tokens.length > 0 && tokens.every((t) => haystack.includes(t));
|
|
147057
|
+
}
|
|
147058
|
+
function sortModels(models) {
|
|
147059
|
+
return [...models].sort((a, b) => {
|
|
147060
|
+
const ar = a.released ?? 0;
|
|
147061
|
+
const br = b.released ?? 0;
|
|
147062
|
+
if (ar !== br) return br - ar;
|
|
147063
|
+
return a.name.localeCompare(b.name);
|
|
147064
|
+
});
|
|
147065
|
+
}
|
|
147066
|
+
function matchModel(query, models) {
|
|
147067
|
+
const q = query.trim();
|
|
147068
|
+
if (!q) return { ok: false, reason: "empty", candidates: [] };
|
|
147069
|
+
const lower = q.toLowerCase();
|
|
147070
|
+
const byRef = models.find((m) => `${m.providerID}/${m.id}`.toLowerCase() === lower);
|
|
147071
|
+
if (byRef) return { ok: true, model: byRef };
|
|
147072
|
+
const byId = models.filter((m) => m.id.toLowerCase() === lower);
|
|
147073
|
+
if (byId.length === 1) return { ok: true, model: byId[0] };
|
|
147074
|
+
if (byId.length > 1) return { ok: false, reason: "ambiguous", candidates: sortModels(byId).slice(0, 5) };
|
|
147075
|
+
const byName = models.filter((m) => m.name.toLowerCase() === lower);
|
|
147076
|
+
if (byName.length === 1) return { ok: true, model: byName[0] };
|
|
147077
|
+
if (byName.length > 1) return { ok: false, reason: "ambiguous", candidates: sortModels(byName).slice(0, 5) };
|
|
147078
|
+
const fuzzy = models.filter((m) => matchesQuery(m, q));
|
|
147079
|
+
if (fuzzy.length === 1) return { ok: true, model: fuzzy[0] };
|
|
147080
|
+
if (fuzzy.length > 1) return { ok: false, reason: "ambiguous", candidates: sortModels(fuzzy).slice(0, 5) };
|
|
147081
|
+
return { ok: false, reason: "not_found", candidates: [] };
|
|
147082
|
+
}
|
|
147083
|
+
function modelMatchErrorText(reason, candidates) {
|
|
147084
|
+
switch (reason) {
|
|
147085
|
+
case "empty":
|
|
147086
|
+
return "\u7528\u6CD5\uFF1A`/model <\u5173\u952E\u8BCD>`\uFF0C\u4F8B\u5982 `/model claude`\u3002";
|
|
147087
|
+
case "ambiguous":
|
|
147088
|
+
return [
|
|
147089
|
+
"\u5339\u914D\u5230\u591A\u4E2A\u6A21\u578B\uFF0C\u8BF7\u8F93\u5165\u66F4\u7CBE\u786E\u7684\u5173\u952E\u8BCD\uFF1A",
|
|
147090
|
+
...candidates.map((m) => `- ${modelLabel(m)}\uFF08\`${m.providerID}/${m.id}\`\uFF09`)
|
|
147091
|
+
].join("\n");
|
|
147092
|
+
case "not_found":
|
|
147093
|
+
default:
|
|
147094
|
+
return "\u6CA1\u6709\u5339\u914D\u7684\u6A21\u578B\uFF0C\u8BD5\u8BD5\u66F4\u77ED\u7684\u5173\u952E\u8BCD\u6216\u5148\u53D1\u9001 `/model` \u67E5\u770B\u5217\u8868\u3002";
|
|
147095
|
+
}
|
|
147096
|
+
}
|
|
146693
147097
|
|
|
146694
147098
|
// src/feishu/session-list.ts
|
|
146695
147099
|
function normalizeSessionList(raw) {
|
|
@@ -146706,10 +147110,10 @@ function normalizeSessionList(raw) {
|
|
|
146706
147110
|
return sortByUpdatedDesc(out);
|
|
146707
147111
|
}
|
|
146708
147112
|
function normalizeSessionInfo(raw) {
|
|
146709
|
-
if (!
|
|
146710
|
-
const sessionID =
|
|
147113
|
+
if (!isRecord7(raw)) return void 0;
|
|
147114
|
+
const sessionID = str4(raw.id) || str4(raw.sessionID);
|
|
146711
147115
|
if (!sessionID) return void 0;
|
|
146712
|
-
const title =
|
|
147116
|
+
const title = str4(raw.title) || str4(raw.slug);
|
|
146713
147117
|
const updatedAt = extractUpdatedAt(raw);
|
|
146714
147118
|
const directory = extractDirectory(raw);
|
|
146715
147119
|
return {
|
|
@@ -146741,7 +147145,7 @@ function toMillis2(value3) {
|
|
|
146741
147145
|
const ms = value3.getTime();
|
|
146742
147146
|
return Number.isFinite(ms) && ms > 0 ? ms : 0;
|
|
146743
147147
|
}
|
|
146744
|
-
if (
|
|
147148
|
+
if (isRecord7(value3)) {
|
|
146745
147149
|
for (const key of ["epochMillis", "epochMilliseconds", "millis", "value"]) {
|
|
146746
147150
|
const ms = toMillis2(value3[key]);
|
|
146747
147151
|
if (ms > 0) return ms;
|
|
@@ -146784,7 +147188,7 @@ function directoryTail(dir) {
|
|
|
146784
147188
|
}
|
|
146785
147189
|
function extractArray(raw) {
|
|
146786
147190
|
if (Array.isArray(raw)) return raw;
|
|
146787
|
-
if (
|
|
147191
|
+
if (isRecord7(raw)) {
|
|
146788
147192
|
for (const key of ["data", "sessions", "items"]) {
|
|
146789
147193
|
if (Array.isArray(raw[key])) return raw[key];
|
|
146790
147194
|
}
|
|
@@ -146792,7 +147196,7 @@ function extractArray(raw) {
|
|
|
146792
147196
|
return void 0;
|
|
146793
147197
|
}
|
|
146794
147198
|
function extractUpdatedAt(raw) {
|
|
146795
|
-
const time =
|
|
147199
|
+
const time = isRecord7(raw.time) ? raw.time : void 0;
|
|
146796
147200
|
const candidates = [
|
|
146797
147201
|
time?.updated,
|
|
146798
147202
|
raw.updatedAt,
|
|
@@ -146808,22 +147212,226 @@ function extractUpdatedAt(raw) {
|
|
|
146808
147212
|
return 0;
|
|
146809
147213
|
}
|
|
146810
147214
|
function extractDirectory(raw) {
|
|
146811
|
-
if (
|
|
146812
|
-
const directory =
|
|
147215
|
+
if (isRecord7(raw.location)) {
|
|
147216
|
+
const directory = str4(raw.location.directory);
|
|
146813
147217
|
if (directory) return directory;
|
|
146814
147218
|
}
|
|
146815
|
-
return
|
|
147219
|
+
return str4(raw.directory) || (typeof raw.location === "string" ? raw.location : "");
|
|
146816
147220
|
}
|
|
146817
147221
|
function pad2(value3) {
|
|
146818
147222
|
return value3 < 10 ? `0${value3}` : String(value3);
|
|
146819
147223
|
}
|
|
146820
|
-
function
|
|
147224
|
+
function isRecord7(value3) {
|
|
146821
147225
|
return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
|
|
146822
147226
|
}
|
|
146823
|
-
function
|
|
147227
|
+
function str4(value3) {
|
|
146824
147228
|
return typeof value3 === "string" ? value3 : "";
|
|
146825
147229
|
}
|
|
146826
147230
|
|
|
147231
|
+
// src/feishu/topic-guidance.ts
|
|
147232
|
+
var TOPIC_GUIDANCE_MARKER = "opencode-feishu-topic-guidance";
|
|
147233
|
+
function buildTopicGuidance(title) {
|
|
147234
|
+
return `\u672C\u4F1A\u8BDD\u4E3B\u9898\uFF1A\u300C${title}\u300D\u3002\u82E5\u7528\u6237\u660E\u663E\u8F6C\u5411\u4E0E\u8BE5\u4E3B\u9898\u65E0\u5173\u7684\u4EFB\u52A1\uFF0C\u53EF\u7B80\u77ED\u63D0\u9192\u4ED6\u7528 /new \u5F00\u65B0\u4F1A\u8BDD\uFF1B\u4E0D\u8981\u56E0\u6B64\u62D2\u7EDD\u56DE\u7B54\uFF0C\u4E5F\u4E0D\u8981\u957F\u7BC7\u8BF4\u6559\u3002`;
|
|
147235
|
+
}
|
|
147236
|
+
async function injectTopicGuidance(input, deps) {
|
|
147237
|
+
try {
|
|
147238
|
+
if (!Array.isArray(input.system)) return;
|
|
147239
|
+
const system = input.system;
|
|
147240
|
+
if (system.some(isGuidancePart)) return;
|
|
147241
|
+
const link2 = await deps.resolveSession(input.sessionID);
|
|
147242
|
+
if (!link2) return;
|
|
147243
|
+
const title = (await deps.resolveTitle(input.sessionID, link2.chatId))?.trim();
|
|
147244
|
+
if (!title) return;
|
|
147245
|
+
system.push({
|
|
147246
|
+
type: "text",
|
|
147247
|
+
text: buildTopicGuidance(title),
|
|
147248
|
+
metadata: { [TOPIC_GUIDANCE_MARKER]: true }
|
|
147249
|
+
});
|
|
147250
|
+
} catch (err2) {
|
|
147251
|
+
deps.log.warn("\u4E3B\u9898\u8F6F\u5F15\u5BFC\u6CE8\u5165\u5931\u8D25", { sessionID: input.sessionID, error: errorMessage(err2) });
|
|
147252
|
+
}
|
|
147253
|
+
}
|
|
147254
|
+
function isGuidancePart(part) {
|
|
147255
|
+
if (typeof part !== "object" || part === null) return false;
|
|
147256
|
+
const metadata = part.metadata;
|
|
147257
|
+
if (typeof metadata !== "object" || metadata === null) return false;
|
|
147258
|
+
return metadata[TOPIC_GUIDANCE_MARKER] === true;
|
|
147259
|
+
}
|
|
147260
|
+
|
|
147261
|
+
// src/permission.ts
|
|
147262
|
+
function decideEffect(action, config) {
|
|
147263
|
+
if (config.permissionGate === "off" || config.permissionGate === "notify") {
|
|
147264
|
+
return {};
|
|
147265
|
+
}
|
|
147266
|
+
if (matchesAny(action, config.denyTools)) {
|
|
147267
|
+
return { effect: "deny", message: `feishu policy: ${action} \u5DF2\u5728 denyTools` };
|
|
147268
|
+
}
|
|
147269
|
+
if (matchesAny(action, config.allowTools)) {
|
|
147270
|
+
return { effect: "allow" };
|
|
147271
|
+
}
|
|
147272
|
+
if (config.permissionGate === "lockdown") {
|
|
147273
|
+
return { effect: "deny", message: `feishu lockdown: ${action} \u4E0D\u5728 allowTools` };
|
|
147274
|
+
}
|
|
147275
|
+
return { effect: "ask", message: `opencode-feishu-v2: \u9700\u8981\u4EBA\u5DE5\u6279\u51C6 ${action}` };
|
|
147276
|
+
}
|
|
147277
|
+
function decideEffectForSession(action, config, session) {
|
|
147278
|
+
if (!session) return decideEffect(action, config);
|
|
147279
|
+
if (session.gateMode === "off") return {};
|
|
147280
|
+
if (matchesAny(action, config.denyTools)) {
|
|
147281
|
+
return { effect: "deny", message: `feishu policy: ${action} \u5DF2\u5728 denyTools` };
|
|
147282
|
+
}
|
|
147283
|
+
if (matchesAny(action, config.allowTools)) {
|
|
147284
|
+
return { effect: "allow" };
|
|
147285
|
+
}
|
|
147286
|
+
if (session.askActions && matchesAny(action, session.askActions)) {
|
|
147287
|
+
return { effect: "ask", message: `opencode-feishu-v2: \u4F1A\u8BDD\u9884\u8BBE\u9700\u4EBA\u5DE5\u6279\u51C6 ${action}` };
|
|
147288
|
+
}
|
|
147289
|
+
return {};
|
|
147290
|
+
}
|
|
147291
|
+
function parseApprovalValue(rawValue) {
|
|
147292
|
+
if (typeof rawValue !== "object" || rawValue === null) return void 0;
|
|
147293
|
+
const record2 = rawValue;
|
|
147294
|
+
const token = typeof record2.t === "string" ? record2.t : "";
|
|
147295
|
+
const d = record2.d;
|
|
147296
|
+
if (!token) return void 0;
|
|
147297
|
+
if (d !== "once" && d !== "always" && d !== "reject") return void 0;
|
|
147298
|
+
return { token, decision: d };
|
|
147299
|
+
}
|
|
147300
|
+
var ApprovalManager = class {
|
|
147301
|
+
constructor(deps) {
|
|
147302
|
+
this.deps = deps;
|
|
147303
|
+
this.now = deps.now ?? (() => Date.now());
|
|
147304
|
+
}
|
|
147305
|
+
deps;
|
|
147306
|
+
seenRequests = new TtlMap(60 * 60 * 1e3);
|
|
147307
|
+
cards = new TtlMap(60 * 60 * 1e3);
|
|
147308
|
+
now;
|
|
147309
|
+
/** 处理 permission.asked:发审批卡。 */
|
|
147310
|
+
async onAsked(request3) {
|
|
147311
|
+
const requestID = request3.id;
|
|
147312
|
+
if (!requestID || !this.seenRequests.setIfAbsent(requestID, true)) return;
|
|
147313
|
+
const link2 = await this.deps.getLink(request3.sessionID);
|
|
147314
|
+
if (!link2) {
|
|
147315
|
+
this.deps.log.debug("permission.asked \u65E0\u98DE\u4E66\u6620\u5C04\uFF0C\u8DF3\u8FC7\u5BA1\u6279\u5361", {
|
|
147316
|
+
requestID,
|
|
147317
|
+
sessionID: request3.sessionID
|
|
147318
|
+
});
|
|
147319
|
+
return;
|
|
147320
|
+
}
|
|
147321
|
+
const canPersistAlways = Array.isArray(request3.save) && request3.save.length > 0;
|
|
147322
|
+
const token = this.deps.sign({
|
|
147323
|
+
requestID,
|
|
147324
|
+
sessionID: request3.sessionID,
|
|
147325
|
+
openId: link2.openId
|
|
147326
|
+
});
|
|
147327
|
+
const input = {
|
|
147328
|
+
requestID,
|
|
147329
|
+
sessionID: request3.sessionID,
|
|
147330
|
+
action: request3.action,
|
|
147331
|
+
resources: request3.resources ?? [],
|
|
147332
|
+
...request3.message ? { message: request3.message } : {},
|
|
147333
|
+
canPersistAlways,
|
|
147334
|
+
token,
|
|
147335
|
+
maxResourcesShown: this.deps.config.maxResourcesShown
|
|
147336
|
+
};
|
|
147337
|
+
const result3 = link2.replyMessageId ? await this.deps.sender.replyCard(link2.replyMessageId, buildApprovalCard(input)) : await this.deps.sender.sendCard(link2.chatId, buildApprovalCard(input));
|
|
147338
|
+
if (!result3.ok || !result3.messageId) {
|
|
147339
|
+
this.deps.log.warn("\u5BA1\u6279\u5361\u53D1\u9001\u5931\u8D25", { requestID, error: result3.error ?? "unknown" });
|
|
147340
|
+
return;
|
|
147341
|
+
}
|
|
147342
|
+
this.cards.set(requestID, {
|
|
147343
|
+
messageId: result3.messageId,
|
|
147344
|
+
input,
|
|
147345
|
+
...link2.dir ? { directory: link2.dir } : {},
|
|
147346
|
+
resolved: false
|
|
147347
|
+
});
|
|
147348
|
+
this.deps.log.info("\u5BA1\u6279\u5361\u5DF2\u53D1\u9001", {
|
|
147349
|
+
requestID,
|
|
147350
|
+
action: request3.action,
|
|
147351
|
+
canPersistAlways,
|
|
147352
|
+
hasDir: Boolean(link2.dir)
|
|
147353
|
+
});
|
|
147354
|
+
}
|
|
147355
|
+
/** 处理 permission.replied:把卡片收敛为结果态(若尚未由点击更新)。 */
|
|
147356
|
+
onReplied(event) {
|
|
147357
|
+
const tracked = this.cards.get(event.requestID);
|
|
147358
|
+
if (!tracked) return;
|
|
147359
|
+
if (!tracked.resolved) {
|
|
147360
|
+
const outcome = {
|
|
147361
|
+
reply: event.reply,
|
|
147362
|
+
operatorOpenId: "",
|
|
147363
|
+
at: this.now()
|
|
147364
|
+
};
|
|
147365
|
+
void this.patchResolved(tracked, outcome, event.requestID);
|
|
147366
|
+
}
|
|
147367
|
+
this.cards.delete(event.requestID);
|
|
147368
|
+
}
|
|
147369
|
+
/**
|
|
147370
|
+
* 处理卡片按钮点击。同步返回飞书回调响应(toast),reply 在后台完成。
|
|
147371
|
+
* 校验顺序:白名单 → 签名 → 绑定字段 → 防重放。
|
|
147372
|
+
*/
|
|
147373
|
+
handleCardAction(action) {
|
|
147374
|
+
const parsed = parseApprovalValue(action.rawValue);
|
|
147375
|
+
if (!parsed) {
|
|
147376
|
+
return toast2("error", "\u65E0\u6CD5\u8BC6\u522B\u7684\u64CD\u4F5C");
|
|
147377
|
+
}
|
|
147378
|
+
if (!this.deps.isAllowed(action.operatorOpenId)) {
|
|
147379
|
+
this.deps.log.warn("\u62D2\u7EDD\u975E\u767D\u540D\u5355\u7528\u6237\u7684\u5BA1\u6279\u70B9\u51FB", {
|
|
147380
|
+
operator: action.operatorOpenId.slice(0, 8)
|
|
147381
|
+
});
|
|
147382
|
+
return toast2("error", "\u65E0\u5BA1\u6279\u6743\u9650");
|
|
147383
|
+
}
|
|
147384
|
+
const verified = this.deps.verify(parsed.token, { u: action.operatorOpenId });
|
|
147385
|
+
if (!verified.ok) {
|
|
147386
|
+
this.deps.log.warn("\u5BA1\u6279 token \u6821\u9A8C\u5931\u8D25", { reason: verified.reason });
|
|
147387
|
+
return toast2("error", `\u5BA1\u6279\u51ED\u8BC1\u65E0\u6548\uFF08${verified.reason}\uFF09`);
|
|
147388
|
+
}
|
|
147389
|
+
const claims = verified.claims;
|
|
147390
|
+
const ttl = Math.max(1e3, claims.e - this.now());
|
|
147391
|
+
if (!this.deps.replay.consume(claims.n, ttl)) {
|
|
147392
|
+
return toast2("warning", "\u8BE5\u64CD\u4F5C\u5DF2\u5904\u7406\uFF0C\u8BF7\u52FF\u91CD\u590D\u70B9\u51FB");
|
|
147393
|
+
}
|
|
147394
|
+
const tracked = this.cards.get(claims.r);
|
|
147395
|
+
const outcome = {
|
|
147396
|
+
reply: parsed.decision,
|
|
147397
|
+
operatorOpenId: action.operatorOpenId,
|
|
147398
|
+
at: this.now()
|
|
147399
|
+
};
|
|
147400
|
+
void this.applyReply(claims.r, claims.s, parsed.decision, tracked, outcome, tracked?.directory);
|
|
147401
|
+
return toast2(
|
|
147402
|
+
parsed.decision === "reject" ? "warning" : "success",
|
|
147403
|
+
parsed.decision === "reject" ? "\u5DF2\u62D2\u7EDD" : "\u5DF2\u5141\u8BB8"
|
|
147404
|
+
);
|
|
147405
|
+
}
|
|
147406
|
+
dispose() {
|
|
147407
|
+
this.seenRequests.clear();
|
|
147408
|
+
this.cards.clear();
|
|
147409
|
+
}
|
|
147410
|
+
async applyReply(requestID, sessionID, reply, tracked, outcome, directory) {
|
|
147411
|
+
try {
|
|
147412
|
+
await this.deps.reply({ sessionID, requestID, reply, ...directory ? { directory } : {} });
|
|
147413
|
+
} catch (err2) {
|
|
147414
|
+
this.deps.log.error("permission.reply \u5931\u8D25", {
|
|
147415
|
+
requestID,
|
|
147416
|
+
hasDir: Boolean(directory),
|
|
147417
|
+
error: errorMessage(err2)
|
|
147418
|
+
});
|
|
147419
|
+
return;
|
|
147420
|
+
}
|
|
147421
|
+
if (tracked && !tracked.resolved) {
|
|
147422
|
+
await this.patchResolved(tracked, outcome, requestID);
|
|
147423
|
+
}
|
|
147424
|
+
}
|
|
147425
|
+
async patchResolved(tracked, outcome, requestID) {
|
|
147426
|
+
tracked.resolved = true;
|
|
147427
|
+
const res = await this.deps.sender.patchCard(tracked.messageId, buildResolvedCard(tracked.input, outcome));
|
|
147428
|
+
if (!res.ok) this.deps.log.warn("\u5BA1\u6279\u7ED3\u679C\u5361\u7247\u66F4\u65B0\u5931\u8D25", { requestID, error: res.error ?? "unknown" });
|
|
147429
|
+
}
|
|
147430
|
+
};
|
|
147431
|
+
function toast2(type, content) {
|
|
147432
|
+
return { toast: { type, content } };
|
|
147433
|
+
}
|
|
147434
|
+
|
|
146827
147435
|
// src/feishu/session-cards.ts
|
|
146828
147436
|
function button2(text, type, value3) {
|
|
146829
147437
|
return {
|
|
@@ -146994,158 +147602,6 @@ function parsePage(value3) {
|
|
|
146994
147602
|
const parsed = typeof value3 === "number" ? value3 : typeof value3 === "string" ? Number.parseInt(value3, 10) : Number.NaN;
|
|
146995
147603
|
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
146996
147604
|
}
|
|
146997
|
-
|
|
146998
|
-
// src/feishu/perm-presets.ts
|
|
146999
|
-
var SHELL_ACTIONS = ["shell", "bash"];
|
|
147000
|
-
var PERMISSION_PRESETS = [
|
|
147001
|
-
{ id: "readonly", icon: "\u{1F512}", label: "\u53EA\u8BFB", description: "\u7981\u6B62\u7F16\u8F91 / \u6267\u884C / \u5199\u5165\uFF0C\u6700\u5B89\u5168\u3002" },
|
|
147002
|
-
{ id: "edit", icon: "\u270F\uFE0F", label: "\u53EF\u7F16\u8F91", description: "\u5141\u8BB8\u6539\u6587\u4EF6\uFF1B\u6267\u884C\u547D\u4EE4\u9700\u4F60\u5BA1\u6279\u3002" },
|
|
147003
|
-
{
|
|
147004
|
-
id: "askHigh",
|
|
147005
|
-
icon: "\u26A0\uFE0F",
|
|
147006
|
-
label: "\u9AD8\u98CE\u9669\u5BA1\u6279",
|
|
147007
|
-
description: "\u7EE7\u627F\u9ED8\u8BA4\u89C4\u5219\uFF0C\u5BF9 shell / \u7F16\u8F91 / \u5916\u90E8\u76EE\u5F55\u9010\u6B21\u5BA1\u6279\u3002"
|
|
147008
|
-
},
|
|
147009
|
-
{ id: "trust", icon: "\u{1F513}", label: "\u5B8C\u5168\u4FE1\u4EFB", description: "\u653E\u884C\u5168\u90E8\u64CD\u4F5C\uFF0C\u8BF7\u8C28\u614E\u4F7F\u7528\u3002" }
|
|
147010
|
-
];
|
|
147011
|
-
var ASK_ACTIONS = {
|
|
147012
|
-
readonly: [],
|
|
147013
|
-
edit: ["shell", "bash"],
|
|
147014
|
-
askHigh: ["shell", "bash", "edit", "external_directory"],
|
|
147015
|
-
trust: []
|
|
147016
|
-
};
|
|
147017
|
-
var PRESET_IDS = PERMISSION_PRESETS.map((p) => p.id);
|
|
147018
|
-
function isPermissionPreset(value3) {
|
|
147019
|
-
return typeof value3 === "string" && PRESET_IDS.includes(value3);
|
|
147020
|
-
}
|
|
147021
|
-
function presetInfo(preset) {
|
|
147022
|
-
return PERMISSION_PRESETS.find((p) => p.id === preset) ?? PERMISSION_PRESETS[1];
|
|
147023
|
-
}
|
|
147024
|
-
function presetLabel(preset) {
|
|
147025
|
-
const info = presetInfo(preset);
|
|
147026
|
-
return `${info.icon} ${info.label}`;
|
|
147027
|
-
}
|
|
147028
|
-
function presetToRuleset(preset) {
|
|
147029
|
-
switch (preset) {
|
|
147030
|
-
case "readonly": {
|
|
147031
|
-
const actions = ["edit", "write", ...SHELL_ACTIONS];
|
|
147032
|
-
return actions.map((action) => ({ action, resource: "*", effect: "deny" }));
|
|
147033
|
-
}
|
|
147034
|
-
case "edit": {
|
|
147035
|
-
return [
|
|
147036
|
-
{ action: "edit", resource: "*", effect: "allow" },
|
|
147037
|
-
...SHELL_ACTIONS.map((action) => ({ action, resource: "*", effect: "ask" }))
|
|
147038
|
-
];
|
|
147039
|
-
}
|
|
147040
|
-
case "askHigh":
|
|
147041
|
-
return [];
|
|
147042
|
-
case "trust":
|
|
147043
|
-
return [{ action: "*", resource: "*", effect: "allow" }];
|
|
147044
|
-
default:
|
|
147045
|
-
return [];
|
|
147046
|
-
}
|
|
147047
|
-
}
|
|
147048
|
-
function presetGateMode(preset) {
|
|
147049
|
-
return preset === "readonly" || preset === "trust" ? "off" : "gate";
|
|
147050
|
-
}
|
|
147051
|
-
function presetAskActions(preset) {
|
|
147052
|
-
return ASK_ACTIONS[preset] ?? [];
|
|
147053
|
-
}
|
|
147054
|
-
|
|
147055
|
-
// src/feishu/models.ts
|
|
147056
|
-
function isRecord6(value3) {
|
|
147057
|
-
return typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
|
|
147058
|
-
}
|
|
147059
|
-
function str4(value3) {
|
|
147060
|
-
return typeof value3 === "string" ? value3 : "";
|
|
147061
|
-
}
|
|
147062
|
-
function normalizeModelList(raw) {
|
|
147063
|
-
const arr = Array.isArray(raw) ? raw : isRecord6(raw) && Array.isArray(raw.data) ? raw.data : [];
|
|
147064
|
-
const out = [];
|
|
147065
|
-
const seen = /* @__PURE__ */ new Set();
|
|
147066
|
-
for (const item of arr) {
|
|
147067
|
-
if (!isRecord6(item)) continue;
|
|
147068
|
-
if (item.enabled === false) continue;
|
|
147069
|
-
const providerID = str4(item.providerID);
|
|
147070
|
-
const id2 = str4(item.id) || str4(item.modelID);
|
|
147071
|
-
if (!providerID || !id2) continue;
|
|
147072
|
-
const key = `${providerID}/${id2}`;
|
|
147073
|
-
if (seen.has(key)) continue;
|
|
147074
|
-
seen.add(key);
|
|
147075
|
-
const time = isRecord6(item.time) ? item.time : {};
|
|
147076
|
-
const released = typeof time.released === "number" && Number.isFinite(time.released) ? time.released : void 0;
|
|
147077
|
-
const status = str4(item.status) || void 0;
|
|
147078
|
-
out.push({
|
|
147079
|
-
providerID,
|
|
147080
|
-
id: id2,
|
|
147081
|
-
name: str4(item.name) || id2,
|
|
147082
|
-
...released !== void 0 ? { released } : {},
|
|
147083
|
-
...status ? { status } : {}
|
|
147084
|
-
});
|
|
147085
|
-
}
|
|
147086
|
-
return out;
|
|
147087
|
-
}
|
|
147088
|
-
function modelLabel(model) {
|
|
147089
|
-
return model.name && model.name.trim() ? model.name.trim() : `${model.providerID}/${model.id}`;
|
|
147090
|
-
}
|
|
147091
|
-
function sameModel(a, b) {
|
|
147092
|
-
if (!a || !b) return false;
|
|
147093
|
-
return a.providerID === b.providerID && a.id === b.id;
|
|
147094
|
-
}
|
|
147095
|
-
function extractSessionModel(raw) {
|
|
147096
|
-
if (!isRecord6(raw)) return void 0;
|
|
147097
|
-
const candidate = isRecord6(raw.model) ? raw.model : raw;
|
|
147098
|
-
const providerID = str4(candidate.providerID);
|
|
147099
|
-
const id2 = str4(candidate.id);
|
|
147100
|
-
if (!providerID || !id2) return void 0;
|
|
147101
|
-
return { providerID, id: id2 };
|
|
147102
|
-
}
|
|
147103
|
-
function matchesQuery(model, query) {
|
|
147104
|
-
const haystack = `${model.name} ${model.id} ${model.providerID} ${model.providerID}/${model.id}`.toLowerCase();
|
|
147105
|
-
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
147106
|
-
return tokens.length > 0 && tokens.every((t) => haystack.includes(t));
|
|
147107
|
-
}
|
|
147108
|
-
function sortModels(models) {
|
|
147109
|
-
return [...models].sort((a, b) => {
|
|
147110
|
-
const ar = a.released ?? 0;
|
|
147111
|
-
const br = b.released ?? 0;
|
|
147112
|
-
if (ar !== br) return br - ar;
|
|
147113
|
-
return a.name.localeCompare(b.name);
|
|
147114
|
-
});
|
|
147115
|
-
}
|
|
147116
|
-
function matchModel(query, models) {
|
|
147117
|
-
const q = query.trim();
|
|
147118
|
-
if (!q) return { ok: false, reason: "empty", candidates: [] };
|
|
147119
|
-
const lower = q.toLowerCase();
|
|
147120
|
-
const byRef = models.find((m) => `${m.providerID}/${m.id}`.toLowerCase() === lower);
|
|
147121
|
-
if (byRef) return { ok: true, model: byRef };
|
|
147122
|
-
const byId = models.filter((m) => m.id.toLowerCase() === lower);
|
|
147123
|
-
if (byId.length === 1) return { ok: true, model: byId[0] };
|
|
147124
|
-
if (byId.length > 1) return { ok: false, reason: "ambiguous", candidates: sortModels(byId).slice(0, 5) };
|
|
147125
|
-
const byName = models.filter((m) => m.name.toLowerCase() === lower);
|
|
147126
|
-
if (byName.length === 1) return { ok: true, model: byName[0] };
|
|
147127
|
-
if (byName.length > 1) return { ok: false, reason: "ambiguous", candidates: sortModels(byName).slice(0, 5) };
|
|
147128
|
-
const fuzzy = models.filter((m) => matchesQuery(m, q));
|
|
147129
|
-
if (fuzzy.length === 1) return { ok: true, model: fuzzy[0] };
|
|
147130
|
-
if (fuzzy.length > 1) return { ok: false, reason: "ambiguous", candidates: sortModels(fuzzy).slice(0, 5) };
|
|
147131
|
-
return { ok: false, reason: "not_found", candidates: [] };
|
|
147132
|
-
}
|
|
147133
|
-
function modelMatchErrorText(reason, candidates) {
|
|
147134
|
-
switch (reason) {
|
|
147135
|
-
case "empty":
|
|
147136
|
-
return "\u7528\u6CD5\uFF1A`/model <\u5173\u952E\u8BCD>`\uFF0C\u4F8B\u5982 `/model claude`\u3002";
|
|
147137
|
-
case "ambiguous":
|
|
147138
|
-
return [
|
|
147139
|
-
"\u5339\u914D\u5230\u591A\u4E2A\u6A21\u578B\uFF0C\u8BF7\u8F93\u5165\u66F4\u7CBE\u786E\u7684\u5173\u952E\u8BCD\uFF1A",
|
|
147140
|
-
...candidates.map((m) => `- ${modelLabel(m)}\uFF08\`${m.providerID}/${m.id}\`\uFF09`)
|
|
147141
|
-
].join("\n");
|
|
147142
|
-
case "not_found":
|
|
147143
|
-
default:
|
|
147144
|
-
return "\u6CA1\u6709\u5339\u914D\u7684\u6A21\u578B\uFF0C\u8BD5\u8BD5\u66F4\u77ED\u7684\u5173\u952E\u8BCD\u6216\u5148\u53D1\u9001 `/model` \u67E5\u770B\u5217\u8868\u3002";
|
|
147145
|
-
}
|
|
147146
|
-
}
|
|
147147
|
-
|
|
147148
|
-
// src/feishu/setup-cards.ts
|
|
147149
147605
|
var SETUP_FORM_CMD = "setup.form";
|
|
147150
147606
|
var SETUP_FORM_NAME = "setup_form";
|
|
147151
147607
|
var SETUP_FORM_FIELDS = {
|
|
@@ -147497,506 +147953,1039 @@ function shortenMiddle(text, max6) {
|
|
|
147497
147953
|
const tail = keep - head3;
|
|
147498
147954
|
return `${one.slice(0, head3)}\u2026${one.slice(one.length - tail)}`;
|
|
147499
147955
|
}
|
|
147500
|
-
|
|
147501
|
-
|
|
147502
|
-
|
|
147503
|
-
"
|
|
147504
|
-
|
|
147505
|
-
"/lib",
|
|
147506
|
-
"/lib64",
|
|
147507
|
-
"/boot",
|
|
147508
|
-
"/dev",
|
|
147509
|
-
"/proc",
|
|
147510
|
-
"/sys",
|
|
147511
|
-
"/run",
|
|
147512
|
-
"/root",
|
|
147513
|
-
"/var"
|
|
147514
|
-
];
|
|
147515
|
-
function isUnder(child, root) {
|
|
147516
|
-
if (!root || root === "/") return true;
|
|
147517
|
-
const normalizedRoot = root.endsWith(sep) ? root.slice(0, -1) : root;
|
|
147518
|
-
return child === normalizedRoot || child.startsWith(`${normalizedRoot}${sep}`);
|
|
147956
|
+
|
|
147957
|
+
// src/session/context.ts
|
|
147958
|
+
function clampPageSize(value3) {
|
|
147959
|
+
const base2 = typeof value3 === "number" && Number.isFinite(value3) ? Math.floor(value3) : 8;
|
|
147960
|
+
return Math.min(20, Math.max(5, base2));
|
|
147519
147961
|
}
|
|
147520
|
-
function
|
|
147521
|
-
return
|
|
147962
|
+
function toast3(type, content) {
|
|
147963
|
+
return { toast: { type, content } };
|
|
147522
147964
|
}
|
|
147523
|
-
function
|
|
147524
|
-
const
|
|
147525
|
-
const
|
|
147526
|
-
|
|
147527
|
-
|
|
147528
|
-
|
|
147529
|
-
|
|
147530
|
-
|
|
147531
|
-
|
|
147532
|
-
|
|
147533
|
-
|
|
147534
|
-
|
|
147965
|
+
function createSessionContext(deps) {
|
|
147966
|
+
const now3 = deps.now ?? (() => Date.now());
|
|
147967
|
+
const ctx = {
|
|
147968
|
+
deps,
|
|
147969
|
+
now: now3,
|
|
147970
|
+
threadRouting: deps.threadRouting ?? true,
|
|
147971
|
+
modelPageSize: deps.modelPageSize ?? 8,
|
|
147972
|
+
sessionPageSize: clampPageSize(deps.sessionPageSize),
|
|
147973
|
+
async reply(message, text) {
|
|
147974
|
+
if (message.threadId) {
|
|
147975
|
+
await deps.sender.replyText(message.messageId, text);
|
|
147976
|
+
return;
|
|
147977
|
+
}
|
|
147978
|
+
await deps.sender.sendText(message.chatId, text);
|
|
147979
|
+
},
|
|
147980
|
+
async replyChat(chatId, text) {
|
|
147981
|
+
await deps.sender.sendText(chatId, text);
|
|
147982
|
+
},
|
|
147983
|
+
async patchCard(messageId, card) {
|
|
147984
|
+
if (!messageId) return;
|
|
147985
|
+
const res = await deps.sender.patchCard(messageId, card);
|
|
147986
|
+
if (!res.ok) deps.log.warn("\u5411\u5BFC\u5361\u7247\u66F4\u65B0\u5931\u8D25", { error: res.error ?? "unknown" });
|
|
147987
|
+
},
|
|
147988
|
+
async loadModels() {
|
|
147989
|
+
try {
|
|
147990
|
+
return [...await deps.listModels()];
|
|
147991
|
+
} catch (err2) {
|
|
147992
|
+
deps.log.warn("\u6A21\u578B\u5217\u8868\u83B7\u53D6\u5931\u8D25", { error: errorMessage(err2) });
|
|
147993
|
+
return [];
|
|
147994
|
+
}
|
|
147995
|
+
},
|
|
147996
|
+
recentModelsLimit() {
|
|
147997
|
+
return deps.recentModelsLimit ?? 5;
|
|
147998
|
+
},
|
|
147999
|
+
async threadSessionID(message) {
|
|
148000
|
+
if (!message.threadId) return void 0;
|
|
148001
|
+
const link2 = await deps.sessionMap.resolveByThread(message.threadId);
|
|
148002
|
+
return link2?.sessionID;
|
|
148003
|
+
},
|
|
148004
|
+
async scopeSessionID(message, scope3) {
|
|
148005
|
+
if (scope3 === "thread") return ctx.threadSessionID(message);
|
|
148006
|
+
const active = await deps.sessionMap.getActive(message.chatId);
|
|
148007
|
+
return active?.sessionID;
|
|
148008
|
+
}
|
|
148009
|
+
};
|
|
148010
|
+
return ctx;
|
|
148011
|
+
}
|
|
148012
|
+
var MAX_ROOT_SUBDIRS = 15;
|
|
148013
|
+
async function scanRootSubdirs(root, deps = {}) {
|
|
148014
|
+
const rootPath = root.trim();
|
|
148015
|
+
if (!rootPath) return [];
|
|
148016
|
+
const rd = deps.readdir ?? ((path2, options2) => readdir(path2, options2));
|
|
148017
|
+
let entries3;
|
|
148018
|
+
try {
|
|
148019
|
+
entries3 = await rd(rootPath, { withFileTypes: true });
|
|
148020
|
+
} catch {
|
|
148021
|
+
return [];
|
|
147535
148022
|
}
|
|
147536
|
-
const
|
|
147537
|
-
const
|
|
147538
|
-
const
|
|
147539
|
-
|
|
147540
|
-
|
|
147541
|
-
if (!roots.some((root) => isUnder(target, root))) {
|
|
147542
|
-
return err("outside_allowed", outsideMessage(roots));
|
|
148023
|
+
const names = entries3.filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name).sort((a, b) => a < b ? -1 : a > b ? 1 : 0).slice(0, MAX_ROOT_SUBDIRS);
|
|
148024
|
+
const out = [];
|
|
148025
|
+
for (const name of names) {
|
|
148026
|
+
const path2 = join(rootPath, name);
|
|
148027
|
+
out.push({ name, path: path2, isRepo: await hasGit(path2, rd) });
|
|
147543
148028
|
}
|
|
147544
|
-
|
|
148029
|
+
return out;
|
|
148030
|
+
}
|
|
148031
|
+
async function hasGit(path2, rd) {
|
|
147545
148032
|
try {
|
|
147546
|
-
|
|
148033
|
+
const entries3 = await rd(path2, { withFileTypes: true });
|
|
148034
|
+
return entries3.some((e) => e.name === ".git");
|
|
147547
148035
|
} catch {
|
|
147548
|
-
|
|
148036
|
+
return false;
|
|
147549
148037
|
}
|
|
147550
|
-
|
|
148038
|
+
}
|
|
148039
|
+
|
|
148040
|
+
// src/session/model-perm.ts
|
|
148041
|
+
async function currentModel(ctx, sessionID) {
|
|
148042
|
+
const link2 = await ctx.deps.sessionMap.resolveBySession(sessionID);
|
|
148043
|
+
if (ctx.deps.getSessionModel) {
|
|
147551
148044
|
try {
|
|
147552
|
-
|
|
147553
|
-
|
|
147554
|
-
|
|
148045
|
+
const model = await ctx.deps.getSessionModel(sessionID, link2?.dir);
|
|
148046
|
+
if (model) return model;
|
|
148047
|
+
} catch (err2) {
|
|
148048
|
+
ctx.deps.log.warn("\u8BFB\u56DE\u4F1A\u8BDD\u6A21\u578B\u5931\u8D25\uFF0C\u56DE\u9000\u8BB0\u5F55\u503C", { sessionID, error: errorMessage(err2) });
|
|
147555
148049
|
}
|
|
147556
148050
|
}
|
|
147557
|
-
|
|
148051
|
+
return link2?.model;
|
|
148052
|
+
}
|
|
148053
|
+
async function renderModelCard(ctx, state, pageOverride) {
|
|
148054
|
+
const models = await ctx.loadModels();
|
|
148055
|
+
const recent = await ctx.deps.recent.listModels();
|
|
148056
|
+
return buildModelCard({
|
|
148057
|
+
models,
|
|
148058
|
+
recent,
|
|
148059
|
+
...state.model ? { current: state.model } : {},
|
|
148060
|
+
page: pageOverride ?? state.page ?? 0,
|
|
148061
|
+
pageSize: ctx.modelPageSize,
|
|
148062
|
+
recentLimit: ctx.recentModelsLimit()
|
|
148063
|
+
});
|
|
148064
|
+
}
|
|
148065
|
+
async function sendModelCardToThread(ctx, message, sessionID, page) {
|
|
148066
|
+
const current = await currentModel(ctx, sessionID);
|
|
148067
|
+
const models = await ctx.loadModels();
|
|
148068
|
+
const recent = await ctx.deps.recent.listModels();
|
|
148069
|
+
const card = buildModelCard({
|
|
148070
|
+
models,
|
|
148071
|
+
recent,
|
|
148072
|
+
...current ? { current } : {},
|
|
148073
|
+
page,
|
|
148074
|
+
pageSize: ctx.modelPageSize,
|
|
148075
|
+
recentLimit: ctx.recentModelsLimit(),
|
|
148076
|
+
sid: sessionID
|
|
148077
|
+
});
|
|
148078
|
+
await ctx.deps.sender.replyCard(message.messageId, card);
|
|
148079
|
+
}
|
|
148080
|
+
async function switchModelInThread(ctx, message, args2) {
|
|
148081
|
+
const sessionID = await ctx.threadSessionID(message);
|
|
148082
|
+
if (!sessionID) {
|
|
148083
|
+
await ctx.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\u3002\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u7528 `/new` \u65B0\u5EFA\u3002");
|
|
148084
|
+
return;
|
|
148085
|
+
}
|
|
148086
|
+
if (!args2.trim()) {
|
|
148087
|
+
await sendModelCardToThread(ctx, message, sessionID, 0);
|
|
148088
|
+
return;
|
|
148089
|
+
}
|
|
148090
|
+
const models = await ctx.loadModels();
|
|
148091
|
+
const matched = matchModel(args2, models);
|
|
148092
|
+
if (!matched.ok) {
|
|
148093
|
+
await ctx.reply(message, modelMatchErrorText(matched.reason, matched.candidates));
|
|
148094
|
+
return;
|
|
148095
|
+
}
|
|
147558
148096
|
try {
|
|
147559
|
-
|
|
147560
|
-
|
|
147561
|
-
|
|
148097
|
+
const outcome = await ctx.deps.switchSessionModel(sessionID, matched.model);
|
|
148098
|
+
await ctx.deps.recent.addModel(matched.model);
|
|
148099
|
+
await ctx.reply(message, modelSwitchReply(outcome, matched.model));
|
|
148100
|
+
} catch (err2) {
|
|
148101
|
+
ctx.deps.log.warn("\u5207\u6362\u4F1A\u8BDD\u6A21\u578B\u5931\u8D25", { sessionID, error: errorMessage(err2) });
|
|
148102
|
+
await ctx.reply(message, `\u26A0\uFE0F \u5207\u6362\u6A21\u578B\u5931\u8D25\uFF1A${errorMessage(err2)}`);
|
|
147562
148103
|
}
|
|
147563
|
-
|
|
147564
|
-
|
|
147565
|
-
|
|
147566
|
-
|
|
148104
|
+
}
|
|
148105
|
+
async function sendPermCardToThread(ctx, message, sessionID) {
|
|
148106
|
+
const link2 = await ctx.deps.sessionMap.resolveBySession(sessionID);
|
|
148107
|
+
const card = buildPermCard({ ...link2?.perm ? { current: link2.perm } : {}, sid: sessionID });
|
|
148108
|
+
await ctx.deps.sender.replyCard(message.messageId, card);
|
|
148109
|
+
}
|
|
148110
|
+
async function setPermInThread(ctx, message, args2) {
|
|
148111
|
+
const sessionID = await ctx.threadSessionID(message);
|
|
148112
|
+
if (!sessionID) {
|
|
148113
|
+
await ctx.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\u3002\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u7528 `/new` \u65B0\u5EFA\u3002");
|
|
148114
|
+
return;
|
|
147567
148115
|
}
|
|
147568
|
-
|
|
148116
|
+
const arg = args2.trim();
|
|
148117
|
+
if (!arg) {
|
|
148118
|
+
await sendPermCardToThread(ctx, message, sessionID);
|
|
148119
|
+
return;
|
|
148120
|
+
}
|
|
148121
|
+
if (!isPermissionPreset(arg)) {
|
|
148122
|
+
await ctx.reply(message, permUsageText());
|
|
148123
|
+
return;
|
|
148124
|
+
}
|
|
148125
|
+
await ctx.deps.applyPermissionPreset(sessionID, arg);
|
|
148126
|
+
await ctx.reply(message, `\u2705 \u5DF2\u66F4\u65B0\u672C\u4F1A\u8BDD\u6743\u9650\uFF1A${presetLabel(arg)}`);
|
|
147569
148127
|
}
|
|
147570
|
-
function
|
|
147571
|
-
|
|
148128
|
+
function permUsageText() {
|
|
148129
|
+
const list = PERMISSION_PRESETS.map((p) => `\`${p.id}\`\uFF08${presetInfo(p.id).icon}${p.label}\uFF09`).join("\u3001");
|
|
148130
|
+
return `\u672A\u77E5\u6743\u9650\u6863\u4F4D\u3002\u53EF\u7528\uFF1A${list}\u3002
|
|
148131
|
+
\u4F8B\u5982\uFF1A\`/perm edit\`\u3002`;
|
|
147572
148132
|
}
|
|
147573
|
-
function
|
|
147574
|
-
|
|
147575
|
-
|
|
147576
|
-
|
|
148133
|
+
function modelSwitchReply(outcome, requested) {
|
|
148134
|
+
const effective = outcome?.effective ?? requested;
|
|
148135
|
+
const ref = `**${modelLabel(effective)}**
|
|
148136
|
+
\`${effective.providerID}/${effective.id}\``;
|
|
148137
|
+
if (outcome?.mismatch) {
|
|
148138
|
+
return [
|
|
148139
|
+
`\u26A0\uFE0F \u6A21\u578B\u53EF\u80FD\u672A\u751F\u6548\uFF1A\u8BF7\u6C42 **${modelLabel(outcome.requested)}**\uFF0C\u670D\u52A1\u7AEF\u5B9E\u9645\u4E3A ${ref}\u3002`,
|
|
148140
|
+
"\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\uFF1B\u5386\u53F2\u6D88\u606F\u4ECD\u662F\u65E7\u6A21\u578B\uFF0C\u5C5E\u6B63\u5E38\u3002"
|
|
148141
|
+
].join("\n");
|
|
148142
|
+
}
|
|
148143
|
+
if (outcome && !outcome.verified) {
|
|
148144
|
+
return `\u2705 \u5DF2\u8BF7\u6C42\u5207\u6362\u6A21\u578B\uFF1A${ref}
|
|
148145
|
+
\u672A\u80FD\u8BFB\u56DE\u6821\u9A8C${outcome.warning ? `\uFF08${outcome.warning}\uFF09` : ""}\uFF1B\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\u3002`;
|
|
148146
|
+
}
|
|
148147
|
+
return `\u2705 \u5DF2\u5207\u6362\u6A21\u578B\uFF1A${ref}
|
|
148148
|
+
\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\uFF1B\u8BE5\u4F1A\u8BDD\u6B64\u524D\u7684\u5386\u53F2\u6D88\u606F\u4ECD\u663E\u793A\u65E7\u6A21\u578B\uFF0C\u5C5E\u6B63\u5E38\u3002`;
|
|
147577
148149
|
}
|
|
147578
|
-
function
|
|
147579
|
-
|
|
148150
|
+
function modelSwitchDoneCard(outcome, requested) {
|
|
148151
|
+
const effective = outcome?.effective ?? requested;
|
|
148152
|
+
if (outcome?.mismatch) {
|
|
148153
|
+
return buildSetupDoneCard("\u26A0\uFE0F \u6A21\u578B\u53EF\u80FD\u672A\u751F\u6548", [
|
|
148154
|
+
`\u8BF7\u6C42\uFF1A**${modelLabel(outcome.requested)}**`,
|
|
148155
|
+
`\u5B9E\u9645\uFF1A**${modelLabel(effective)}**`,
|
|
148156
|
+
"\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\uFF1B\u5386\u53F2\u6D88\u606F\u4ECD\u662F\u65E7\u6A21\u578B\uFF0C\u5C5E\u6B63\u5E38\u3002"
|
|
148157
|
+
]);
|
|
148158
|
+
}
|
|
148159
|
+
if (outcome && !outcome.verified) {
|
|
148160
|
+
return buildSetupDoneCard("\u2705 \u5DF2\u5207\u6362\u6A21\u578B", [
|
|
148161
|
+
`\u5F53\u524D\u6A21\u578B\uFF1A**${modelLabel(effective)}**`,
|
|
148162
|
+
"\uFF08\u672A\u80FD\u8BFB\u56DE\u6821\u9A8C\uFF1B\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\u3002\uFF09"
|
|
148163
|
+
]);
|
|
148164
|
+
}
|
|
148165
|
+
return buildSetupDoneCard("\u2705 \u5DF2\u5207\u6362\u6A21\u578B", [
|
|
148166
|
+
`\u5F53\u524D\u6A21\u578B\uFF1A**${modelLabel(effective)}**`,
|
|
148167
|
+
"\uFF08\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\uFF1B\u5386\u53F2\u6D88\u606F\u4ECD\u662F\u65E7\u6A21\u578B\uFF0C\u5C5E\u6B63\u5E38\u3002\uFF09"
|
|
148168
|
+
]);
|
|
147580
148169
|
}
|
|
147581
|
-
|
|
147582
|
-
// src/feishu/wizard.ts
|
|
147583
|
-
var WIZARD_KEY_PREFIX = "feishu:v2:setup:";
|
|
147584
|
-
var STEPS = ["dir", "model", "perm", "confirm"];
|
|
147585
|
-
function wizardStart(title, anchorMessageId) {
|
|
148170
|
+
function createModelPermApi(ctx) {
|
|
147586
148171
|
return {
|
|
147587
|
-
|
|
147588
|
-
|
|
147589
|
-
|
|
148172
|
+
currentModel: (sessionID) => currentModel(ctx, sessionID),
|
|
148173
|
+
renderModelCard: (state, pageOverride) => renderModelCard(ctx, state, pageOverride),
|
|
148174
|
+
sendModelCardToThread: (message, sessionID, page) => sendModelCardToThread(ctx, message, sessionID, page),
|
|
148175
|
+
switchModelInThread: (message, args2) => switchModelInThread(ctx, message, args2),
|
|
148176
|
+
sendPermCardToThread: (message, sessionID) => sendPermCardToThread(ctx, message, sessionID),
|
|
148177
|
+
setPermInThread: (message, args2) => setPermInThread(ctx, message, args2)
|
|
147590
148178
|
};
|
|
147591
148179
|
}
|
|
147592
|
-
|
|
147593
|
-
|
|
147594
|
-
|
|
147595
|
-
|
|
147596
|
-
|
|
147597
|
-
|
|
147598
|
-
|
|
147599
|
-
|
|
147600
|
-
|
|
147601
|
-
|
|
147602
|
-
|
|
147603
|
-
|
|
147604
|
-
|
|
147605
|
-
|
|
147606
|
-
default:
|
|
147607
|
-
return state;
|
|
148180
|
+
|
|
148181
|
+
// src/session/setup-wizard.ts
|
|
148182
|
+
async function cmdNew(ctx, message, args2) {
|
|
148183
|
+
const title = args2.trim() || void 0;
|
|
148184
|
+
if (!ctx.threadRouting) {
|
|
148185
|
+
const finalTitle = title ?? defaultSessionTitle(ctx.now());
|
|
148186
|
+
const created = await ctx.deps.createSession({
|
|
148187
|
+
title: finalTitle,
|
|
148188
|
+
chatId: message.chatId,
|
|
148189
|
+
openId: message.senderOpenId
|
|
148190
|
+
});
|
|
148191
|
+
await ctx.reply(message, `\u2705 \u5DF2\u65B0\u5EFA\u5E76\u5207\u6362\u5230\u4F1A\u8BDD\u300C${finalTitle}\u300D
|
|
148192
|
+
\`${created.id}\``);
|
|
148193
|
+
return;
|
|
147608
148194
|
}
|
|
148195
|
+
await openSetupForm(ctx, message, title);
|
|
147609
148196
|
}
|
|
147610
|
-
function
|
|
147611
|
-
|
|
148197
|
+
async function cmdForm(ctx, message, args2) {
|
|
148198
|
+
await openSetupForm(ctx, message, args2.trim() || void 0);
|
|
147612
148199
|
}
|
|
147613
|
-
function
|
|
147614
|
-
if (
|
|
147615
|
-
|
|
147616
|
-
|
|
147617
|
-
|
|
147618
|
-
if (!
|
|
147619
|
-
|
|
147620
|
-
|
|
148200
|
+
async function openSetupForm(ctx, message, title) {
|
|
148201
|
+
if (message.threadId) {
|
|
148202
|
+
await ctx.reply(message, threadForbiddenText("form"));
|
|
148203
|
+
return;
|
|
148204
|
+
}
|
|
148205
|
+
if (!ctx.threadRouting) {
|
|
148206
|
+
await ctx.reply(message, "\u5F53\u524D\u4E3A\u56DE\u9000\u6A21\u5F0F\uFF08`threadRouting=false`\uFF09\uFF0C\u4E0D\u652F\u6301\u8868\u5355\u5EFA\u4F1A\u8BDD\uFF0C\u8BF7\u7528 `/new`\u3002");
|
|
148207
|
+
return;
|
|
148208
|
+
}
|
|
148209
|
+
let state = await ctx.deps.wizard.get(message.chatId);
|
|
148210
|
+
if (!state) {
|
|
148211
|
+
state = await ctx.deps.wizard.start(message.chatId, title, message.messageId);
|
|
148212
|
+
} else if (title !== void 0) {
|
|
148213
|
+
state = { ...state, title };
|
|
148214
|
+
await ctx.deps.wizard.set(message.chatId, state);
|
|
148215
|
+
}
|
|
148216
|
+
const card = await renderFormCard(ctx, state);
|
|
148217
|
+
const res = await ctx.deps.sender.sendCard(message.chatId, card);
|
|
148218
|
+
if (!res.ok) ctx.deps.log.warn("\u8868\u5355\u5361\u53D1\u9001\u5931\u8D25", { chatId: message.chatId, error: res.error ?? "unknown" });
|
|
147621
148219
|
}
|
|
147622
|
-
function
|
|
147623
|
-
if (
|
|
147624
|
-
|
|
147625
|
-
|
|
147626
|
-
|
|
147627
|
-
const
|
|
147628
|
-
|
|
147629
|
-
|
|
147630
|
-
|
|
147631
|
-
|
|
147632
|
-
|
|
147633
|
-
|
|
147634
|
-
|
|
147635
|
-
|
|
147636
|
-
|
|
147637
|
-
|
|
147638
|
-
|
|
147639
|
-
|
|
147640
|
-
|
|
147641
|
-
|
|
148220
|
+
async function cmdDir(ctx, message, args2) {
|
|
148221
|
+
if (message.threadId) {
|
|
148222
|
+
await ctx.reply(message, threadForbiddenText("dir"));
|
|
148223
|
+
return;
|
|
148224
|
+
}
|
|
148225
|
+
const validation = ctx.deps.validateDir(args2);
|
|
148226
|
+
if (!validation.ok) {
|
|
148227
|
+
await ctx.reply(message, validation.message);
|
|
148228
|
+
return;
|
|
148229
|
+
}
|
|
148230
|
+
if (!await ctx.deps.wizard.get(message.chatId)) {
|
|
148231
|
+
await ctx.deps.wizard.start(message.chatId, void 0, message.messageId);
|
|
148232
|
+
}
|
|
148233
|
+
const state = await ctx.deps.wizard.apply(message.chatId, { type: "setDir", dir: validation.path });
|
|
148234
|
+
await ctx.deps.recent.addDir(validation.path);
|
|
148235
|
+
if (!state) {
|
|
148236
|
+
await ctx.reply(message, "\u5411\u5BFC\u72B6\u6001\u5DF2\u4E22\u5931\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001 `/new` \u5F00\u59CB\u3002");
|
|
148237
|
+
return;
|
|
148238
|
+
}
|
|
148239
|
+
await ctx.deps.sender.sendCard(message.chatId, await renderFormCard(ctx, state));
|
|
147642
148240
|
}
|
|
147643
|
-
|
|
147644
|
-
|
|
147645
|
-
|
|
147646
|
-
|
|
148241
|
+
async function cmdModel(ctx, message, args2, scope3) {
|
|
148242
|
+
if (scope3 === "thread") {
|
|
148243
|
+
await switchModelInThread(ctx, message, args2);
|
|
148244
|
+
return;
|
|
147647
148245
|
}
|
|
147648
|
-
|
|
147649
|
-
|
|
147650
|
-
key(chatId) {
|
|
147651
|
-
return `${WIZARD_KEY_PREFIX}${chatId}`;
|
|
148246
|
+
if (!await ctx.deps.wizard.get(message.chatId)) {
|
|
148247
|
+
await ctx.deps.wizard.start(message.chatId, void 0, message.messageId);
|
|
147652
148248
|
}
|
|
147653
|
-
|
|
147654
|
-
|
|
147655
|
-
|
|
147656
|
-
|
|
147657
|
-
} catch (err2) {
|
|
147658
|
-
this.log.warn("\u5411\u5BFC\u72B6\u6001\u8BFB\u53D6\u5931\u8D25", { chatId, error: errorMessage(err2) });
|
|
147659
|
-
return void 0;
|
|
147660
|
-
}
|
|
148249
|
+
if (!args2.trim()) {
|
|
148250
|
+
const state = await ctx.deps.wizard.get(message.chatId);
|
|
148251
|
+
await ctx.deps.sender.sendCard(message.chatId, await renderFormCard(ctx, state));
|
|
148252
|
+
return;
|
|
147661
148253
|
}
|
|
147662
|
-
|
|
147663
|
-
|
|
147664
|
-
|
|
147665
|
-
|
|
147666
|
-
|
|
147667
|
-
return;
|
|
147668
|
-
}
|
|
147669
|
-
await this.storage.set(this.key(chatId), state);
|
|
147670
|
-
} catch (err2) {
|
|
147671
|
-
this.log.warn("\u5411\u5BFC\u72B6\u6001\u5199\u5165\u5931\u8D25", { chatId, error: errorMessage(err2) });
|
|
147672
|
-
}
|
|
148254
|
+
const models = await ctx.loadModels();
|
|
148255
|
+
const matched = matchModel(args2, models);
|
|
148256
|
+
if (!matched.ok) {
|
|
148257
|
+
await ctx.reply(message, modelMatchErrorText(matched.reason, matched.candidates));
|
|
148258
|
+
return;
|
|
147673
148259
|
}
|
|
147674
|
-
|
|
147675
|
-
|
|
147676
|
-
|
|
147677
|
-
|
|
147678
|
-
|
|
147679
|
-
|
|
148260
|
+
const next = await ctx.deps.wizard.apply(message.chatId, { type: "setModel", model: matched.model });
|
|
148261
|
+
await ctx.deps.recent.addModel(matched.model);
|
|
148262
|
+
if (!next) return;
|
|
148263
|
+
await ctx.deps.sender.sendCard(message.chatId, await renderFormCard(ctx, next));
|
|
148264
|
+
}
|
|
148265
|
+
async function cmdPerm(ctx, message, args2, scope3) {
|
|
148266
|
+
if (scope3 === "thread") {
|
|
148267
|
+
await setPermInThread(ctx, message, args2);
|
|
148268
|
+
return;
|
|
147680
148269
|
}
|
|
147681
|
-
|
|
147682
|
-
|
|
147683
|
-
await
|
|
147684
|
-
return state;
|
|
148270
|
+
const arg = args2.trim();
|
|
148271
|
+
if (!await ctx.deps.wizard.get(message.chatId)) {
|
|
148272
|
+
await ctx.deps.wizard.start(message.chatId, void 0, message.messageId);
|
|
147685
148273
|
}
|
|
147686
|
-
|
|
147687
|
-
await
|
|
148274
|
+
if (!arg) {
|
|
148275
|
+
const state = await ctx.deps.wizard.get(message.chatId);
|
|
148276
|
+
await ctx.deps.sender.sendCard(message.chatId, await renderFormCard(ctx, state));
|
|
148277
|
+
return;
|
|
147688
148278
|
}
|
|
147689
|
-
|
|
147690
|
-
|
|
147691
|
-
|
|
147692
|
-
|
|
147693
|
-
|
|
147694
|
-
|
|
147695
|
-
|
|
148279
|
+
if (!isPermissionPreset(arg)) {
|
|
148280
|
+
await ctx.reply(message, permUsageText());
|
|
148281
|
+
return;
|
|
148282
|
+
}
|
|
148283
|
+
const next = await ctx.deps.wizard.apply(message.chatId, { type: "setPerm", perm: arg });
|
|
148284
|
+
if (!next) return;
|
|
148285
|
+
await ctx.deps.sender.sendCard(message.chatId, await renderFormCard(ctx, next));
|
|
147696
148286
|
}
|
|
147697
|
-
function
|
|
147698
|
-
|
|
148287
|
+
async function cmdCancel(ctx, message) {
|
|
148288
|
+
if (message.threadId) {
|
|
148289
|
+
await ctx.reply(message, threadForbiddenText("cancel"));
|
|
148290
|
+
return;
|
|
148291
|
+
}
|
|
148292
|
+
await ctx.deps.wizard.cancel(message.chatId);
|
|
148293
|
+
await ctx.reply(message, "\u2716\uFE0F \u5DF2\u53D6\u6D88\u5EFA\u4F1A\u8BDD\u8868\u5355\u3002\u53D1\u9001 `/new [\u6807\u9898]` \u53EF\u91CD\u65B0\u5F00\u59CB\u3002");
|
|
147699
148294
|
}
|
|
147700
|
-
function
|
|
147701
|
-
if (!
|
|
147702
|
-
|
|
147703
|
-
|
|
147704
|
-
|
|
147705
|
-
|
|
147706
|
-
|
|
148295
|
+
async function sendSetupFormForChat(ctx, chatId, anchorMessageId, openId) {
|
|
148296
|
+
if (!ctx.threadRouting) {
|
|
148297
|
+
const title = defaultSessionTitle(ctx.now());
|
|
148298
|
+
await ctx.deps.createSession({ title, chatId, openId });
|
|
148299
|
+
await ctx.replyChat(chatId, `\u2705 \u5DF2\u65B0\u5EFA\u4F1A\u8BDD\u300C${title}\u300D`);
|
|
148300
|
+
return;
|
|
148301
|
+
}
|
|
148302
|
+
let state = await ctx.deps.wizard.get(chatId);
|
|
148303
|
+
if (!state) state = await ctx.deps.wizard.start(chatId, void 0, anchorMessageId);
|
|
148304
|
+
const card = await renderFormCard(ctx, state);
|
|
148305
|
+
const res = await ctx.deps.sender.sendCard(chatId, card);
|
|
148306
|
+
if (!res.ok) ctx.deps.log.warn("\u8868\u5355\u5361\u53D1\u9001\u5931\u8D25", { chatId, error: res.error ?? "unknown" });
|
|
147707
148307
|
}
|
|
147708
|
-
|
|
147709
|
-
|
|
147710
|
-
|
|
147711
|
-
|
|
147712
|
-
|
|
148308
|
+
async function applySetupCardAction(ctx, action, value3) {
|
|
148309
|
+
const chatId = action.chatId;
|
|
148310
|
+
if (value3.kind === "confirm") {
|
|
148311
|
+
await confirmSetup(ctx, chatId, action);
|
|
148312
|
+
return;
|
|
147713
148313
|
}
|
|
147714
|
-
|
|
147715
|
-
|
|
147716
|
-
|
|
147717
|
-
|
|
147718
|
-
const raw = await this.read(RECENT_DIRS_KEY);
|
|
147719
|
-
return Array.isArray(raw) ? raw.filter((v) => typeof v === "string") : [];
|
|
148314
|
+
if (value3.kind === "cancel") {
|
|
148315
|
+
await ctx.deps.wizard.cancel(chatId);
|
|
148316
|
+
await ctx.patchCard(action.messageId, buildSetupDoneCard("\u2716\uFE0F \u5DF2\u53D6\u6D88", ["\u5EFA\u4F1A\u8BDD\u5411\u5BFC\u5DF2\u53D6\u6D88\u3002\u53D1\u9001 `/new` \u91CD\u65B0\u5F00\u59CB\u3002"]));
|
|
148317
|
+
return;
|
|
147720
148318
|
}
|
|
147721
|
-
|
|
147722
|
-
const
|
|
147723
|
-
|
|
147724
|
-
|
|
147725
|
-
await this.write(RECENT_DIRS_KEY, list.slice(0, Math.max(1, this.limits.dirs)));
|
|
148319
|
+
if (value3.kind === "form") {
|
|
148320
|
+
const state2 = await ctx.deps.wizard.get(chatId) ?? await ctx.deps.wizard.start(chatId, void 0, action.messageId);
|
|
148321
|
+
await ctx.patchCard(action.messageId, await renderFormCard(ctx, state2));
|
|
148322
|
+
return;
|
|
147726
148323
|
}
|
|
147727
|
-
|
|
147728
|
-
|
|
147729
|
-
|
|
147730
|
-
|
|
148324
|
+
if (value3.kind !== "dir" && value3.sid) {
|
|
148325
|
+
if (value3.kind === "model") {
|
|
148326
|
+
try {
|
|
148327
|
+
const outcome = await ctx.deps.switchSessionModel(value3.sid, value3.model);
|
|
148328
|
+
await ctx.deps.recent.addModel(value3.model);
|
|
148329
|
+
await ctx.patchCard(action.messageId, modelSwitchDoneCard(outcome, value3.model));
|
|
148330
|
+
} catch (err2) {
|
|
148331
|
+
ctx.deps.log.warn("\u5361\u7247\u5207\u6362\u6A21\u578B\u5931\u8D25", { sessionID: value3.sid, error: errorMessage(err2) });
|
|
148332
|
+
await ctx.patchCard(
|
|
148333
|
+
action.messageId,
|
|
148334
|
+
buildSetupDoneCard("\u26A0\uFE0F \u5207\u6362\u6A21\u578B\u5931\u8D25", [errorMessage(err2)])
|
|
148335
|
+
);
|
|
148336
|
+
}
|
|
148337
|
+
return;
|
|
148338
|
+
}
|
|
148339
|
+
if (value3.kind === "perm") {
|
|
148340
|
+
await ctx.deps.applyPermissionPreset(value3.sid, value3.preset);
|
|
148341
|
+
await ctx.patchCard(
|
|
148342
|
+
action.messageId,
|
|
148343
|
+
buildSetupDoneCard("\u2705 \u5DF2\u66F4\u65B0\u6743\u9650", [`\u5F53\u524D\u6743\u9650\uFF1A${presetLabel(value3.preset)}`])
|
|
148344
|
+
);
|
|
148345
|
+
return;
|
|
148346
|
+
}
|
|
148347
|
+
if (value3.kind === "more") {
|
|
148348
|
+
const current = await currentModel(ctx, value3.sid);
|
|
148349
|
+
const models = await ctx.loadModels();
|
|
148350
|
+
const recent = await ctx.deps.recent.listModels();
|
|
148351
|
+
const card = buildModelCard({
|
|
148352
|
+
models,
|
|
148353
|
+
recent,
|
|
148354
|
+
...current ? { current } : {},
|
|
148355
|
+
page: value3.page,
|
|
148356
|
+
pageSize: ctx.modelPageSize,
|
|
148357
|
+
recentLimit: ctx.recentModelsLimit(),
|
|
148358
|
+
sid: value3.sid
|
|
148359
|
+
});
|
|
148360
|
+
await ctx.patchCard(action.messageId, card);
|
|
148361
|
+
return;
|
|
148362
|
+
}
|
|
148363
|
+
return;
|
|
147731
148364
|
}
|
|
147732
|
-
|
|
147733
|
-
|
|
147734
|
-
|
|
147735
|
-
|
|
147736
|
-
await this.write(RECENT_MODELS_KEY, list.slice(0, Math.max(1, this.limits.models)));
|
|
148365
|
+
const state = await ctx.deps.wizard.get(chatId);
|
|
148366
|
+
if (!state) {
|
|
148367
|
+
await ctx.patchCard(action.messageId, buildSetupDoneCard("\u26A0\uFE0F \u5411\u5BFC\u5DF2\u5931\u6548", ["\u8BF7\u91CD\u65B0\u53D1\u9001 `/new` \u5F00\u59CB\u3002"]));
|
|
148368
|
+
return;
|
|
147737
148369
|
}
|
|
147738
|
-
|
|
147739
|
-
|
|
147740
|
-
|
|
147741
|
-
|
|
147742
|
-
|
|
147743
|
-
|
|
147744
|
-
}
|
|
148370
|
+
if (value3.kind === "dir") {
|
|
148371
|
+
const next = await ctx.deps.wizard.apply(chatId, { type: "setDir", dir: value3.dir });
|
|
148372
|
+
await ctx.deps.recent.addDir(value3.dir);
|
|
148373
|
+
if (!next) return;
|
|
148374
|
+
await ctx.patchCard(action.messageId, await renderModelCard(ctx, next));
|
|
148375
|
+
return;
|
|
147745
148376
|
}
|
|
147746
|
-
|
|
147747
|
-
|
|
147748
|
-
|
|
147749
|
-
|
|
147750
|
-
|
|
147751
|
-
|
|
148377
|
+
if (value3.kind === "model") {
|
|
148378
|
+
const next = await ctx.deps.wizard.apply(chatId, { type: "setModel", model: value3.model });
|
|
148379
|
+
await ctx.deps.recent.addModel(value3.model);
|
|
148380
|
+
if (!next) return;
|
|
148381
|
+
await ctx.patchCard(action.messageId, buildPermCard({ ...next.perm ? { current: next.perm } : {} }));
|
|
148382
|
+
return;
|
|
148383
|
+
}
|
|
148384
|
+
if (value3.kind === "perm") {
|
|
148385
|
+
const next = await ctx.deps.wizard.apply(chatId, { type: "setPerm", perm: value3.preset });
|
|
148386
|
+
if (!next) return;
|
|
148387
|
+
await ctx.patchCard(action.messageId, buildConfirmCard(confirmInput(next)));
|
|
148388
|
+
return;
|
|
148389
|
+
}
|
|
148390
|
+
if (value3.kind === "more") {
|
|
148391
|
+
const next = await ctx.deps.wizard.apply(chatId, { type: "setPage", page: value3.page });
|
|
148392
|
+
if (!next) return;
|
|
148393
|
+
await ctx.patchCard(action.messageId, await renderModelCard(ctx, next, value3.page));
|
|
147752
148394
|
}
|
|
147753
|
-
};
|
|
147754
|
-
|
|
147755
|
-
// src/feishu/topic-guidance.ts
|
|
147756
|
-
var TOPIC_GUIDANCE_MARKER = "opencode-feishu-topic-guidance";
|
|
147757
|
-
function buildTopicGuidance(title) {
|
|
147758
|
-
return `\u672C\u4F1A\u8BDD\u4E3B\u9898\uFF1A\u300C${title}\u300D\u3002\u82E5\u7528\u6237\u660E\u663E\u8F6C\u5411\u4E0E\u8BE5\u4E3B\u9898\u65E0\u5173\u7684\u4EFB\u52A1\uFF0C\u53EF\u7B80\u77ED\u63D0\u9192\u4ED6\u7528 /new \u5F00\u65B0\u4F1A\u8BDD\uFF1B\u4E0D\u8981\u56E0\u6B64\u62D2\u7EDD\u56DE\u7B54\uFF0C\u4E5F\u4E0D\u8981\u957F\u7BC7\u8BF4\u6559\u3002`;
|
|
147759
148395
|
}
|
|
147760
|
-
async function
|
|
147761
|
-
|
|
147762
|
-
|
|
147763
|
-
|
|
147764
|
-
|
|
147765
|
-
|
|
147766
|
-
|
|
147767
|
-
|
|
147768
|
-
|
|
147769
|
-
|
|
147770
|
-
|
|
147771
|
-
|
|
147772
|
-
|
|
148396
|
+
async function confirmSetup(ctx, chatId, action) {
|
|
148397
|
+
const state = await ctx.deps.wizard.get(chatId);
|
|
148398
|
+
if (!state || state.step !== "confirm" || !state.dir || !state.perm) {
|
|
148399
|
+
await ctx.patchCard(action.messageId, buildSetupDoneCard("\u26A0\uFE0F \u5411\u5BFC\u72B6\u6001\u4E0D\u5B8C\u6574", ["\u8BF7\u91CD\u65B0\u53D1\u9001 `/new` \u5F00\u59CB\u3002"]));
|
|
148400
|
+
return;
|
|
148401
|
+
}
|
|
148402
|
+
await ctx.deps.wizard.cancel(chatId);
|
|
148403
|
+
const title = state.title?.trim() || defaultSessionTitle(ctx.now());
|
|
148404
|
+
await createSessionFromSetup(ctx, chatId, action, {
|
|
148405
|
+
title,
|
|
148406
|
+
dir: state.dir,
|
|
148407
|
+
perm: state.perm,
|
|
148408
|
+
...state.model ? { model: state.model } : {},
|
|
148409
|
+
...state.anchorMessageId ? { anchorMessageId: state.anchorMessageId } : {}
|
|
148410
|
+
});
|
|
148411
|
+
}
|
|
148412
|
+
async function applySetupFormSubmit(ctx, action) {
|
|
148413
|
+
const fv = action.formValue;
|
|
148414
|
+
const fvKeys = fv && typeof fv === "object" ? Object.keys(fv) : [];
|
|
148415
|
+
ctx.deps.log.info("\u8868\u5355\u63D0\u4EA4\u8FDB\u5165\u5904\u7406", {
|
|
148416
|
+
chatId: action.chatId,
|
|
148417
|
+
messageId: action.messageId,
|
|
148418
|
+
formValueKeys: fvKeys,
|
|
148419
|
+
formValueType: Array.isArray(fv) ? "array" : typeof fv
|
|
148420
|
+
});
|
|
148421
|
+
const values2 = parseSetupFormValues(action.formValue);
|
|
148422
|
+
if (!values2) {
|
|
148423
|
+
ctx.deps.log.warn("\u8868\u5355\u6570\u636E\u7F3A\u5931\uFF08parse \u8FD4\u56DE undefined\uFF09", { formValueKeys: fvKeys });
|
|
148424
|
+
await ctx.patchCard(action.messageId, buildSetupDoneCard("\u26A0\uFE0F \u8868\u5355\u6570\u636E\u7F3A\u5931", ["\u8BF7\u91CD\u65B0\u53D1\u9001 `/form` \u586B\u5199\u3002"]));
|
|
148425
|
+
return;
|
|
148426
|
+
}
|
|
148427
|
+
const state = await ctx.deps.wizard.get(action.chatId);
|
|
148428
|
+
if (!state) {
|
|
148429
|
+
ctx.deps.log.warn("\u8868\u5355\u63D0\u4EA4\u4F46\u5411\u5BFC\u72B6\u6001\u4E0D\u5B58\u5728\uFF08\u53EF\u80FD\u5DF2\u8FC7\u671F/\u5DF2\u88AB\u6D88\u8D39\uFF09", {
|
|
148430
|
+
chatId: action.chatId,
|
|
148431
|
+
dirLen: values2.dir.length,
|
|
148432
|
+
hasModel: Boolean(values2.model),
|
|
148433
|
+
hasPerm: Boolean(values2.perm)
|
|
147773
148434
|
});
|
|
148435
|
+
await ctx.patchCard(action.messageId, buildSetupDoneCard("\u26A0\uFE0F \u8868\u5355\u5DF2\u5931\u6548", ["\u8BF7\u91CD\u65B0\u53D1\u9001 `/form` \u6216 `/new` \u6253\u5F00\u8868\u5355\u3002"]));
|
|
148436
|
+
return;
|
|
148437
|
+
}
|
|
148438
|
+
const title = state.title?.trim() || defaultSessionTitle(ctx.now());
|
|
148439
|
+
const requestedDir = resolveSetupFormDir(values2, ctx.deps.allowedRoots ?? []);
|
|
148440
|
+
let preserved = {
|
|
148441
|
+
dir: requestedDir,
|
|
148442
|
+
...values2.model ? { model: values2.model } : {},
|
|
148443
|
+
...values2.perm ? { perm: values2.perm } : {}
|
|
148444
|
+
};
|
|
148445
|
+
ctx.deps.log.info("\u8868\u5355\u5B57\u6BB5\u89E3\u6790", {
|
|
148446
|
+
dirInput: values2.dir,
|
|
148447
|
+
dirSelect: values2.dirSelect,
|
|
148448
|
+
dir: requestedDir,
|
|
148449
|
+
model: values2.model ? `${values2.model.providerID}/${values2.model.id}` : void 0,
|
|
148450
|
+
perm: values2.perm,
|
|
148451
|
+
hasState: true
|
|
148452
|
+
});
|
|
148453
|
+
const validation = ctx.deps.validateDir(requestedDir);
|
|
148454
|
+
if (!validation.ok) {
|
|
148455
|
+
ctx.deps.log.warn("\u76EE\u5F55\u6821\u9A8C\u5931\u8D25", { dir: requestedDir, reason: validation.message });
|
|
148456
|
+
await ctx.patchCard(
|
|
148457
|
+
action.messageId,
|
|
148458
|
+
await renderFormCard(ctx, state, { error: validation.message, values: preserved })
|
|
148459
|
+
);
|
|
148460
|
+
return;
|
|
148461
|
+
}
|
|
148462
|
+
preserved = { ...preserved, dir: validation.path };
|
|
148463
|
+
if (!values2.perm) {
|
|
148464
|
+
ctx.deps.log.warn("\u6743\u9650\u6863\u4F4D\u7F3A\u5931", { dir: validation.path });
|
|
148465
|
+
await ctx.patchCard(
|
|
148466
|
+
action.messageId,
|
|
148467
|
+
await renderFormCard(ctx, state, { error: "\u8BF7\u9009\u62E9\u6743\u9650\u6863\u4F4D\u3002", values: { ...preserved, dir: validation.path } })
|
|
148468
|
+
);
|
|
148469
|
+
return;
|
|
148470
|
+
}
|
|
148471
|
+
const model = values2.model ? await resolveFormModel(ctx, values2.model) : state?.model;
|
|
148472
|
+
ctx.deps.log.info("\u8868\u5355\u6821\u9A8C\u901A\u8FC7\uFF0C\u5F00\u59CB\u5EFA\u4F1A\u8BDD", {
|
|
148473
|
+
dir: validation.path,
|
|
148474
|
+
perm: values2.perm,
|
|
148475
|
+
model: model ? `${model.providerID}/${model.id}` : void 0
|
|
148476
|
+
});
|
|
148477
|
+
await ctx.deps.wizard.cancel(action.chatId);
|
|
148478
|
+
await createSessionFromSetup(ctx, action.chatId, action, {
|
|
148479
|
+
title,
|
|
148480
|
+
dir: validation.path,
|
|
148481
|
+
perm: values2.perm,
|
|
148482
|
+
...model ? { model } : {}
|
|
148483
|
+
});
|
|
148484
|
+
}
|
|
148485
|
+
async function resolveFormModel(ctx, ref) {
|
|
148486
|
+
const models = await ctx.loadModels();
|
|
148487
|
+
const matched = matchModel(`${ref.providerID}/${ref.id}`, models);
|
|
148488
|
+
return matched.ok ? matched.model : ref;
|
|
148489
|
+
}
|
|
148490
|
+
async function createSessionFromSetup(ctx, chatId, action, opts) {
|
|
148491
|
+
const { title, dir, perm, model } = opts;
|
|
148492
|
+
const permissions = presetToRuleset(perm);
|
|
148493
|
+
const gateMode = presetGateMode(perm);
|
|
148494
|
+
const created = await ctx.deps.createSession({
|
|
148495
|
+
title,
|
|
148496
|
+
chatId,
|
|
148497
|
+
openId: action.operatorOpenId,
|
|
148498
|
+
setActive: true,
|
|
148499
|
+
directory: dir,
|
|
148500
|
+
permissions,
|
|
148501
|
+
perm,
|
|
148502
|
+
gateMode,
|
|
148503
|
+
...model ? { model } : {}
|
|
148504
|
+
});
|
|
148505
|
+
await ctx.deps.recent.addDir(dir);
|
|
148506
|
+
if (model) await ctx.deps.recent.addModel(model);
|
|
148507
|
+
const readyCard = buildSessionReadyCard({
|
|
148508
|
+
title,
|
|
148509
|
+
sessionID: created.id,
|
|
148510
|
+
dir,
|
|
148511
|
+
...model ? { model: modelLabel(model) } : {},
|
|
148512
|
+
perm: presetLabel(perm)
|
|
148513
|
+
});
|
|
148514
|
+
const anchorId = action.messageId || opts.anchorMessageId || "";
|
|
148515
|
+
const res = await ctx.deps.sender.replyCard(anchorId, readyCard, { replyInThread: true });
|
|
148516
|
+
ctx.deps.log.info("\u521B\u5EFA\u4F1A\u8BDD\u5E76\u5F00\u8BDD\u9898", {
|
|
148517
|
+
sessionID: created.id,
|
|
148518
|
+
anchorMessageId: anchorId,
|
|
148519
|
+
anchorFromFormCard: Boolean(action.messageId),
|
|
148520
|
+
replyOk: res.ok,
|
|
148521
|
+
replyMessageId: res.messageId,
|
|
148522
|
+
replyThreadId: res.threadId,
|
|
148523
|
+
replyError: res.error
|
|
148524
|
+
});
|
|
148525
|
+
if (!res.ok || !res.messageId) {
|
|
148526
|
+
ctx.deps.log.warn("\u4E00\u952E\u5F00\u8BDD\u9898\u5931\u8D25", { error: res.error ?? "unknown" });
|
|
148527
|
+
await ctx.patchCard(
|
|
148528
|
+
action.messageId,
|
|
148529
|
+
buildSessionCreatedCard({
|
|
148530
|
+
title,
|
|
148531
|
+
sessionID: created.id,
|
|
148532
|
+
dir,
|
|
148533
|
+
...model ? { model: modelLabel(model) } : {},
|
|
148534
|
+
perm: presetLabel(perm),
|
|
148535
|
+
note: "\u26A0\uFE0F \u81EA\u52A8\u5F00\u8BDD\u9898\u5931\u8D25\uFF1A\u8BF7\u5728 `/sessions` \u7684\u4F1A\u8BDD\u5361\u4E0A\u624B\u52A8\u300C\u521B\u5EFA\u8BDD\u9898\u300D\uFF0C\u6216\u5728\u4E3B\u804A\u5929\u6D41\u7528 `/use` \u5207\u6362\u540E\u7EE7\u7EED\u3002"
|
|
148536
|
+
})
|
|
148537
|
+
);
|
|
148538
|
+
return;
|
|
148539
|
+
}
|
|
148540
|
+
await ctx.deps.sessionMap.bindRoot(anchorId, created.id);
|
|
148541
|
+
const meta = res.threadId ? void 0 : await ctx.deps.sender.getMessageMeta(res.messageId);
|
|
148542
|
+
const threadId = res.threadId ?? meta?.threadId;
|
|
148543
|
+
if (threadId) {
|
|
148544
|
+
await ctx.deps.sessionMap.bindThread(threadId, created.id, chatId, action.operatorOpenId, anchorId);
|
|
148545
|
+
} else {
|
|
148546
|
+
ctx.deps.log.warn("\u4E00\u952E\u5F00\u8BDD\u9898\u540E\u672A\u8BFB\u5230 thread_id\uFF0C\u8BE5\u4F1A\u8BDD\u6682\u65E0\u6CD5\u81EA\u52A8\u8DEF\u7531", { messageId: res.messageId });
|
|
148547
|
+
}
|
|
148548
|
+
await ctx.patchCard(
|
|
148549
|
+
action.messageId,
|
|
148550
|
+
buildSessionCreatedCard({
|
|
148551
|
+
title,
|
|
148552
|
+
sessionID: created.id,
|
|
148553
|
+
dir,
|
|
148554
|
+
...model ? { model: modelLabel(model) } : {},
|
|
148555
|
+
perm: presetLabel(perm),
|
|
148556
|
+
...threadId ? {} : { note: "\uFF08\u672A\u62FF\u5230\u8BDD\u9898 ID\uFF0C\u82E5\u8BDD\u9898\u672A\u51FA\u73B0\u8BF7\u5728\u4F1A\u8BDD\u5361\u4E0A\u624B\u52A8\u521B\u5EFA\u3002\uFF09" }
|
|
148557
|
+
})
|
|
148558
|
+
);
|
|
148559
|
+
}
|
|
148560
|
+
async function renderFormCard(ctx, state, over) {
|
|
148561
|
+
const models = await ctx.loadModels();
|
|
148562
|
+
const recent = await ctx.deps.recent.listModels();
|
|
148563
|
+
const rootSubdirs = await scanRoot(ctx, ctx.deps.allowedRoots?.[0]);
|
|
148564
|
+
const values2 = over?.values ?? {
|
|
148565
|
+
...state?.dir ? { dir: state.dir } : {},
|
|
148566
|
+
...state?.perm ? { perm: state.perm } : {}
|
|
148567
|
+
};
|
|
148568
|
+
return buildSetupFormCard({
|
|
148569
|
+
models,
|
|
148570
|
+
recent,
|
|
148571
|
+
...state?.model ? { defaultModel: state.model } : {},
|
|
148572
|
+
...rootSubdirs.length > 0 ? { rootSubdirs } : {},
|
|
148573
|
+
...ctx.deps.allowedRoots ? { allowedRoots: ctx.deps.allowedRoots } : {},
|
|
148574
|
+
...over?.error ? { error: over.error } : {},
|
|
148575
|
+
values: values2
|
|
148576
|
+
});
|
|
148577
|
+
}
|
|
148578
|
+
async function scanRoot(ctx, root) {
|
|
148579
|
+
if (!root) return [];
|
|
148580
|
+
const scan = ctx.deps.scanRootSubdirs ?? scanRootSubdirs;
|
|
148581
|
+
try {
|
|
148582
|
+
return [...await scan(root)];
|
|
147774
148583
|
} catch (err2) {
|
|
147775
|
-
deps.log.warn("\
|
|
148584
|
+
ctx.deps.log.warn("\u6839\u76EE\u5F55\u5B50\u76EE\u5F55\u626B\u63CF\u5931\u8D25", { error: errorMessage(err2) });
|
|
148585
|
+
return [];
|
|
147776
148586
|
}
|
|
147777
148587
|
}
|
|
147778
|
-
function
|
|
147779
|
-
|
|
147780
|
-
|
|
147781
|
-
|
|
147782
|
-
|
|
148588
|
+
function confirmInput(state) {
|
|
148589
|
+
return {
|
|
148590
|
+
...state.title ? { title: state.title } : {},
|
|
148591
|
+
...state.dir ? { dir: state.dir } : {},
|
|
148592
|
+
...state.model ? { model: state.model } : {},
|
|
148593
|
+
...state.perm ? { perm: state.perm } : {}
|
|
148594
|
+
};
|
|
148595
|
+
}
|
|
148596
|
+
function createSetupWizardApi(ctx) {
|
|
148597
|
+
return {
|
|
148598
|
+
cmdNew: (message, args2) => cmdNew(ctx, message, args2),
|
|
148599
|
+
cmdForm: (message, args2) => cmdForm(ctx, message, args2),
|
|
148600
|
+
cmdDir: (message, args2) => cmdDir(ctx, message, args2),
|
|
148601
|
+
cmdModel: (message, args2, scope3) => cmdModel(ctx, message, args2, scope3),
|
|
148602
|
+
cmdPerm: (message, args2, scope3) => cmdPerm(ctx, message, args2, scope3),
|
|
148603
|
+
cmdCancel: (message) => cmdCancel(ctx, message),
|
|
148604
|
+
applySetupCardAction: (action, value3) => applySetupCardAction(ctx, action, value3),
|
|
148605
|
+
applySetupFormSubmit: (action) => applySetupFormSubmit(ctx, action),
|
|
148606
|
+
sendSetupFormForChat: (chatId, anchorMessageId, openId) => sendSetupFormForChat(ctx, chatId, anchorMessageId, openId)
|
|
148607
|
+
};
|
|
147783
148608
|
}
|
|
147784
148609
|
|
|
147785
|
-
// src/
|
|
147786
|
-
function
|
|
147787
|
-
|
|
147788
|
-
|
|
148610
|
+
// src/session/session-list.ts
|
|
148611
|
+
async function cmdSessions(ctx, message, page = 0) {
|
|
148612
|
+
const entries3 = await loadSessionEntries(ctx, message.chatId);
|
|
148613
|
+
const active = await ctx.deps.sessionMap.getActive(message.chatId);
|
|
148614
|
+
const card = await buildListCard(ctx, message.chatId, entries3, page, active?.sessionID);
|
|
148615
|
+
const res = await ctx.deps.sender.sendCard(message.chatId, card);
|
|
148616
|
+
if (!res.ok) {
|
|
148617
|
+
ctx.deps.log.warn("\u4F1A\u8BDD\u5361\u7247\u53D1\u9001\u5931\u8D25", { chatId: message.chatId, error: res.error ?? "unknown" });
|
|
148618
|
+
}
|
|
148619
|
+
}
|
|
148620
|
+
async function loadSessionEntries(ctx, chatId) {
|
|
148621
|
+
if (ctx.deps.listAllSessions) {
|
|
148622
|
+
try {
|
|
148623
|
+
const raw = await ctx.deps.listAllSessions();
|
|
148624
|
+
const normalized = normalizeSessionList(raw);
|
|
148625
|
+
if (normalized) {
|
|
148626
|
+
ctx.deps.log.info("\u5217\u51FA\u5168\u90E8\u4F1A\u8BDD", { count: normalized.length, source: "session.list" });
|
|
148627
|
+
return normalized;
|
|
148628
|
+
}
|
|
148629
|
+
ctx.deps.log.warn("session.list \u8FD4\u56DE\u5F62\u72B6\u65E0\u6CD5\u8BC6\u522B\uFF0C\u56DE\u9000 SessionMap \u5217\u8868", { chatId });
|
|
148630
|
+
} catch (err2) {
|
|
148631
|
+
ctx.deps.log.warn("session.list \u8C03\u7528\u5931\u8D25\uFF0C\u56DE\u9000 SessionMap \u5217\u8868", {
|
|
148632
|
+
chatId,
|
|
148633
|
+
error: errorMessage(err2)
|
|
148634
|
+
});
|
|
148635
|
+
}
|
|
148636
|
+
} else {
|
|
148637
|
+
ctx.deps.log.warn("\u8FD0\u884C\u65F6\u672A\u66B4\u9732 session.list\uFF0C\u56DE\u9000 SessionMap \u5217\u8868", { chatId });
|
|
147789
148638
|
}
|
|
147790
|
-
|
|
147791
|
-
|
|
148639
|
+
const fallback = fallbackEntries(await ctx.deps.sessionMap.listSessions(chatId));
|
|
148640
|
+
ctx.deps.log.warn("\u4F1A\u8BDD\u5217\u8868\u56DE\u9000\u5230 SessionMap", { chatId, count: fallback.length });
|
|
148641
|
+
return fallback;
|
|
148642
|
+
}
|
|
148643
|
+
async function buildListCard(ctx, chatId, entries3, page, activeID) {
|
|
148644
|
+
const pageSize = ctx.sessionPageSize;
|
|
148645
|
+
const pageCount = Math.max(1, Math.ceil(entries3.length / pageSize));
|
|
148646
|
+
const safePage = Math.min(Math.max(0, Math.floor(page)), pageCount - 1);
|
|
148647
|
+
const slice = entries3.slice(safePage * pageSize, safePage * pageSize + pageSize);
|
|
148648
|
+
const rows = [];
|
|
148649
|
+
for (let i = 0; i < slice.length; i += 1) {
|
|
148650
|
+
const entry = slice[i];
|
|
148651
|
+
const bound = Boolean(await ctx.deps.sessionMap.threadIdForSession(entry.sessionID));
|
|
148652
|
+
rows.push({
|
|
148653
|
+
index: safePage * pageSize + i + 1,
|
|
148654
|
+
sessionID: entry.sessionID,
|
|
148655
|
+
title: entry.title,
|
|
148656
|
+
updatedAt: entry.updatedAt,
|
|
148657
|
+
...entry.directory ? { directory: entry.directory } : {},
|
|
148658
|
+
bound,
|
|
148659
|
+
...entry.sessionID === activeID ? { active: true } : {}
|
|
148660
|
+
});
|
|
147792
148661
|
}
|
|
147793
|
-
|
|
147794
|
-
|
|
148662
|
+
return buildSessionListCard({
|
|
148663
|
+
chatId,
|
|
148664
|
+
rows,
|
|
148665
|
+
page: safePage,
|
|
148666
|
+
pageCount,
|
|
148667
|
+
total: entries3.length,
|
|
148668
|
+
now: ctx.now()
|
|
148669
|
+
});
|
|
148670
|
+
}
|
|
148671
|
+
async function enterSessionThread(ctx, input) {
|
|
148672
|
+
const { chatId, sessionID, anchorMessageId, operatorOpenId } = input;
|
|
148673
|
+
if (!anchorMessageId || !chatId) {
|
|
148674
|
+
return { ok: false, error: "missing anchor/chat" };
|
|
147795
148675
|
}
|
|
147796
|
-
|
|
147797
|
-
|
|
148676
|
+
const info = input.info;
|
|
148677
|
+
const card = buildSessionOpenedCard({
|
|
148678
|
+
title: info?.title ?? "",
|
|
148679
|
+
sessionID,
|
|
148680
|
+
...info?.directory ? { dir: info.directory } : {},
|
|
148681
|
+
...info?.updatedAt ? { updatedAt: info.updatedAt } : {},
|
|
148682
|
+
now: ctx.now()
|
|
148683
|
+
});
|
|
148684
|
+
const res = await ctx.deps.sender.replyCard(anchorMessageId, card, { replyInThread: true });
|
|
148685
|
+
ctx.deps.log.info("\u8FDB\u5165\u4F1A\u8BDD\u5E76\u5F00\u8BDD\u9898", {
|
|
148686
|
+
sessionID,
|
|
148687
|
+
source: input.source,
|
|
148688
|
+
anchorMessageId,
|
|
148689
|
+
replyOk: res.ok,
|
|
148690
|
+
replyMessageId: res.messageId,
|
|
148691
|
+
replyThreadId: res.threadId,
|
|
148692
|
+
replyError: res.error
|
|
148693
|
+
});
|
|
148694
|
+
if (!res.ok || !res.messageId) {
|
|
148695
|
+
const error = res.error ?? "unknown";
|
|
148696
|
+
ctx.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u5931\u8D25", { sessionID, anchorMessageId, error });
|
|
148697
|
+
if (input.patchMessageId) {
|
|
148698
|
+
await ctx.deps.sender.patchCard(
|
|
148699
|
+
input.patchMessageId,
|
|
148700
|
+
buildSessionMissingCard(sessionID, error)
|
|
148701
|
+
);
|
|
148702
|
+
}
|
|
148703
|
+
return { ok: false, error };
|
|
148704
|
+
}
|
|
148705
|
+
const meta = res.threadId ? void 0 : await ctx.deps.sender.getMessageMeta(res.messageId);
|
|
148706
|
+
const threadId = res.threadId ?? meta?.threadId;
|
|
148707
|
+
if (!threadId) {
|
|
148708
|
+
ctx.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u540E\u672A\u8BFB\u5230 thread_id\uFF0C\u8BE5\u4F1A\u8BDD\u6682\u65E0\u6CD5\u81EA\u52A8\u8DEF\u7531", { messageId: res.messageId });
|
|
148709
|
+
return { ok: false, error: "\u672A\u62FF\u5230\u8BDD\u9898 ID" };
|
|
148710
|
+
}
|
|
148711
|
+
await ctx.deps.sessionMap.bindThread(threadId, sessionID, chatId, operatorOpenId, anchorMessageId);
|
|
148712
|
+
await ctx.deps.sessionMap.bindRoot(res.messageId, sessionID);
|
|
148713
|
+
ctx.deps.log.info("\u5DF2\u7ED1\u5B9A\u8BDD\u9898\u4E0E\u4F1A\u8BDD", { sessionID, threadId, rootId: res.messageId });
|
|
148714
|
+
return { ok: true, threadId };
|
|
148715
|
+
}
|
|
148716
|
+
async function handleOpenCardAction(ctx, action, value3) {
|
|
148717
|
+
if (!ctx.threadRouting) {
|
|
148718
|
+
return toast3("error", "\u5F53\u524D\u4E3A\u56DE\u9000\u6A21\u5F0F\uFF0C\u4E0D\u652F\u6301\u8BDD\u9898\u8DEF\u7531");
|
|
148719
|
+
}
|
|
148720
|
+
const chatId = action.chatId || value3.chatId;
|
|
148721
|
+
const hasApi = Boolean(ctx.deps.getSessionInfo);
|
|
148722
|
+
const info = hasApi ? normalizeSessionInfo(await ctx.deps.getSessionInfo(value3.sessionID)) : void 0;
|
|
148723
|
+
if (hasApi && !info) {
|
|
148724
|
+
ctx.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E0D\u5B58\u5728", { sessionID: value3.sessionID, chatId });
|
|
148725
|
+
void patchMissingCard(ctx, action.messageId, value3.sessionID).catch((err2) => {
|
|
148726
|
+
ctx.deps.log.warn("\u4F1A\u8BDD\u4E0D\u5B58\u5728\u63D0\u793A\u5361 patch \u5931\u8D25", { error: errorMessage(err2) });
|
|
148727
|
+
});
|
|
148728
|
+
return toast3("error", "\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
147798
148729
|
}
|
|
147799
|
-
|
|
148730
|
+
const snapshot2 = info ?? { sessionID: value3.sessionID, title: "", updatedAt: 0 };
|
|
148731
|
+
void enterSessionThread(ctx, {
|
|
148732
|
+
chatId,
|
|
148733
|
+
sessionID: value3.sessionID,
|
|
148734
|
+
anchorMessageId: action.messageId,
|
|
148735
|
+
operatorOpenId: action.operatorOpenId,
|
|
148736
|
+
info: snapshot2,
|
|
148737
|
+
patchMessageId: action.messageId,
|
|
148738
|
+
source: "card"
|
|
148739
|
+
}).catch((err2) => {
|
|
148740
|
+
ctx.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u5904\u7406\u5931\u8D25", { sessionID: value3.sessionID, error: errorMessage(err2) });
|
|
148741
|
+
});
|
|
148742
|
+
return toast3("success", "\u6B63\u5728\u8FDB\u5165\u8BDD\u9898\u2026");
|
|
147800
148743
|
}
|
|
147801
|
-
function
|
|
147802
|
-
if (!
|
|
147803
|
-
|
|
147804
|
-
|
|
147805
|
-
|
|
148744
|
+
async function patchMissingCard(ctx, messageId, sessionID) {
|
|
148745
|
+
if (!messageId) return;
|
|
148746
|
+
await ctx.deps.sender.patchCard(messageId, buildSessionMissingCard(sessionID));
|
|
148747
|
+
}
|
|
148748
|
+
async function applySessionCardAction(ctx, action, value3) {
|
|
148749
|
+
const chatId = action.chatId || value3.chatId;
|
|
148750
|
+
if (!chatId) return;
|
|
148751
|
+
if (value3.cmd === "new") {
|
|
148752
|
+
await sendSetupFormForChat(ctx, chatId, action.messageId, action.operatorOpenId);
|
|
148753
|
+
return;
|
|
147806
148754
|
}
|
|
147807
|
-
if (
|
|
147808
|
-
|
|
148755
|
+
if (value3.cmd === "list") {
|
|
148756
|
+
if (action.messageId) await patchListCard(ctx, chatId, action.messageId, value3.page);
|
|
148757
|
+
return;
|
|
147809
148758
|
}
|
|
147810
|
-
if (
|
|
147811
|
-
return
|
|
148759
|
+
if (value3.cmd === "open") {
|
|
148760
|
+
return;
|
|
147812
148761
|
}
|
|
147813
|
-
|
|
148762
|
+
const ok = await ctx.deps.sessionMap.setActive(chatId, value3.sessionID);
|
|
148763
|
+
if (!ok) ctx.deps.log.debug("\u5361\u7247\u5207\u6362\u4F1A\u8BDD\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E0D\u5B58\u5728", { sessionID: value3.sessionID });
|
|
148764
|
+
if (action.messageId) await patchListCard(ctx, chatId, action.messageId);
|
|
147814
148765
|
}
|
|
147815
|
-
function
|
|
147816
|
-
|
|
147817
|
-
const
|
|
147818
|
-
const
|
|
147819
|
-
const
|
|
147820
|
-
if (!
|
|
147821
|
-
if (d !== "once" && d !== "always" && d !== "reject") return void 0;
|
|
147822
|
-
return { token, decision: d };
|
|
148766
|
+
async function patchListCard(ctx, chatId, messageId, page = 0) {
|
|
148767
|
+
const entries3 = await loadSessionEntries(ctx, chatId);
|
|
148768
|
+
const active = await ctx.deps.sessionMap.getActive(chatId);
|
|
148769
|
+
const card = await buildListCard(ctx, chatId, entries3, page, active?.sessionID);
|
|
148770
|
+
const res = await ctx.deps.sender.patchCard(messageId, card);
|
|
148771
|
+
if (!res.ok) ctx.deps.log.warn("\u4F1A\u8BDD\u5361\u7247\u66F4\u65B0\u5931\u8D25", { error: res.error ?? "unknown" });
|
|
147823
148772
|
}
|
|
147824
|
-
|
|
147825
|
-
|
|
147826
|
-
|
|
147827
|
-
|
|
148773
|
+
function createSessionListApi(ctx) {
|
|
148774
|
+
return {
|
|
148775
|
+
cmdSessions: (message, page) => cmdSessions(ctx, message, page),
|
|
148776
|
+
loadSessionEntries: (chatId) => loadSessionEntries(ctx, chatId),
|
|
148777
|
+
buildListCard: (chatId, entries3, page, activeID) => buildListCard(ctx, chatId, entries3, page, activeID),
|
|
148778
|
+
enterSessionThread: (input) => enterSessionThread(ctx, input),
|
|
148779
|
+
handleOpenCardAction: (action, value3) => handleOpenCardAction(ctx, action, value3),
|
|
148780
|
+
patchMissingCard: (messageId, sessionID) => patchMissingCard(ctx, messageId, sessionID),
|
|
148781
|
+
applySessionCardAction: (action, value3) => applySessionCardAction(ctx, action, value3),
|
|
148782
|
+
patchListCard: (chatId, messageId, page) => patchListCard(ctx, chatId, messageId, page)
|
|
148783
|
+
};
|
|
148784
|
+
}
|
|
148785
|
+
|
|
148786
|
+
// src/session/session-ops.ts
|
|
148787
|
+
async function cmdUse(ctx, message, args2) {
|
|
148788
|
+
const sessions = await ctx.deps.sessionMap.listSessions(message.chatId);
|
|
148789
|
+
if (sessions.length === 0) {
|
|
148790
|
+
await ctx.reply(message, "\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u3002\u4F7F\u7528 /new \u65B0\u5EFA\u4E00\u4E2A\u3002");
|
|
148791
|
+
return;
|
|
147828
148792
|
}
|
|
147829
|
-
|
|
147830
|
-
|
|
147831
|
-
|
|
147832
|
-
|
|
147833
|
-
|
|
147834
|
-
|
|
147835
|
-
|
|
147836
|
-
|
|
147837
|
-
|
|
147838
|
-
|
|
147839
|
-
|
|
147840
|
-
|
|
147841
|
-
|
|
147842
|
-
|
|
148793
|
+
const matched = matchSession(args2, sessions);
|
|
148794
|
+
if (!matched.ok) {
|
|
148795
|
+
await ctx.reply(message, useErrorText(matched.reason));
|
|
148796
|
+
return;
|
|
148797
|
+
}
|
|
148798
|
+
const entry = matched.entry;
|
|
148799
|
+
const ok = await ctx.deps.sessionMap.setActive(message.chatId, entry.sessionID);
|
|
148800
|
+
if (!ok) {
|
|
148801
|
+
await ctx.reply(message, "\u5207\u6362\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF0C\u5148\u7528 /sessions \u67E5\u770B\u5217\u8868\u3002");
|
|
148802
|
+
return;
|
|
148803
|
+
}
|
|
148804
|
+
await ctx.reply(message, `\u2705 \u5DF2\u5207\u6362\u5230\u300C${entry.title.trim() || "(\u672A\u547D\u540D)"}\u300D
|
|
148805
|
+
\`${entry.sessionID}\``);
|
|
148806
|
+
}
|
|
148807
|
+
async function cmdCurrent(ctx, message, scope3) {
|
|
148808
|
+
if (scope3 === "thread") {
|
|
148809
|
+
const sessionID = await ctx.threadSessionID(message);
|
|
148810
|
+
if (!sessionID) {
|
|
148811
|
+
await ctx.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\u3002\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u7528 `/new` \u65B0\u5EFA\u3002");
|
|
147843
148812
|
return;
|
|
147844
148813
|
}
|
|
147845
|
-
const
|
|
147846
|
-
const
|
|
147847
|
-
|
|
147848
|
-
|
|
147849
|
-
|
|
147850
|
-
|
|
147851
|
-
|
|
147852
|
-
|
|
147853
|
-
|
|
147854
|
-
|
|
147855
|
-
|
|
147856
|
-
|
|
147857
|
-
|
|
147858
|
-
|
|
147859
|
-
|
|
147860
|
-
|
|
147861
|
-
|
|
147862
|
-
|
|
147863
|
-
|
|
148814
|
+
const entry = await ctx.deps.sessionMap.getSession(message.chatId, sessionID);
|
|
148815
|
+
const link2 = await ctx.deps.sessionMap.resolveBySession(sessionID);
|
|
148816
|
+
const model = await currentModel(ctx, sessionID) ?? link2?.model;
|
|
148817
|
+
const lines = [
|
|
148818
|
+
`\u672C\u8BDD\u9898\u4F1A\u8BDD\uFF1A\u300C${entry?.title.trim() || "(\u672A\u547D\u540D)"}\u300D`,
|
|
148819
|
+
`\`${sessionID}\``,
|
|
148820
|
+
...link2?.dir ? [`\u76EE\u5F55\uFF1A\`${link2.dir}\``] : [],
|
|
148821
|
+
...model ? [`\u6A21\u578B\uFF1A${modelLabel(model)}`] : [],
|
|
148822
|
+
...link2?.perm ? [`\u6743\u9650\uFF1A${presetLabel(link2.perm)}`] : []
|
|
148823
|
+
];
|
|
148824
|
+
await ctx.reply(message, lines.join("\n"));
|
|
148825
|
+
return;
|
|
148826
|
+
}
|
|
148827
|
+
const active = await ctx.deps.sessionMap.getActive(message.chatId);
|
|
148828
|
+
if (!active) {
|
|
148829
|
+
await ctx.reply(message, "\u5F53\u524D\u6CA1\u6709\u4F1A\u8BDD\u3002\u4F7F\u7528 /new \u65B0\u5EFA\u4E00\u4E2A\u3002");
|
|
148830
|
+
return;
|
|
148831
|
+
}
|
|
148832
|
+
const count = (await ctx.deps.sessionMap.listSessions(message.chatId)).length;
|
|
148833
|
+
await ctx.reply(
|
|
148834
|
+
message,
|
|
148835
|
+
`\u5F53\u524D\u4F1A\u8BDD\uFF1A\u300C${active.title.trim() || "(\u672A\u547D\u540D)"}\u300D
|
|
148836
|
+
\`${active.sessionID}\`
|
|
148837
|
+
\u5171 ${count} \u4E2A\u4F1A\u8BDD\u3002`
|
|
148838
|
+
);
|
|
148839
|
+
}
|
|
148840
|
+
async function cmdStop(ctx, message, scope3) {
|
|
148841
|
+
if (scope3 === "thread") {
|
|
148842
|
+
const sessionID = await ctx.threadSessionID(message);
|
|
148843
|
+
if (!sessionID) {
|
|
148844
|
+
await ctx.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\uFF0C\u65E0\u6CD5\u4E2D\u65AD\u3002");
|
|
147864
148845
|
return;
|
|
147865
148846
|
}
|
|
147866
|
-
|
|
147867
|
-
|
|
147868
|
-
|
|
147869
|
-
|
|
147870
|
-
resolved: false
|
|
147871
|
-
});
|
|
147872
|
-
this.deps.log.info("\u5BA1\u6279\u5361\u5DF2\u53D1\u9001", {
|
|
147873
|
-
requestID,
|
|
147874
|
-
action: request3.action,
|
|
147875
|
-
canPersistAlways,
|
|
147876
|
-
hasDir: Boolean(link2.dir)
|
|
147877
|
-
});
|
|
148847
|
+
await ctx.deps.interruptSession(sessionID);
|
|
148848
|
+
const entry = await ctx.deps.sessionMap.getSession(message.chatId, sessionID);
|
|
148849
|
+
await ctx.reply(message, `\u23F9\uFE0F \u5DF2\u8BF7\u6C42\u4E2D\u65AD\u672C\u8BDD\u9898\u4F1A\u8BDD\uFF1A\u300C${entry?.title.trim() || "(\u672A\u547D\u540D)"}\u300D`);
|
|
148850
|
+
return;
|
|
147878
148851
|
}
|
|
147879
|
-
|
|
147880
|
-
|
|
147881
|
-
|
|
147882
|
-
|
|
147883
|
-
if (!tracked.resolved) {
|
|
147884
|
-
const outcome = {
|
|
147885
|
-
reply: event.reply,
|
|
147886
|
-
operatorOpenId: "",
|
|
147887
|
-
at: this.now()
|
|
147888
|
-
};
|
|
147889
|
-
void this.patchResolved(tracked, outcome, event.requestID);
|
|
147890
|
-
}
|
|
147891
|
-
this.cards.delete(event.requestID);
|
|
148852
|
+
const active = await ctx.deps.sessionMap.getActive(message.chatId);
|
|
148853
|
+
if (!active) {
|
|
148854
|
+
await ctx.reply(message, "\u5F53\u524D\u6CA1\u6709\u4F1A\u8BDD\u53EF\u4E2D\u65AD\u3002");
|
|
148855
|
+
return;
|
|
147892
148856
|
}
|
|
147893
|
-
|
|
147894
|
-
|
|
147895
|
-
|
|
147896
|
-
|
|
147897
|
-
|
|
147898
|
-
|
|
147899
|
-
|
|
147900
|
-
return toast2("error", "\u65E0\u6CD5\u8BC6\u522B\u7684\u64CD\u4F5C");
|
|
147901
|
-
}
|
|
147902
|
-
if (!this.deps.isAllowed(action.operatorOpenId)) {
|
|
147903
|
-
this.deps.log.warn("\u62D2\u7EDD\u975E\u767D\u540D\u5355\u7528\u6237\u7684\u5BA1\u6279\u70B9\u51FB", {
|
|
147904
|
-
operator: action.operatorOpenId.slice(0, 8)
|
|
147905
|
-
});
|
|
147906
|
-
return toast2("error", "\u65E0\u5BA1\u6279\u6743\u9650");
|
|
147907
|
-
}
|
|
147908
|
-
const verified = this.deps.verify(parsed.token, { u: action.operatorOpenId });
|
|
147909
|
-
if (!verified.ok) {
|
|
147910
|
-
this.deps.log.warn("\u5BA1\u6279 token \u6821\u9A8C\u5931\u8D25", { reason: verified.reason });
|
|
147911
|
-
return toast2("error", `\u5BA1\u6279\u51ED\u8BC1\u65E0\u6548\uFF08${verified.reason}\uFF09`);
|
|
147912
|
-
}
|
|
147913
|
-
const claims = verified.claims;
|
|
147914
|
-
const ttl = Math.max(1e3, claims.e - this.now());
|
|
147915
|
-
if (!this.deps.replay.consume(claims.n, ttl)) {
|
|
147916
|
-
return toast2("warning", "\u8BE5\u64CD\u4F5C\u5DF2\u5904\u7406\uFF0C\u8BF7\u52FF\u91CD\u590D\u70B9\u51FB");
|
|
147917
|
-
}
|
|
147918
|
-
const tracked = this.cards.get(claims.r);
|
|
147919
|
-
const outcome = {
|
|
147920
|
-
reply: parsed.decision,
|
|
147921
|
-
operatorOpenId: action.operatorOpenId,
|
|
147922
|
-
at: this.now()
|
|
147923
|
-
};
|
|
147924
|
-
void this.applyReply(claims.r, claims.s, parsed.decision, tracked, outcome, tracked?.directory);
|
|
147925
|
-
return toast2(
|
|
147926
|
-
parsed.decision === "reject" ? "warning" : "success",
|
|
147927
|
-
parsed.decision === "reject" ? "\u5DF2\u62D2\u7EDD" : "\u5DF2\u5141\u8BB8"
|
|
147928
|
-
);
|
|
148857
|
+
await ctx.deps.interruptSession(active.sessionID);
|
|
148858
|
+
await ctx.reply(message, `\u23F9\uFE0F \u5DF2\u8BF7\u6C42\u4E2D\u65AD\u5F53\u524D\u4F1A\u8BDD\uFF1A\u300C${active.title.trim() || "(\u672A\u547D\u540D)"}\u300D`);
|
|
148859
|
+
}
|
|
148860
|
+
async function cmdCd(ctx, message, args2) {
|
|
148861
|
+
if (!message.threadId) {
|
|
148862
|
+
await ctx.reply(message, "`/cd` \u53EA\u80FD\u5728\u8BDD\u9898\u5185\u4F7F\u7528\uFF08\u7528\u4E8E\u79FB\u52A8\u8BE5\u8BDD\u9898\u4F1A\u8BDD\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF09\u3002");
|
|
148863
|
+
return;
|
|
147929
148864
|
}
|
|
147930
|
-
|
|
147931
|
-
|
|
147932
|
-
|
|
148865
|
+
const sessionID = await ctx.threadSessionID(message);
|
|
148866
|
+
if (!sessionID) {
|
|
148867
|
+
await ctx.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\u3002\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u7528 `/new` \u65B0\u5EFA\u3002");
|
|
148868
|
+
return;
|
|
147933
148869
|
}
|
|
147934
|
-
|
|
147935
|
-
|
|
147936
|
-
|
|
147937
|
-
|
|
147938
|
-
|
|
147939
|
-
|
|
147940
|
-
|
|
147941
|
-
|
|
147942
|
-
|
|
148870
|
+
const validation = ctx.deps.validateDir(args2);
|
|
148871
|
+
if (!validation.ok) {
|
|
148872
|
+
await ctx.reply(message, validation.message);
|
|
148873
|
+
return;
|
|
148874
|
+
}
|
|
148875
|
+
await ctx.deps.moveSessionDir(sessionID, validation.path);
|
|
148876
|
+
await ctx.deps.recent.addDir(validation.path);
|
|
148877
|
+
await ctx.reply(message, `\u2705 \u5DF2\u5207\u6362\u672C\u4F1A\u8BDD\u76EE\u5F55\uFF1A\`${validation.path}\``);
|
|
148878
|
+
}
|
|
148879
|
+
async function cmdResume(ctx, message, args2) {
|
|
148880
|
+
if (message.threadId) {
|
|
148881
|
+
await ctx.reply(message, threadForbiddenText("resume"));
|
|
148882
|
+
return;
|
|
148883
|
+
}
|
|
148884
|
+
if (!ctx.threadRouting) {
|
|
148885
|
+
await ctx.reply(message, "\u5F53\u524D\u4E3A\u56DE\u9000\u6A21\u5F0F\uFF08`threadRouting=false`\uFF09\uFF0C\u4E0D\u652F\u6301\u7EED\u804A\u5386\u53F2\u4F1A\u8BDD\u3002");
|
|
148886
|
+
return;
|
|
148887
|
+
}
|
|
148888
|
+
const entries3 = await loadSessionEntries(ctx, message.chatId);
|
|
148889
|
+
if (entries3.length === 0) {
|
|
148890
|
+
await ctx.reply(message, "\u6CA1\u6709\u53EF\u7EED\u804A\u7684\u4F1A\u8BDD\u3002\u7528 `/new` \u65B0\u5EFA\uFF0C\u6216\u5148\u5728\u8BDD\u9898\u91CC\u53D1\u6D88\u606F\u521B\u5EFA\u4E00\u4E2A\u3002");
|
|
148891
|
+
return;
|
|
148892
|
+
}
|
|
148893
|
+
const query = args2.trim();
|
|
148894
|
+
let index2 = 0;
|
|
148895
|
+
if (query) {
|
|
148896
|
+
if (!/^\d+$/.test(query)) {
|
|
148897
|
+
await ctx.reply(message, "\u7528\u6CD5\uFF1A`/resume [\u5E8F\u53F7]`\uFF08\u5E8F\u53F7\u89C1 `/sessions`\uFF1B\u7701\u7565 = \u6700\u8FD1\u66F4\u65B0\u7684\u4F1A\u8BDD\uFF09\u3002");
|
|
147943
148898
|
return;
|
|
147944
148899
|
}
|
|
147945
|
-
|
|
147946
|
-
await this.patchResolved(tracked, outcome, requestID);
|
|
147947
|
-
}
|
|
148900
|
+
index2 = Number.parseInt(query, 10) - 1;
|
|
147948
148901
|
}
|
|
147949
|
-
|
|
147950
|
-
|
|
147951
|
-
|
|
147952
|
-
|
|
148902
|
+
if (index2 < 0 || index2 >= entries3.length) {
|
|
148903
|
+
await ctx.reply(
|
|
148904
|
+
message,
|
|
148905
|
+
`\u5E8F\u53F7\u8D8A\u754C\uFF1A\u5F53\u524D\u5171 ${entries3.length} \u4E2A\u4F1A\u8BDD\uFF0C\u6709\u6548\u8303\u56F4 1\u2013${entries3.length}\u3002`
|
|
148906
|
+
);
|
|
148907
|
+
return;
|
|
148908
|
+
}
|
|
148909
|
+
const entry = entries3[index2];
|
|
148910
|
+
const result3 = await enterSessionThread(ctx, {
|
|
148911
|
+
chatId: message.chatId,
|
|
148912
|
+
sessionID: entry.sessionID,
|
|
148913
|
+
anchorMessageId: message.messageId,
|
|
148914
|
+
operatorOpenId: message.senderOpenId,
|
|
148915
|
+
info: entry,
|
|
148916
|
+
source: "resume"
|
|
148917
|
+
});
|
|
148918
|
+
if (!result3.ok) {
|
|
148919
|
+
await ctx.reply(
|
|
148920
|
+
message,
|
|
148921
|
+
`\u26A0\uFE0F \u8FDB\u5165\u8BDD\u9898\u5931\u8D25\uFF1A${result3.error ?? "unknown"}\u3002\u53EF\u53D1\u9001 \`/sessions\` \u624B\u52A8\u8FDB\u5165\u3002`
|
|
148922
|
+
);
|
|
147953
148923
|
}
|
|
147954
|
-
};
|
|
147955
|
-
function toast2(type, content) {
|
|
147956
|
-
return { toast: { type, content } };
|
|
147957
148924
|
}
|
|
147958
|
-
|
|
147959
|
-
|
|
147960
|
-
|
|
147961
|
-
|
|
147962
|
-
|
|
147963
|
-
let entries3;
|
|
147964
|
-
try {
|
|
147965
|
-
entries3 = await rd(rootPath, { withFileTypes: true });
|
|
147966
|
-
} catch {
|
|
147967
|
-
return [];
|
|
148925
|
+
async function cmdNow(ctx, message, scope3) {
|
|
148926
|
+
const sessionID = await ctx.scopeSessionID(message, scope3);
|
|
148927
|
+
if (!sessionID) {
|
|
148928
|
+
await ctx.reply(message, "\u5F53\u524D\u6CA1\u6709\u4F1A\u8BDD\u3002");
|
|
148929
|
+
return;
|
|
147968
148930
|
}
|
|
147969
|
-
|
|
147970
|
-
|
|
147971
|
-
|
|
147972
|
-
const path2 = join(rootPath, name);
|
|
147973
|
-
out.push({ name, path: path2, isRepo: await hasGit(path2, rd) });
|
|
148931
|
+
if (!ctx.deps.promoteQueued) {
|
|
148932
|
+
await ctx.reply(message, "\u5F53\u524D\u7248\u672C\u4E0D\u652F\u6301\u63D2\u961F\uFF08\u8FD0\u884C\u65F6\u672A\u66B4\u9732 inbox\uFF09\u3002");
|
|
148933
|
+
return;
|
|
147974
148934
|
}
|
|
147975
|
-
|
|
148935
|
+
const promoted = await ctx.deps.promoteQueued(sessionID);
|
|
148936
|
+
if (promoted < 0) {
|
|
148937
|
+
await ctx.reply(message, "\u5F53\u524D\u7248\u672C\u4E0D\u652F\u6301\u63D2\u961F\uFF08\u8FD0\u884C\u65F6\u672A\u66B4\u9732 inbox\uFF09\u3002");
|
|
148938
|
+
return;
|
|
148939
|
+
}
|
|
148940
|
+
if (promoted === 0) {
|
|
148941
|
+
await ctx.reply(message, "\u6CA1\u6709\u6392\u961F\u4E2D\u7684\u6D88\u606F\uFF08\u8BE5\u4F1A\u8BDD\u5F53\u524D\u7A7A\u95F2\u6216\u65E0\u5F85\u6295\u9012\u9879\uFF09\u3002");
|
|
148942
|
+
return;
|
|
148943
|
+
}
|
|
148944
|
+
await ctx.reply(message, `\u26A1 \u5DF2\u628A ${promoted} \u6761\u6392\u961F\u6D88\u606F\u6539\u4E3A\u7ACB\u5373\u63D2\u961F\u6267\u884C\u3002`);
|
|
147976
148945
|
}
|
|
147977
|
-
async function
|
|
147978
|
-
|
|
147979
|
-
|
|
147980
|
-
|
|
147981
|
-
|
|
147982
|
-
|
|
148946
|
+
async function cmdSteer(ctx, message, args2, scope3) {
|
|
148947
|
+
const sessionID = await ctx.scopeSessionID(message, scope3);
|
|
148948
|
+
if (!sessionID) {
|
|
148949
|
+
await ctx.reply(message, "\u5F53\u524D\u6CA1\u6709\u4F1A\u8BDD\u3002");
|
|
148950
|
+
return;
|
|
148951
|
+
}
|
|
148952
|
+
const text = args2.trim();
|
|
148953
|
+
if (!text) {
|
|
148954
|
+
await ctx.reply(message, "\u7528\u6CD5\uFF1A`/steer <\u6587\u672C>`\uFF08\u7ACB\u5373\u63D2\u961F\u53D1\u9001\uFF09\uFF1B\u628A\u5DF2\u6392\u961F\u6D88\u606F\u63D2\u961F\u8BF7\u7528 `/now`\u3002");
|
|
148955
|
+
return;
|
|
148956
|
+
}
|
|
148957
|
+
if (!ctx.deps.steerPrompt) {
|
|
148958
|
+
await ctx.reply(message, "\u5F53\u524D\u7248\u672C\u4E0D\u652F\u6301\u63D2\u961F\u3002");
|
|
148959
|
+
return;
|
|
147983
148960
|
}
|
|
148961
|
+
await ctx.deps.steerPrompt(message, sessionID, text);
|
|
148962
|
+
}
|
|
148963
|
+
function createSessionOpsApi(ctx) {
|
|
148964
|
+
return {
|
|
148965
|
+
cmdUse: (message, args2) => cmdUse(ctx, message, args2),
|
|
148966
|
+
cmdCurrent: (message, scope3) => cmdCurrent(ctx, message, scope3),
|
|
148967
|
+
cmdStop: (message, scope3) => cmdStop(ctx, message, scope3),
|
|
148968
|
+
cmdCd: (message, args2) => cmdCd(ctx, message, args2),
|
|
148969
|
+
cmdResume: (message, args2) => cmdResume(ctx, message, args2),
|
|
148970
|
+
cmdNow: (message, scope3) => cmdNow(ctx, message, scope3),
|
|
148971
|
+
cmdSteer: (message, args2, scope3) => cmdSteer(ctx, message, args2, scope3)
|
|
148972
|
+
};
|
|
147984
148973
|
}
|
|
147985
148974
|
|
|
147986
148975
|
// src/session-commands.ts
|
|
147987
148976
|
var SessionCommands = class {
|
|
148977
|
+
ctx;
|
|
147988
148978
|
constructor(deps) {
|
|
147989
|
-
|
|
147990
|
-
|
|
147991
|
-
|
|
147992
|
-
|
|
147993
|
-
|
|
148979
|
+
const ctx = createSessionContext(deps);
|
|
148980
|
+
Object.assign(
|
|
148981
|
+
ctx,
|
|
148982
|
+
createSessionListApi(ctx),
|
|
148983
|
+
createSetupWizardApi(ctx),
|
|
148984
|
+
createSessionOpsApi(ctx),
|
|
148985
|
+
createModelPermApi(ctx)
|
|
148986
|
+
);
|
|
148987
|
+
this.ctx = ctx;
|
|
147994
148988
|
}
|
|
147995
|
-
deps;
|
|
147996
|
-
now;
|
|
147997
|
-
threadRouting;
|
|
147998
|
-
modelPageSize;
|
|
147999
|
-
sessionPageSize;
|
|
148000
148989
|
/**
|
|
148001
148990
|
* 文本命令拦截。返回 true 表示已作为命令处理(调用方**不得**再发 prompt)。
|
|
148002
148991
|
* 非命令返回 false。
|
|
@@ -148007,13 +148996,13 @@ var SessionCommands = class {
|
|
|
148007
148996
|
const scope3 = message.threadId ? "thread" : "main";
|
|
148008
148997
|
try {
|
|
148009
148998
|
if (scope3 === "thread" && !isCommandAllowedInThread(parsed.name)) {
|
|
148010
|
-
await this.reply(message, threadForbiddenText(parsed.raw));
|
|
148999
|
+
await this.ctx.reply(message, threadForbiddenText(parsed.raw));
|
|
148011
149000
|
return true;
|
|
148012
149001
|
}
|
|
148013
149002
|
await this.dispatch(parsed, message, scope3);
|
|
148014
149003
|
} catch (err2) {
|
|
148015
|
-
this.deps.log.warn("\u4F1A\u8BDD\u547D\u4EE4\u5904\u7406\u5931\u8D25", { command: parsed.raw, error: errorMessage(err2) });
|
|
148016
|
-
await this.reply(message, `\u26A0\uFE0F \u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${errorMessage(err2)}`);
|
|
149004
|
+
this.ctx.deps.log.warn("\u4F1A\u8BDD\u547D\u4EE4\u5904\u7406\u5931\u8D25", { command: parsed.raw, error: errorMessage(err2) });
|
|
149005
|
+
await this.ctx.reply(message, `\u26A0\uFE0F \u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${errorMessage(err2)}`);
|
|
148017
149006
|
}
|
|
148018
149007
|
return true;
|
|
148019
149008
|
}
|
|
@@ -148025,1105 +149014,272 @@ var SessionCommands = class {
|
|
|
148025
149014
|
* gateway 的 SDK 会 await 该 Promise 作为回调响应,仍应在 3s 内完成。
|
|
148026
149015
|
*/
|
|
148027
149016
|
handleCardAction(action) {
|
|
149017
|
+
const ctx = this.ctx;
|
|
148028
149018
|
if (isSetupFormAction(action.rawValue) || parseSetupFormValues(action.formValue)) {
|
|
148029
|
-
if (!
|
|
148030
|
-
|
|
149019
|
+
if (!ctx.deps.isAllowed(action.operatorOpenId)) {
|
|
149020
|
+
ctx.deps.log.warn("\u62D2\u7EDD\u975E\u767D\u540D\u5355\u7528\u6237\u7684\u8868\u5355\u63D0\u4EA4", { operator: action.operatorOpenId.slice(0, 8) });
|
|
148031
149021
|
return toast3("error", "\u65E0\u64CD\u4F5C\u6743\u9650");
|
|
148032
149022
|
}
|
|
148033
|
-
void
|
|
148034
|
-
|
|
149023
|
+
void ctx.applySetupFormSubmit(action).catch((err2) => {
|
|
149024
|
+
ctx.deps.log.warn("\u8868\u5355\u63D0\u4EA4\u5904\u7406\u5931\u8D25", { error: errorMessage(err2) });
|
|
148035
149025
|
});
|
|
148036
149026
|
return toast3("success", "\u6B63\u5728\u521B\u5EFA\u4F1A\u8BDD\u2026");
|
|
148037
149027
|
}
|
|
148038
149028
|
const sessionValue = parseSessionCardValue(action.rawValue);
|
|
148039
149029
|
const setupValue = sessionValue ? void 0 : parseSetupCardValue(action.rawValue);
|
|
148040
149030
|
if (!sessionValue && !setupValue) return toast3("error", "\u65E0\u6CD5\u8BC6\u522B\u7684\u64CD\u4F5C");
|
|
148041
|
-
if (!
|
|
148042
|
-
|
|
149031
|
+
if (!ctx.deps.isAllowed(action.operatorOpenId)) {
|
|
149032
|
+
ctx.deps.log.warn("\u62D2\u7EDD\u975E\u767D\u540D\u5355\u7528\u6237\u7684\u5361\u7247\u70B9\u51FB", { operator: action.operatorOpenId.slice(0, 8) });
|
|
148043
149033
|
return toast3("error", "\u65E0\u64CD\u4F5C\u6743\u9650");
|
|
148044
149034
|
}
|
|
148045
149035
|
if (sessionValue) {
|
|
148046
149036
|
if (sessionValue.cmd === "open") {
|
|
148047
|
-
return
|
|
149037
|
+
return ctx.handleOpenCardAction(action, sessionValue);
|
|
148048
149038
|
}
|
|
148049
|
-
void
|
|
148050
|
-
|
|
149039
|
+
void ctx.applySessionCardAction(action, sessionValue).catch((err2) => {
|
|
149040
|
+
ctx.deps.log.warn("\u4F1A\u8BDD\u5361\u7247\u64CD\u4F5C\u5931\u8D25", { cmd: sessionValue.cmd, error: errorMessage(err2) });
|
|
148051
149041
|
});
|
|
148052
149042
|
if (sessionValue.cmd === "new") return toast3("success", "\u6B63\u5728\u6253\u5F00\u8868\u5355\u2026");
|
|
148053
149043
|
if (sessionValue.cmd === "list") return toast3("info", "\u5DF2\u7FFB\u9875");
|
|
148054
149044
|
return toast3("success", "\u5DF2\u5207\u6362");
|
|
148055
149045
|
}
|
|
148056
149046
|
const value3 = setupValue;
|
|
148057
|
-
void
|
|
148058
|
-
|
|
149047
|
+
void ctx.applySetupCardAction(action, value3).catch((err2) => {
|
|
149048
|
+
ctx.deps.log.warn("\u5411\u5BFC\u5361\u7247\u64CD\u4F5C\u5931\u8D25", { wizard: value3.kind, error: errorMessage(err2) });
|
|
148059
149049
|
});
|
|
148060
149050
|
return setupToast(value3);
|
|
148061
149051
|
}
|
|
148062
|
-
/**
|
|
148063
|
-
* `{cmd:"open"}`:白名单(上层已校验)→ `ctx.session.get` 校验存在 →
|
|
148064
|
-
* 不存在 toast「会话不存在」并 patch 提示;存在则后台开话题并绑定。
|
|
148065
|
-
*/
|
|
148066
|
-
async handleOpenCardAction(action, value3) {
|
|
148067
|
-
if (!this.threadRouting) {
|
|
148068
|
-
return toast3("error", "\u5F53\u524D\u4E3A\u56DE\u9000\u6A21\u5F0F\uFF0C\u4E0D\u652F\u6301\u8BDD\u9898\u8DEF\u7531");
|
|
148069
|
-
}
|
|
148070
|
-
const chatId = action.chatId || value3.chatId;
|
|
148071
|
-
const hasApi = Boolean(this.deps.getSessionInfo);
|
|
148072
|
-
const info = hasApi ? normalizeSessionInfo(await this.deps.getSessionInfo(value3.sessionID)) : void 0;
|
|
148073
|
-
if (hasApi && !info) {
|
|
148074
|
-
this.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E0D\u5B58\u5728", { sessionID: value3.sessionID, chatId });
|
|
148075
|
-
void this.patchMissingCard(action.messageId, value3.sessionID).catch((err2) => {
|
|
148076
|
-
this.deps.log.warn("\u4F1A\u8BDD\u4E0D\u5B58\u5728\u63D0\u793A\u5361 patch \u5931\u8D25", { error: errorMessage(err2) });
|
|
148077
|
-
});
|
|
148078
|
-
return toast3("error", "\u4F1A\u8BDD\u4E0D\u5B58\u5728");
|
|
148079
|
-
}
|
|
148080
|
-
const snapshot2 = info ?? { sessionID: value3.sessionID, title: "", updatedAt: 0 };
|
|
148081
|
-
void this.enterSessionThread({
|
|
148082
|
-
chatId,
|
|
148083
|
-
sessionID: value3.sessionID,
|
|
148084
|
-
anchorMessageId: action.messageId,
|
|
148085
|
-
operatorOpenId: action.operatorOpenId,
|
|
148086
|
-
info: snapshot2,
|
|
148087
|
-
patchMessageId: action.messageId,
|
|
148088
|
-
source: "card"
|
|
148089
|
-
}).catch((err2) => {
|
|
148090
|
-
this.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u5904\u7406\u5931\u8D25", { sessionID: value3.sessionID, error: errorMessage(err2) });
|
|
148091
|
-
});
|
|
148092
|
-
return toast3("success", "\u6B63\u5728\u8FDB\u5165\u8BDD\u9898\u2026");
|
|
148093
|
-
}
|
|
148094
|
-
/** 会话不存在:把原列表卡 patch 成提示卡。 */
|
|
148095
|
-
async patchMissingCard(messageId, sessionID) {
|
|
148096
|
-
if (!messageId) return;
|
|
148097
|
-
await this.deps.sender.patchCard(messageId, buildSessionMissingCard(sessionID));
|
|
148098
|
-
}
|
|
148099
|
-
/**
|
|
148100
|
-
* 「进入话题」核心流程(卡片动作 / `/resume` 共用):
|
|
148101
|
-
* `reply_in_thread` 对触发消息开新话题 → 发「✅ 已进入会话」卡 → 拿 thread_id 绑定
|
|
148102
|
-
* thread + root 映射。成功后该话题内消息即续上历史会话(opencode 上下文天然持久)。
|
|
148103
|
-
*/
|
|
148104
|
-
async enterSessionThread(input) {
|
|
148105
|
-
const { chatId, sessionID, anchorMessageId, operatorOpenId } = input;
|
|
148106
|
-
if (!anchorMessageId || !chatId) {
|
|
148107
|
-
return { ok: false, error: "missing anchor/chat" };
|
|
148108
|
-
}
|
|
148109
|
-
const info = input.info;
|
|
148110
|
-
const card = buildSessionOpenedCard({
|
|
148111
|
-
title: info?.title ?? "",
|
|
148112
|
-
sessionID,
|
|
148113
|
-
...info?.directory ? { dir: info.directory } : {},
|
|
148114
|
-
...info?.updatedAt ? { updatedAt: info.updatedAt } : {},
|
|
148115
|
-
now: this.now()
|
|
148116
|
-
});
|
|
148117
|
-
const res = await this.deps.sender.replyCard(anchorMessageId, card, { replyInThread: true });
|
|
148118
|
-
this.deps.log.info("\u8FDB\u5165\u4F1A\u8BDD\u5E76\u5F00\u8BDD\u9898", {
|
|
148119
|
-
sessionID,
|
|
148120
|
-
source: input.source,
|
|
148121
|
-
anchorMessageId,
|
|
148122
|
-
replyOk: res.ok,
|
|
148123
|
-
replyMessageId: res.messageId,
|
|
148124
|
-
replyThreadId: res.threadId,
|
|
148125
|
-
replyError: res.error
|
|
148126
|
-
});
|
|
148127
|
-
if (!res.ok || !res.messageId) {
|
|
148128
|
-
const error = res.error ?? "unknown";
|
|
148129
|
-
this.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u5931\u8D25", { sessionID, anchorMessageId, error });
|
|
148130
|
-
if (input.patchMessageId) {
|
|
148131
|
-
await this.deps.sender.patchCard(
|
|
148132
|
-
input.patchMessageId,
|
|
148133
|
-
buildSessionMissingCard(sessionID, error)
|
|
148134
|
-
);
|
|
148135
|
-
}
|
|
148136
|
-
return { ok: false, error };
|
|
148137
|
-
}
|
|
148138
|
-
const meta = res.threadId ? void 0 : await this.deps.sender.getMessageMeta(res.messageId);
|
|
148139
|
-
const threadId = res.threadId ?? meta?.threadId;
|
|
148140
|
-
if (!threadId) {
|
|
148141
|
-
this.deps.log.warn("\u8FDB\u5165\u8BDD\u9898\u540E\u672A\u8BFB\u5230 thread_id\uFF0C\u8BE5\u4F1A\u8BDD\u6682\u65E0\u6CD5\u81EA\u52A8\u8DEF\u7531", { messageId: res.messageId });
|
|
148142
|
-
return { ok: false, error: "\u672A\u62FF\u5230\u8BDD\u9898 ID" };
|
|
148143
|
-
}
|
|
148144
|
-
await this.deps.sessionMap.bindThread(threadId, sessionID, chatId, operatorOpenId, anchorMessageId);
|
|
148145
|
-
await this.deps.sessionMap.bindRoot(res.messageId, sessionID);
|
|
148146
|
-
this.deps.log.info("\u5DF2\u7ED1\u5B9A\u8BDD\u9898\u4E0E\u4F1A\u8BDD", { sessionID, threadId, rootId: res.messageId });
|
|
148147
|
-
return { ok: true, threadId };
|
|
148148
|
-
}
|
|
148149
149052
|
async dispatch(parsed, message, scope3) {
|
|
148150
149053
|
switch (parsed.name) {
|
|
148151
149054
|
case "new":
|
|
148152
|
-
return this.cmdNew(message, parsed.args);
|
|
149055
|
+
return this.ctx.cmdNew(message, parsed.args);
|
|
148153
149056
|
case "sessions":
|
|
148154
|
-
return this.cmdSessions(message);
|
|
149057
|
+
return this.ctx.cmdSessions(message);
|
|
148155
149058
|
case "use":
|
|
148156
|
-
return this.cmdUse(message, parsed.args);
|
|
149059
|
+
return this.ctx.cmdUse(message, parsed.args);
|
|
148157
149060
|
case "resume":
|
|
148158
|
-
return this.cmdResume(message, parsed.args);
|
|
149061
|
+
return this.ctx.cmdResume(message, parsed.args);
|
|
148159
149062
|
case "current":
|
|
148160
|
-
return this.cmdCurrent(message, scope3);
|
|
149063
|
+
return this.ctx.cmdCurrent(message, scope3);
|
|
148161
149064
|
case "stop":
|
|
148162
|
-
return this.cmdStop(message, scope3);
|
|
149065
|
+
return this.ctx.cmdStop(message, scope3);
|
|
148163
149066
|
case "steer":
|
|
148164
|
-
return this.cmdSteer(message, parsed.args, scope3);
|
|
149067
|
+
return this.ctx.cmdSteer(message, parsed.args, scope3);
|
|
148165
149068
|
case "now":
|
|
148166
|
-
return this.cmdNow(message, scope3);
|
|
149069
|
+
return this.ctx.cmdNow(message, scope3);
|
|
148167
149070
|
case "dir":
|
|
148168
|
-
return this.cmdDir(message, parsed.args);
|
|
149071
|
+
return this.ctx.cmdDir(message, parsed.args);
|
|
148169
149072
|
case "model":
|
|
148170
|
-
return this.cmdModel(message, parsed.args, scope3);
|
|
149073
|
+
return this.ctx.cmdModel(message, parsed.args, scope3);
|
|
148171
149074
|
case "perm":
|
|
148172
|
-
return this.cmdPerm(message, parsed.args, scope3);
|
|
149075
|
+
return this.ctx.cmdPerm(message, parsed.args, scope3);
|
|
148173
149076
|
case "cd":
|
|
148174
|
-
return this.cmdCd(message, parsed.args);
|
|
149077
|
+
return this.ctx.cmdCd(message, parsed.args);
|
|
148175
149078
|
case "cancel":
|
|
148176
|
-
return this.cmdCancel(message);
|
|
149079
|
+
return this.ctx.cmdCancel(message);
|
|
148177
149080
|
case "form":
|
|
148178
|
-
return this.cmdForm(message, parsed.args);
|
|
149081
|
+
return this.ctx.cmdForm(message, parsed.args);
|
|
148179
149082
|
case "unknown":
|
|
148180
|
-
return this.reply(message, `\u672A\u77E5\u547D\u4EE4 \`/${parsed.raw}\`
|
|
149083
|
+
return this.ctx.reply(message, `\u672A\u77E5\u547D\u4EE4 \`/${parsed.raw}\`
|
|
148181
149084
|
|
|
148182
149085
|
${helpText(scope3)}`);
|
|
148183
149086
|
case "help":
|
|
148184
149087
|
default:
|
|
148185
|
-
return this.reply(message, helpText(scope3));
|
|
148186
|
-
}
|
|
148187
|
-
}
|
|
148188
|
-
// ── 建会话(主聊天流) ────────────────────────────────────────────────
|
|
148189
|
-
/**
|
|
148190
|
-
* `/new`:与 `/form` **完全等价**,直接发建会话表单卡(不再走目录→模型→权限→确认分步卡)。
|
|
148191
|
-
* 带标题时写入向导状态,表单提交后作为会话标题。
|
|
148192
|
-
*/
|
|
148193
|
-
async cmdNew(message, args2) {
|
|
148194
|
-
const title = args2.trim() || void 0;
|
|
148195
|
-
if (!this.threadRouting) {
|
|
148196
|
-
const finalTitle = title ?? defaultSessionTitle(this.now());
|
|
148197
|
-
const created = await this.deps.createSession({
|
|
148198
|
-
title: finalTitle,
|
|
148199
|
-
chatId: message.chatId,
|
|
148200
|
-
openId: message.senderOpenId
|
|
148201
|
-
});
|
|
148202
|
-
await this.reply(message, `\u2705 \u5DF2\u65B0\u5EFA\u5E76\u5207\u6362\u5230\u4F1A\u8BDD\u300C${finalTitle}\u300D
|
|
148203
|
-
\`${created.id}\``);
|
|
148204
|
-
return;
|
|
148205
|
-
}
|
|
148206
|
-
await this.openSetupForm(message, title);
|
|
148207
|
-
}
|
|
148208
|
-
/**
|
|
148209
|
-
* `/form [标题]`:直接打开发建会话表单卡。
|
|
148210
|
-
* 与 `/new [标题]` 走同一入口,二者完全等价。
|
|
148211
|
-
*/
|
|
148212
|
-
async cmdForm(message, args2) {
|
|
148213
|
-
await this.openSetupForm(message, args2.trim() || void 0);
|
|
148214
|
-
}
|
|
148215
|
-
/**
|
|
148216
|
-
* `/new` / `/form` 共同入口:发(或复用)建会话表单卡。
|
|
148217
|
-
* 已有向导状态时保留 `/dir` `/model` `/perm` 预填的字段;带标题则更新标题。
|
|
148218
|
-
*/
|
|
148219
|
-
async openSetupForm(message, title) {
|
|
148220
|
-
if (message.threadId) {
|
|
148221
|
-
await this.reply(message, threadForbiddenText("form"));
|
|
148222
|
-
return;
|
|
148223
|
-
}
|
|
148224
|
-
if (!this.threadRouting) {
|
|
148225
|
-
await this.reply(message, "\u5F53\u524D\u4E3A\u56DE\u9000\u6A21\u5F0F\uFF08`threadRouting=false`\uFF09\uFF0C\u4E0D\u652F\u6301\u8868\u5355\u5EFA\u4F1A\u8BDD\uFF0C\u8BF7\u7528 `/new`\u3002");
|
|
148226
|
-
return;
|
|
148227
|
-
}
|
|
148228
|
-
let state = await this.deps.wizard.get(message.chatId);
|
|
148229
|
-
if (!state) {
|
|
148230
|
-
state = await this.deps.wizard.start(message.chatId, title, message.messageId);
|
|
148231
|
-
} else if (title !== void 0) {
|
|
148232
|
-
state = { ...state, title };
|
|
148233
|
-
await this.deps.wizard.set(message.chatId, state);
|
|
148234
|
-
}
|
|
148235
|
-
const card = await this.renderFormCard(state);
|
|
148236
|
-
const res = await this.deps.sender.sendCard(message.chatId, card);
|
|
148237
|
-
if (!res.ok) this.deps.log.warn("\u8868\u5355\u5361\u53D1\u9001\u5931\u8D25", { chatId: message.chatId, error: res.error ?? "unknown" });
|
|
148238
|
-
}
|
|
148239
|
-
/**
|
|
148240
|
-
* `/dir <path>`:目录只作表单**预填**(不再是必经步骤)。
|
|
148241
|
-
* 目录容错:留空 = 允许根目录;不存在则自动创建(仍在 allowedRoots 之下)。
|
|
148242
|
-
*/
|
|
148243
|
-
async cmdDir(message, args2) {
|
|
148244
|
-
if (message.threadId) {
|
|
148245
|
-
await this.reply(message, threadForbiddenText("dir"));
|
|
148246
|
-
return;
|
|
148247
|
-
}
|
|
148248
|
-
const validation = this.deps.validateDir(args2);
|
|
148249
|
-
if (!validation.ok) {
|
|
148250
|
-
await this.reply(message, validation.message);
|
|
148251
|
-
return;
|
|
148252
|
-
}
|
|
148253
|
-
if (!await this.deps.wizard.get(message.chatId)) {
|
|
148254
|
-
await this.deps.wizard.start(message.chatId, void 0, message.messageId);
|
|
148255
|
-
}
|
|
148256
|
-
const state = await this.deps.wizard.apply(message.chatId, { type: "setDir", dir: validation.path });
|
|
148257
|
-
await this.deps.recent.addDir(validation.path);
|
|
148258
|
-
if (!state) {
|
|
148259
|
-
await this.reply(message, "\u5411\u5BFC\u72B6\u6001\u5DF2\u4E22\u5931\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001 `/new` \u5F00\u59CB\u3002");
|
|
148260
|
-
return;
|
|
148261
|
-
}
|
|
148262
|
-
await this.deps.sender.sendCard(message.chatId, await this.renderFormCard(state));
|
|
148263
|
-
}
|
|
148264
|
-
/** `/model [关键词]`:向导内选模型(仅预填表单);话题内切换当前会话模型。 */
|
|
148265
|
-
async cmdModel(message, args2, scope3) {
|
|
148266
|
-
if (scope3 === "thread") {
|
|
148267
|
-
const sessionID = await this.threadSessionID(message);
|
|
148268
|
-
if (!sessionID) {
|
|
148269
|
-
await this.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\u3002\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u7528 `/new` \u65B0\u5EFA\u3002");
|
|
148270
|
-
return;
|
|
148271
|
-
}
|
|
148272
|
-
if (!args2.trim()) {
|
|
148273
|
-
await this.sendModelCardToThread(message, sessionID, 0);
|
|
148274
|
-
return;
|
|
148275
|
-
}
|
|
148276
|
-
const models2 = await this.loadModels();
|
|
148277
|
-
const matched2 = matchModel(args2, models2);
|
|
148278
|
-
if (!matched2.ok) {
|
|
148279
|
-
await this.reply(message, modelMatchErrorText(matched2.reason, matched2.candidates));
|
|
148280
|
-
return;
|
|
148281
|
-
}
|
|
148282
|
-
try {
|
|
148283
|
-
const outcome = await this.deps.switchSessionModel(sessionID, matched2.model);
|
|
148284
|
-
await this.deps.recent.addModel(matched2.model);
|
|
148285
|
-
await this.reply(message, modelSwitchReply(outcome, matched2.model));
|
|
148286
|
-
} catch (err2) {
|
|
148287
|
-
this.deps.log.warn("\u5207\u6362\u4F1A\u8BDD\u6A21\u578B\u5931\u8D25", { sessionID, error: errorMessage(err2) });
|
|
148288
|
-
await this.reply(message, `\u26A0\uFE0F \u5207\u6362\u6A21\u578B\u5931\u8D25\uFF1A${errorMessage(err2)}`);
|
|
148289
|
-
}
|
|
148290
|
-
return;
|
|
148291
|
-
}
|
|
148292
|
-
if (!await this.deps.wizard.get(message.chatId)) {
|
|
148293
|
-
await this.deps.wizard.start(message.chatId, void 0, message.messageId);
|
|
148294
|
-
}
|
|
148295
|
-
if (!args2.trim()) {
|
|
148296
|
-
const state = await this.deps.wizard.get(message.chatId);
|
|
148297
|
-
await this.deps.sender.sendCard(message.chatId, await this.renderFormCard(state));
|
|
148298
|
-
return;
|
|
148299
|
-
}
|
|
148300
|
-
const models = await this.loadModels();
|
|
148301
|
-
const matched = matchModel(args2, models);
|
|
148302
|
-
if (!matched.ok) {
|
|
148303
|
-
await this.reply(message, modelMatchErrorText(matched.reason, matched.candidates));
|
|
148304
|
-
return;
|
|
148305
|
-
}
|
|
148306
|
-
const next = await this.deps.wizard.apply(message.chatId, { type: "setModel", model: matched.model });
|
|
148307
|
-
await this.deps.recent.addModel(matched.model);
|
|
148308
|
-
if (!next) return;
|
|
148309
|
-
await this.deps.sender.sendCard(message.chatId, await this.renderFormCard(next));
|
|
148310
|
-
}
|
|
148311
|
-
/** `/perm [档位]`:向导内选权限(仅预填表单);话题内修改当前会话权限。 */
|
|
148312
|
-
async cmdPerm(message, args2, scope3) {
|
|
148313
|
-
const arg = args2.trim();
|
|
148314
|
-
if (scope3 === "thread") {
|
|
148315
|
-
const sessionID = await this.threadSessionID(message);
|
|
148316
|
-
if (!sessionID) {
|
|
148317
|
-
await this.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\u3002\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u7528 `/new` \u65B0\u5EFA\u3002");
|
|
148318
|
-
return;
|
|
148319
|
-
}
|
|
148320
|
-
if (!arg) {
|
|
148321
|
-
await this.sendPermCardToThread(message, sessionID);
|
|
148322
|
-
return;
|
|
148323
|
-
}
|
|
148324
|
-
if (!isPermissionPreset(arg)) {
|
|
148325
|
-
await this.reply(message, permUsageText());
|
|
148326
|
-
return;
|
|
148327
|
-
}
|
|
148328
|
-
await this.deps.applyPermissionPreset(sessionID, arg);
|
|
148329
|
-
await this.reply(message, `\u2705 \u5DF2\u66F4\u65B0\u672C\u4F1A\u8BDD\u6743\u9650\uFF1A${presetLabel(arg)}`);
|
|
148330
|
-
return;
|
|
148331
|
-
}
|
|
148332
|
-
if (!await this.deps.wizard.get(message.chatId)) {
|
|
148333
|
-
await this.deps.wizard.start(message.chatId, void 0, message.messageId);
|
|
148334
|
-
}
|
|
148335
|
-
if (!arg) {
|
|
148336
|
-
const state = await this.deps.wizard.get(message.chatId);
|
|
148337
|
-
await this.deps.sender.sendCard(message.chatId, await this.renderFormCard(state));
|
|
148338
|
-
return;
|
|
148339
|
-
}
|
|
148340
|
-
if (!isPermissionPreset(arg)) {
|
|
148341
|
-
await this.reply(message, permUsageText());
|
|
148342
|
-
return;
|
|
148343
|
-
}
|
|
148344
|
-
const next = await this.deps.wizard.apply(message.chatId, { type: "setPerm", perm: arg });
|
|
148345
|
-
if (!next) return;
|
|
148346
|
-
await this.deps.sender.sendCard(message.chatId, await this.renderFormCard(next));
|
|
148347
|
-
}
|
|
148348
|
-
/**
|
|
148349
|
-
* `/cd <path>`:话题内移动当前会话目录。
|
|
148350
|
-
* 目录容错:留空 = 回到允许根目录;不存在则自动创建(仍在 allowedRoots 之下)。
|
|
148351
|
-
*/
|
|
148352
|
-
async cmdCd(message, args2) {
|
|
148353
|
-
if (!message.threadId) {
|
|
148354
|
-
await this.reply(message, "`/cd` \u53EA\u80FD\u5728\u8BDD\u9898\u5185\u4F7F\u7528\uFF08\u7528\u4E8E\u79FB\u52A8\u8BE5\u8BDD\u9898\u4F1A\u8BDD\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF09\u3002");
|
|
148355
|
-
return;
|
|
148356
|
-
}
|
|
148357
|
-
const sessionID = await this.threadSessionID(message);
|
|
148358
|
-
if (!sessionID) {
|
|
148359
|
-
await this.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\u3002\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u7528 `/new` \u65B0\u5EFA\u3002");
|
|
148360
|
-
return;
|
|
148361
|
-
}
|
|
148362
|
-
const validation = this.deps.validateDir(args2);
|
|
148363
|
-
if (!validation.ok) {
|
|
148364
|
-
await this.reply(message, validation.message);
|
|
148365
|
-
return;
|
|
148366
|
-
}
|
|
148367
|
-
await this.deps.moveSessionDir(sessionID, validation.path);
|
|
148368
|
-
await this.deps.recent.addDir(validation.path);
|
|
148369
|
-
await this.reply(message, `\u2705 \u5DF2\u5207\u6362\u672C\u4F1A\u8BDD\u76EE\u5F55\uFF1A\`${validation.path}\``);
|
|
148370
|
-
}
|
|
148371
|
-
/** `/cancel`:放弃向导。 */
|
|
148372
|
-
async cmdCancel(message) {
|
|
148373
|
-
if (message.threadId) {
|
|
148374
|
-
await this.reply(message, threadForbiddenText("cancel"));
|
|
148375
|
-
return;
|
|
148376
|
-
}
|
|
148377
|
-
await this.deps.wizard.cancel(message.chatId);
|
|
148378
|
-
await this.reply(message, "\u2716\uFE0F \u5DF2\u53D6\u6D88\u5EFA\u4F1A\u8BDD\u8868\u5355\u3002\u53D1\u9001 `/new [\u6807\u9898]` \u53EF\u91CD\u65B0\u5F00\u59CB\u3002");
|
|
148379
|
-
}
|
|
148380
|
-
// ── 既有会话管理 ──────────────────────────────────────────────────────
|
|
148381
|
-
/**
|
|
148382
|
-
* `/sessions`(别名 `/ls`):列出 opencode **全部**会话(按最近更新倒序、分页 8 条)。
|
|
148383
|
-
* 数据源优先 `ctx.session.list()`;拿不到时回退 SessionMap 映射表。
|
|
148384
|
-
*/
|
|
148385
|
-
async cmdSessions(message, page = 0) {
|
|
148386
|
-
const entries3 = await this.loadSessionEntries(message.chatId);
|
|
148387
|
-
const active = await this.deps.sessionMap.getActive(message.chatId);
|
|
148388
|
-
const card = await this.buildListCard(message.chatId, entries3, page, active?.sessionID);
|
|
148389
|
-
const res = await this.deps.sender.sendCard(message.chatId, card);
|
|
148390
|
-
if (!res.ok) {
|
|
148391
|
-
this.deps.log.warn("\u4F1A\u8BDD\u5361\u7247\u53D1\u9001\u5931\u8D25", { chatId: message.chatId, error: res.error ?? "unknown" });
|
|
149088
|
+
return this.ctx.reply(message, helpText(scope3));
|
|
148392
149089
|
}
|
|
148393
149090
|
}
|
|
148394
|
-
|
|
148395
|
-
|
|
148396
|
-
|
|
148397
|
-
|
|
148398
|
-
|
|
148399
|
-
|
|
148400
|
-
|
|
148401
|
-
|
|
148402
|
-
|
|
148403
|
-
|
|
148404
|
-
|
|
148405
|
-
|
|
148406
|
-
|
|
148407
|
-
|
|
148408
|
-
|
|
148409
|
-
|
|
148410
|
-
|
|
148411
|
-
|
|
148412
|
-
const query = args2.trim();
|
|
148413
|
-
let index2 = 0;
|
|
148414
|
-
if (query) {
|
|
148415
|
-
if (!/^\d+$/.test(query)) {
|
|
148416
|
-
await this.reply(message, "\u7528\u6CD5\uFF1A`/resume [\u5E8F\u53F7]`\uFF08\u5E8F\u53F7\u89C1 `/sessions`\uFF1B\u7701\u7565 = \u6700\u8FD1\u66F4\u65B0\u7684\u4F1A\u8BDD\uFF09\u3002");
|
|
148417
|
-
return;
|
|
148418
|
-
}
|
|
148419
|
-
index2 = Number.parseInt(query, 10) - 1;
|
|
148420
|
-
}
|
|
148421
|
-
if (index2 < 0 || index2 >= entries3.length) {
|
|
148422
|
-
await this.reply(
|
|
148423
|
-
message,
|
|
148424
|
-
`\u5E8F\u53F7\u8D8A\u754C\uFF1A\u5F53\u524D\u5171 ${entries3.length} \u4E2A\u4F1A\u8BDD\uFF0C\u6709\u6548\u8303\u56F4 1\u2013${entries3.length}\u3002`
|
|
148425
|
-
);
|
|
148426
|
-
return;
|
|
148427
|
-
}
|
|
148428
|
-
const entry = entries3[index2];
|
|
148429
|
-
const result3 = await this.enterSessionThread({
|
|
148430
|
-
chatId: message.chatId,
|
|
148431
|
-
sessionID: entry.sessionID,
|
|
148432
|
-
anchorMessageId: message.messageId,
|
|
148433
|
-
operatorOpenId: message.senderOpenId,
|
|
148434
|
-
info: entry,
|
|
148435
|
-
source: "resume"
|
|
148436
|
-
});
|
|
148437
|
-
if (!result3.ok) {
|
|
148438
|
-
await this.reply(
|
|
148439
|
-
message,
|
|
148440
|
-
`\u26A0\uFE0F \u8FDB\u5165\u8BDD\u9898\u5931\u8D25\uFF1A${result3.error ?? "unknown"}\u3002\u53EF\u53D1\u9001 \`/sessions\` \u624B\u52A8\u8FDB\u5165\u3002`
|
|
148441
|
-
);
|
|
148442
|
-
}
|
|
149091
|
+
};
|
|
149092
|
+
function setupToast(value3) {
|
|
149093
|
+
switch (value3.kind) {
|
|
149094
|
+
case "dir":
|
|
149095
|
+
return toast3("success", "\u5DF2\u9009\u62E9\u76EE\u5F55");
|
|
149096
|
+
case "model":
|
|
149097
|
+
return toast3("success", "\u5DF2\u9009\u62E9\u6A21\u578B");
|
|
149098
|
+
case "perm":
|
|
149099
|
+
return toast3("success", "\u5DF2\u9009\u62E9\u6743\u9650");
|
|
149100
|
+
case "more":
|
|
149101
|
+
return toast3("info", "\u5DF2\u7FFB\u9875");
|
|
149102
|
+
case "confirm":
|
|
149103
|
+
return toast3("success", "\u6B63\u5728\u521B\u5EFA\u4F1A\u8BDD\u2026");
|
|
149104
|
+
case "form":
|
|
149105
|
+
return toast3("info", "\u5DF2\u6253\u5F00\u8868\u5355");
|
|
149106
|
+
case "cancel":
|
|
149107
|
+
default:
|
|
149108
|
+
return toast3("info", "\u5DF2\u53D6\u6D88");
|
|
148443
149109
|
}
|
|
148444
|
-
|
|
148445
|
-
|
|
148446
|
-
|
|
148447
|
-
|
|
148448
|
-
|
|
148449
|
-
|
|
148450
|
-
|
|
148451
|
-
|
|
148452
|
-
|
|
148453
|
-
|
|
148454
|
-
|
|
148455
|
-
|
|
148456
|
-
|
|
148457
|
-
|
|
148458
|
-
|
|
148459
|
-
|
|
148460
|
-
|
|
148461
|
-
|
|
148462
|
-
|
|
148463
|
-
|
|
148464
|
-
|
|
148465
|
-
|
|
148466
|
-
|
|
148467
|
-
|
|
148468
|
-
|
|
148469
|
-
|
|
148470
|
-
|
|
148471
|
-
/** 把全量列表切成当前页并构建列表卡(含「已绑话题」标记)。 */
|
|
148472
|
-
async buildListCard(chatId, entries3, page, activeID) {
|
|
148473
|
-
const pageSize = this.sessionPageSize;
|
|
148474
|
-
const pageCount = Math.max(1, Math.ceil(entries3.length / pageSize));
|
|
148475
|
-
const safePage = Math.min(Math.max(0, Math.floor(page)), pageCount - 1);
|
|
148476
|
-
const slice = entries3.slice(safePage * pageSize, safePage * pageSize + pageSize);
|
|
148477
|
-
const rows = [];
|
|
148478
|
-
for (let i = 0; i < slice.length; i += 1) {
|
|
148479
|
-
const entry = slice[i];
|
|
148480
|
-
const bound = Boolean(await this.deps.sessionMap.threadIdForSession(entry.sessionID));
|
|
148481
|
-
rows.push({
|
|
148482
|
-
index: safePage * pageSize + i + 1,
|
|
148483
|
-
sessionID: entry.sessionID,
|
|
148484
|
-
title: entry.title,
|
|
148485
|
-
updatedAt: entry.updatedAt,
|
|
148486
|
-
...entry.directory ? { directory: entry.directory } : {},
|
|
148487
|
-
bound,
|
|
148488
|
-
...entry.sessionID === activeID ? { active: true } : {}
|
|
149110
|
+
}
|
|
149111
|
+
|
|
149112
|
+
// src/runtime/event-router.ts
|
|
149113
|
+
async function routeEvent(event, deps) {
|
|
149114
|
+
const touched = event.data?.sessionID;
|
|
149115
|
+
if (typeof touched === "string") deps.touch(touched);
|
|
149116
|
+
switch (event.type) {
|
|
149117
|
+
case "permission.asked":
|
|
149118
|
+
void deps.onPermissionAsked(event.data).catch((err2) => deps.log.warn("\u5904\u7406 permission.asked \u5931\u8D25", { error: errorMessage(err2) }));
|
|
149119
|
+
break;
|
|
149120
|
+
case "permission.replied":
|
|
149121
|
+
deps.onPermissionReplied(event.data);
|
|
149122
|
+
break;
|
|
149123
|
+
case "form.created":
|
|
149124
|
+
void deps.onFormCreated(event.data).catch((err2) => deps.log.warn("\u5904\u7406 form.created \u5931\u8D25", { error: errorMessage(err2) }));
|
|
149125
|
+
break;
|
|
149126
|
+
case "form.replied":
|
|
149127
|
+
deps.onFormReplied(event.data);
|
|
149128
|
+
break;
|
|
149129
|
+
case "form.cancelled":
|
|
149130
|
+
deps.onFormCancelled(event.data);
|
|
149131
|
+
break;
|
|
149132
|
+
case "session.text.started": {
|
|
149133
|
+
const data = event.data;
|
|
149134
|
+
deps.applyRun(data.sessionID, {
|
|
149135
|
+
type: "text.started",
|
|
149136
|
+
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
148489
149137
|
});
|
|
149138
|
+
break;
|
|
148490
149139
|
}
|
|
148491
|
-
|
|
148492
|
-
|
|
148493
|
-
|
|
148494
|
-
|
|
148495
|
-
|
|
148496
|
-
|
|
148497
|
-
|
|
148498
|
-
|
|
148499
|
-
}
|
|
148500
|
-
async cmdUse(message, args2) {
|
|
148501
|
-
const sessions = await this.deps.sessionMap.listSessions(message.chatId);
|
|
148502
|
-
if (sessions.length === 0) {
|
|
148503
|
-
await this.reply(message, "\u8FD8\u6CA1\u6709\u4F1A\u8BDD\u3002\u4F7F\u7528 /new \u65B0\u5EFA\u4E00\u4E2A\u3002");
|
|
148504
|
-
return;
|
|
148505
|
-
}
|
|
148506
|
-
const matched = matchSession(args2, sessions);
|
|
148507
|
-
if (!matched.ok) {
|
|
148508
|
-
await this.reply(message, useErrorText(matched.reason));
|
|
148509
|
-
return;
|
|
148510
|
-
}
|
|
148511
|
-
const entry = matched.entry;
|
|
148512
|
-
const ok = await this.deps.sessionMap.setActive(message.chatId, entry.sessionID);
|
|
148513
|
-
if (!ok) {
|
|
148514
|
-
await this.reply(message, "\u5207\u6362\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E0D\u5B58\u5728\uFF0C\u5148\u7528 /sessions \u67E5\u770B\u5217\u8868\u3002");
|
|
148515
|
-
return;
|
|
148516
|
-
}
|
|
148517
|
-
await this.reply(message, `\u2705 \u5DF2\u5207\u6362\u5230\u300C${entry.title.trim() || "(\u672A\u547D\u540D)"}\u300D
|
|
148518
|
-
\`${entry.sessionID}\``);
|
|
148519
|
-
}
|
|
148520
|
-
async cmdCurrent(message, scope3) {
|
|
148521
|
-
if (scope3 === "thread") {
|
|
148522
|
-
const sessionID = await this.threadSessionID(message);
|
|
148523
|
-
if (!sessionID) {
|
|
148524
|
-
await this.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\u3002\u8BF7\u56DE\u5230\u4E3B\u804A\u5929\u6D41\u7528 `/new` \u65B0\u5EFA\u3002");
|
|
148525
|
-
return;
|
|
148526
|
-
}
|
|
148527
|
-
const entry = await this.deps.sessionMap.getSession(message.chatId, sessionID);
|
|
148528
|
-
const link2 = await this.deps.sessionMap.resolveBySession(sessionID);
|
|
148529
|
-
const model = await this.currentModel(sessionID) ?? link2?.model;
|
|
148530
|
-
const lines = [
|
|
148531
|
-
`\u672C\u8BDD\u9898\u4F1A\u8BDD\uFF1A\u300C${entry?.title.trim() || "(\u672A\u547D\u540D)"}\u300D`,
|
|
148532
|
-
`\`${sessionID}\``,
|
|
148533
|
-
...link2?.dir ? [`\u76EE\u5F55\uFF1A\`${link2.dir}\``] : [],
|
|
148534
|
-
...model ? [`\u6A21\u578B\uFF1A${modelLabel(model)}`] : [],
|
|
148535
|
-
...link2?.perm ? [`\u6743\u9650\uFF1A${presetLabel(link2.perm)}`] : []
|
|
148536
|
-
];
|
|
148537
|
-
await this.reply(message, lines.join("\n"));
|
|
148538
|
-
return;
|
|
148539
|
-
}
|
|
148540
|
-
const active = await this.deps.sessionMap.getActive(message.chatId);
|
|
148541
|
-
if (!active) {
|
|
148542
|
-
await this.reply(message, "\u5F53\u524D\u6CA1\u6709\u4F1A\u8BDD\u3002\u4F7F\u7528 /new \u65B0\u5EFA\u4E00\u4E2A\u3002");
|
|
148543
|
-
return;
|
|
148544
|
-
}
|
|
148545
|
-
const count = (await this.deps.sessionMap.listSessions(message.chatId)).length;
|
|
148546
|
-
await this.reply(
|
|
148547
|
-
message,
|
|
148548
|
-
`\u5F53\u524D\u4F1A\u8BDD\uFF1A\u300C${active.title.trim() || "(\u672A\u547D\u540D)"}\u300D
|
|
148549
|
-
\`${active.sessionID}\`
|
|
148550
|
-
\u5171 ${count} \u4E2A\u4F1A\u8BDD\u3002`
|
|
148551
|
-
);
|
|
148552
|
-
}
|
|
148553
|
-
async cmdStop(message, scope3) {
|
|
148554
|
-
if (scope3 === "thread") {
|
|
148555
|
-
const sessionID = await this.threadSessionID(message);
|
|
148556
|
-
if (!sessionID) {
|
|
148557
|
-
await this.reply(message, "\u672C\u8BDD\u9898\u5C1A\u672A\u5173\u8054\u4F1A\u8BDD\uFF0C\u65E0\u6CD5\u4E2D\u65AD\u3002");
|
|
148558
|
-
return;
|
|
148559
|
-
}
|
|
148560
|
-
await this.deps.interruptSession(sessionID);
|
|
148561
|
-
const entry = await this.deps.sessionMap.getSession(message.chatId, sessionID);
|
|
148562
|
-
await this.reply(message, `\u23F9\uFE0F \u5DF2\u8BF7\u6C42\u4E2D\u65AD\u672C\u8BDD\u9898\u4F1A\u8BDD\uFF1A\u300C${entry?.title.trim() || "(\u672A\u547D\u540D)"}\u300D`);
|
|
148563
|
-
return;
|
|
148564
|
-
}
|
|
148565
|
-
const active = await this.deps.sessionMap.getActive(message.chatId);
|
|
148566
|
-
if (!active) {
|
|
148567
|
-
await this.reply(message, "\u5F53\u524D\u6CA1\u6709\u4F1A\u8BDD\u53EF\u4E2D\u65AD\u3002");
|
|
148568
|
-
return;
|
|
148569
|
-
}
|
|
148570
|
-
await this.deps.interruptSession(active.sessionID);
|
|
148571
|
-
await this.reply(message, `\u23F9\uFE0F \u5DF2\u8BF7\u6C42\u4E2D\u65AD\u5F53\u524D\u4F1A\u8BDD\uFF1A\u300C${active.title.trim() || "(\u672A\u547D\u540D)"}\u300D`);
|
|
148572
|
-
}
|
|
148573
|
-
/** 解析命令作用域内的目标会话:话题内用 thread 映射,主聊天流用当前会话。 */
|
|
148574
|
-
async scopeSessionID(message, scope3) {
|
|
148575
|
-
if (scope3 === "thread") return this.threadSessionID(message);
|
|
148576
|
-
const active = await this.deps.sessionMap.getActive(message.chatId);
|
|
148577
|
-
return active?.sessionID;
|
|
148578
|
-
}
|
|
148579
|
-
/** `/now`:把已排队(未投递)的消息提升为 steer,立即插队执行。 */
|
|
148580
|
-
async cmdNow(message, scope3) {
|
|
148581
|
-
const sessionID = await this.scopeSessionID(message, scope3);
|
|
148582
|
-
if (!sessionID) {
|
|
148583
|
-
await this.reply(message, "\u5F53\u524D\u6CA1\u6709\u4F1A\u8BDD\u3002");
|
|
148584
|
-
return;
|
|
148585
|
-
}
|
|
148586
|
-
if (!this.deps.promoteQueued) {
|
|
148587
|
-
await this.reply(message, "\u5F53\u524D\u7248\u672C\u4E0D\u652F\u6301\u63D2\u961F\uFF08\u8FD0\u884C\u65F6\u672A\u66B4\u9732 inbox\uFF09\u3002");
|
|
148588
|
-
return;
|
|
148589
|
-
}
|
|
148590
|
-
const promoted = await this.deps.promoteQueued(sessionID);
|
|
148591
|
-
if (promoted < 0) {
|
|
148592
|
-
await this.reply(message, "\u5F53\u524D\u7248\u672C\u4E0D\u652F\u6301\u63D2\u961F\uFF08\u8FD0\u884C\u65F6\u672A\u66B4\u9732 inbox\uFF09\u3002");
|
|
148593
|
-
return;
|
|
148594
|
-
}
|
|
148595
|
-
if (promoted === 0) {
|
|
148596
|
-
await this.reply(message, "\u6CA1\u6709\u6392\u961F\u4E2D\u7684\u6D88\u606F\uFF08\u8BE5\u4F1A\u8BDD\u5F53\u524D\u7A7A\u95F2\u6216\u65E0\u5F85\u6295\u9012\u9879\uFF09\u3002");
|
|
148597
|
-
return;
|
|
148598
|
-
}
|
|
148599
|
-
await this.reply(message, `\u26A1 \u5DF2\u628A ${promoted} \u6761\u6392\u961F\u6D88\u606F\u6539\u4E3A\u7ACB\u5373\u63D2\u961F\u6267\u884C\u3002`);
|
|
148600
|
-
}
|
|
148601
|
-
/** `/steer <文本>`:立即插队发送一条消息(打断当前步骤插入执行)。 */
|
|
148602
|
-
async cmdSteer(message, args2, scope3) {
|
|
148603
|
-
const sessionID = await this.scopeSessionID(message, scope3);
|
|
148604
|
-
if (!sessionID) {
|
|
148605
|
-
await this.reply(message, "\u5F53\u524D\u6CA1\u6709\u4F1A\u8BDD\u3002");
|
|
148606
|
-
return;
|
|
148607
|
-
}
|
|
148608
|
-
const text = args2.trim();
|
|
148609
|
-
if (!text) {
|
|
148610
|
-
await this.reply(message, "\u7528\u6CD5\uFF1A`/steer <\u6587\u672C>`\uFF08\u7ACB\u5373\u63D2\u961F\u53D1\u9001\uFF09\uFF1B\u628A\u5DF2\u6392\u961F\u6D88\u606F\u63D2\u961F\u8BF7\u7528 `/now`\u3002");
|
|
148611
|
-
return;
|
|
148612
|
-
}
|
|
148613
|
-
if (!this.deps.steerPrompt) {
|
|
148614
|
-
await this.reply(message, "\u5F53\u524D\u7248\u672C\u4E0D\u652F\u6301\u63D2\u961F\u3002");
|
|
148615
|
-
return;
|
|
148616
|
-
}
|
|
148617
|
-
await this.deps.steerPrompt(message, sessionID, text);
|
|
148618
|
-
}
|
|
148619
|
-
// ── 会话列表卡片 ──────────────────────────────────────────────────────
|
|
148620
|
-
/**
|
|
148621
|
-
* 会话卡按钮(`use` / `new` / `list`;`open` 由 `handleOpenCardAction` 处理)。
|
|
148622
|
-
* - `new`:发送 `/form` 建会话表单卡(不再直接建会话);
|
|
148623
|
-
* - `list`:按页码 patch 当前列表卡;
|
|
148624
|
-
* - `use`:旧卡片兼容,切换当前会话并刷新列表(含翻页位置)。
|
|
148625
|
-
*/
|
|
148626
|
-
async applySessionCardAction(action, value3) {
|
|
148627
|
-
const chatId = action.chatId || value3.chatId;
|
|
148628
|
-
if (!chatId) return;
|
|
148629
|
-
if (value3.cmd === "new") {
|
|
148630
|
-
await this.sendSetupFormForChat(chatId, action.messageId, action.operatorOpenId);
|
|
148631
|
-
return;
|
|
148632
|
-
}
|
|
148633
|
-
if (value3.cmd === "list") {
|
|
148634
|
-
if (action.messageId) await this.patchListCard(chatId, action.messageId, value3.page);
|
|
148635
|
-
return;
|
|
148636
|
-
}
|
|
148637
|
-
if (value3.cmd === "open") {
|
|
148638
|
-
return;
|
|
148639
|
-
}
|
|
148640
|
-
const ok = await this.deps.sessionMap.setActive(chatId, value3.sessionID);
|
|
148641
|
-
if (!ok) this.deps.log.debug("\u5361\u7247\u5207\u6362\u4F1A\u8BDD\u5931\u8D25\uFF1A\u4F1A\u8BDD\u4E0D\u5B58\u5728", { sessionID: value3.sessionID });
|
|
148642
|
-
if (action.messageId) await this.patchListCard(chatId, action.messageId);
|
|
148643
|
-
}
|
|
148644
|
-
/** 列表卡「➕ 新建会话」→ 打开 `/form` 建会话表单卡(复用向导状态/标题)。 */
|
|
148645
|
-
async sendSetupFormForChat(chatId, anchorMessageId, openId) {
|
|
148646
|
-
if (!this.threadRouting) {
|
|
148647
|
-
const title = defaultSessionTitle(this.now());
|
|
148648
|
-
await this.deps.createSession({ title, chatId, openId });
|
|
148649
|
-
await this.replyChat(chatId, `\u2705 \u5DF2\u65B0\u5EFA\u4F1A\u8BDD\u300C${title}\u300D`);
|
|
148650
|
-
return;
|
|
148651
|
-
}
|
|
148652
|
-
let state = await this.deps.wizard.get(chatId);
|
|
148653
|
-
if (!state) state = await this.deps.wizard.start(chatId, void 0, anchorMessageId);
|
|
148654
|
-
const card = await this.renderFormCard(state);
|
|
148655
|
-
const res = await this.deps.sender.sendCard(chatId, card);
|
|
148656
|
-
if (!res.ok) this.deps.log.warn("\u8868\u5355\u5361\u53D1\u9001\u5931\u8D25", { chatId, error: res.error ?? "unknown" });
|
|
148657
|
-
}
|
|
148658
|
-
async patchListCard(chatId, messageId, page = 0) {
|
|
148659
|
-
const entries3 = await this.loadSessionEntries(chatId);
|
|
148660
|
-
const active = await this.deps.sessionMap.getActive(chatId);
|
|
148661
|
-
const card = await this.buildListCard(chatId, entries3, page, active?.sessionID);
|
|
148662
|
-
const res = await this.deps.sender.patchCard(messageId, card);
|
|
148663
|
-
if (!res.ok) this.deps.log.warn("\u4F1A\u8BDD\u5361\u7247\u66F4\u65B0\u5931\u8D25", { error: res.error ?? "unknown" });
|
|
148664
|
-
}
|
|
148665
|
-
/** 主聊天流文本回执(不引用任何消息)。 */
|
|
148666
|
-
async replyChat(chatId, text) {
|
|
148667
|
-
await this.deps.sender.sendText(chatId, text);
|
|
148668
|
-
}
|
|
148669
|
-
// ── 向导 / 会话操作卡片 ───────────────────────────────────────────────
|
|
148670
|
-
async applySetupCardAction(action, value3) {
|
|
148671
|
-
const chatId = action.chatId;
|
|
148672
|
-
if (value3.kind === "confirm") {
|
|
148673
|
-
await this.confirmSetup(chatId, action);
|
|
148674
|
-
return;
|
|
148675
|
-
}
|
|
148676
|
-
if (value3.kind === "cancel") {
|
|
148677
|
-
await this.deps.wizard.cancel(chatId);
|
|
148678
|
-
await this.patchCard(action.messageId, buildSetupDoneCard("\u2716\uFE0F \u5DF2\u53D6\u6D88", ["\u5EFA\u4F1A\u8BDD\u5411\u5BFC\u5DF2\u53D6\u6D88\u3002\u53D1\u9001 `/new` \u91CD\u65B0\u5F00\u59CB\u3002"]));
|
|
148679
|
-
return;
|
|
148680
|
-
}
|
|
148681
|
-
if (value3.kind === "form") {
|
|
148682
|
-
const state2 = await this.deps.wizard.get(chatId) ?? await this.deps.wizard.start(chatId, void 0, action.messageId);
|
|
148683
|
-
await this.patchCard(action.messageId, await this.renderFormCard(state2));
|
|
148684
|
-
return;
|
|
148685
|
-
}
|
|
148686
|
-
if (value3.kind !== "dir" && value3.sid) {
|
|
148687
|
-
if (value3.kind === "model") {
|
|
148688
|
-
try {
|
|
148689
|
-
const outcome = await this.deps.switchSessionModel(value3.sid, value3.model);
|
|
148690
|
-
await this.deps.recent.addModel(value3.model);
|
|
148691
|
-
await this.patchCard(action.messageId, modelSwitchDoneCard(outcome, value3.model));
|
|
148692
|
-
} catch (err2) {
|
|
148693
|
-
this.deps.log.warn("\u5361\u7247\u5207\u6362\u6A21\u578B\u5931\u8D25", { sessionID: value3.sid, error: errorMessage(err2) });
|
|
148694
|
-
await this.patchCard(
|
|
148695
|
-
action.messageId,
|
|
148696
|
-
buildSetupDoneCard("\u26A0\uFE0F \u5207\u6362\u6A21\u578B\u5931\u8D25", [errorMessage(err2)])
|
|
148697
|
-
);
|
|
148698
|
-
}
|
|
148699
|
-
return;
|
|
148700
|
-
}
|
|
148701
|
-
if (value3.kind === "perm") {
|
|
148702
|
-
await this.deps.applyPermissionPreset(value3.sid, value3.preset);
|
|
148703
|
-
await this.patchCard(
|
|
148704
|
-
action.messageId,
|
|
148705
|
-
buildSetupDoneCard("\u2705 \u5DF2\u66F4\u65B0\u6743\u9650", [`\u5F53\u524D\u6743\u9650\uFF1A${presetLabel(value3.preset)}`])
|
|
148706
|
-
);
|
|
148707
|
-
return;
|
|
148708
|
-
}
|
|
148709
|
-
if (value3.kind === "more") {
|
|
148710
|
-
const current = await this.currentModel(value3.sid);
|
|
148711
|
-
const models = await this.loadModels();
|
|
148712
|
-
const recent = await this.deps.recent.listModels();
|
|
148713
|
-
const card = buildModelCard({
|
|
148714
|
-
models,
|
|
148715
|
-
recent,
|
|
148716
|
-
...current ? { current } : {},
|
|
148717
|
-
page: value3.page,
|
|
148718
|
-
pageSize: this.modelPageSize,
|
|
148719
|
-
recentLimit: this.recentModelsLimit(),
|
|
148720
|
-
sid: value3.sid
|
|
148721
|
-
});
|
|
148722
|
-
await this.patchCard(action.messageId, card);
|
|
148723
|
-
return;
|
|
148724
|
-
}
|
|
148725
|
-
return;
|
|
148726
|
-
}
|
|
148727
|
-
const state = await this.deps.wizard.get(chatId);
|
|
148728
|
-
if (!state) {
|
|
148729
|
-
await this.patchCard(action.messageId, buildSetupDoneCard("\u26A0\uFE0F \u5411\u5BFC\u5DF2\u5931\u6548", ["\u8BF7\u91CD\u65B0\u53D1\u9001 `/new` \u5F00\u59CB\u3002"]));
|
|
148730
|
-
return;
|
|
148731
|
-
}
|
|
148732
|
-
if (value3.kind === "dir") {
|
|
148733
|
-
const next = await this.deps.wizard.apply(chatId, { type: "setDir", dir: value3.dir });
|
|
148734
|
-
await this.deps.recent.addDir(value3.dir);
|
|
148735
|
-
if (!next) return;
|
|
148736
|
-
await this.patchCard(action.messageId, await this.renderModelCard(next));
|
|
148737
|
-
return;
|
|
148738
|
-
}
|
|
148739
|
-
if (value3.kind === "model") {
|
|
148740
|
-
const next = await this.deps.wizard.apply(chatId, { type: "setModel", model: value3.model });
|
|
148741
|
-
await this.deps.recent.addModel(value3.model);
|
|
148742
|
-
if (!next) return;
|
|
148743
|
-
await this.patchCard(action.messageId, buildPermCard({ ...next.perm ? { current: next.perm } : {} }));
|
|
148744
|
-
return;
|
|
149140
|
+
case "session.text.delta": {
|
|
149141
|
+
const data = event.data;
|
|
149142
|
+
deps.applyRun(data.sessionID, {
|
|
149143
|
+
type: "text.delta",
|
|
149144
|
+
delta: data.delta,
|
|
149145
|
+
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
149146
|
+
});
|
|
149147
|
+
break;
|
|
148745
149148
|
}
|
|
148746
|
-
|
|
148747
|
-
const
|
|
148748
|
-
|
|
148749
|
-
|
|
148750
|
-
|
|
149149
|
+
case "session.text.ended": {
|
|
149150
|
+
const data = event.data;
|
|
149151
|
+
deps.applyRun(data.sessionID, {
|
|
149152
|
+
type: "text.ended",
|
|
149153
|
+
...data.text ? { text: data.text } : {},
|
|
149154
|
+
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
149155
|
+
});
|
|
149156
|
+
break;
|
|
148751
149157
|
}
|
|
148752
|
-
|
|
148753
|
-
const
|
|
148754
|
-
|
|
148755
|
-
|
|
149158
|
+
case "session.tool.input.started": {
|
|
149159
|
+
const data = event.data;
|
|
149160
|
+
deps.applyRun(data.sessionID, {
|
|
149161
|
+
type: "tool.input.started",
|
|
149162
|
+
id: data.id,
|
|
149163
|
+
name: data.name,
|
|
149164
|
+
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
149165
|
+
});
|
|
149166
|
+
break;
|
|
148756
149167
|
}
|
|
148757
|
-
|
|
148758
|
-
|
|
148759
|
-
|
|
148760
|
-
|
|
148761
|
-
* @deprecated `/new` 已不再发确认卡;仅当用户点击**历史遗留**的确认卡时才会走到这里。
|
|
148762
|
-
* 新流程见 `applySetupFormSubmit`(表单提交)。
|
|
148763
|
-
*/
|
|
148764
|
-
async confirmSetup(chatId, action) {
|
|
148765
|
-
const state = await this.deps.wizard.get(chatId);
|
|
148766
|
-
if (!state || state.step !== "confirm" || !state.dir || !state.perm) {
|
|
148767
|
-
await this.patchCard(action.messageId, buildSetupDoneCard("\u26A0\uFE0F \u5411\u5BFC\u72B6\u6001\u4E0D\u5B8C\u6574", ["\u8BF7\u91CD\u65B0\u53D1\u9001 `/new` \u5F00\u59CB\u3002"]));
|
|
148768
|
-
return;
|
|
149168
|
+
case "session.tool.input.ended": {
|
|
149169
|
+
const data = event.data;
|
|
149170
|
+
deps.applyRun(data.sessionID, { type: "tool.input.ended", id: data.id, input: data.input });
|
|
149171
|
+
break;
|
|
148769
149172
|
}
|
|
148770
|
-
|
|
148771
|
-
|
|
148772
|
-
|
|
148773
|
-
|
|
148774
|
-
|
|
148775
|
-
|
|
148776
|
-
|
|
148777
|
-
|
|
148778
|
-
});
|
|
148779
|
-
}
|
|
148780
|
-
/**
|
|
148781
|
-
* 表单提交(P6.1):与按钮向导**共用创建路径**。
|
|
148782
|
-
* 目录先用 `validateDir` 校验(失败 → 回带错误说明的表单卡并保留已填项,不建会话)。
|
|
148783
|
-
*/
|
|
148784
|
-
async applySetupFormSubmit(action) {
|
|
148785
|
-
const fv = action.formValue;
|
|
148786
|
-
const fvKeys = fv && typeof fv === "object" ? Object.keys(fv) : [];
|
|
148787
|
-
this.deps.log.info("\u8868\u5355\u63D0\u4EA4\u8FDB\u5165\u5904\u7406", {
|
|
148788
|
-
chatId: action.chatId,
|
|
148789
|
-
messageId: action.messageId,
|
|
148790
|
-
formValueKeys: fvKeys,
|
|
148791
|
-
formValueType: Array.isArray(fv) ? "array" : typeof fv
|
|
148792
|
-
});
|
|
148793
|
-
const values2 = parseSetupFormValues(action.formValue);
|
|
148794
|
-
if (!values2) {
|
|
148795
|
-
this.deps.log.warn("\u8868\u5355\u6570\u636E\u7F3A\u5931\uFF08parse \u8FD4\u56DE undefined\uFF09", { formValueKeys: fvKeys });
|
|
148796
|
-
await this.patchCard(action.messageId, buildSetupDoneCard("\u26A0\uFE0F \u8868\u5355\u6570\u636E\u7F3A\u5931", ["\u8BF7\u91CD\u65B0\u53D1\u9001 `/form` \u586B\u5199\u3002"]));
|
|
148797
|
-
return;
|
|
149173
|
+
case "session.tool.success": {
|
|
149174
|
+
const data = event.data;
|
|
149175
|
+
deps.applyRun(data.sessionID, {
|
|
149176
|
+
type: "tool.success",
|
|
149177
|
+
id: data.id,
|
|
149178
|
+
output: contentToText(data.content)
|
|
149179
|
+
});
|
|
149180
|
+
break;
|
|
148798
149181
|
}
|
|
148799
|
-
|
|
148800
|
-
|
|
148801
|
-
|
|
148802
|
-
|
|
148803
|
-
|
|
148804
|
-
|
|
148805
|
-
hasPerm: Boolean(values2.perm)
|
|
149182
|
+
case "session.tool.error": {
|
|
149183
|
+
const data = event.data;
|
|
149184
|
+
deps.applyRun(data.sessionID, {
|
|
149185
|
+
type: "tool.error",
|
|
149186
|
+
id: data.id,
|
|
149187
|
+
output: extractErrorText(data.error ?? data.content)
|
|
148806
149188
|
});
|
|
148807
|
-
|
|
148808
|
-
return;
|
|
149189
|
+
break;
|
|
148809
149190
|
}
|
|
148810
|
-
|
|
148811
|
-
|
|
148812
|
-
|
|
148813
|
-
|
|
148814
|
-
|
|
148815
|
-
...values2.perm ? { perm: values2.perm } : {}
|
|
148816
|
-
};
|
|
148817
|
-
this.deps.log.info("\u8868\u5355\u5B57\u6BB5\u89E3\u6790", {
|
|
148818
|
-
dirInput: values2.dir,
|
|
148819
|
-
dirSelect: values2.dirSelect,
|
|
148820
|
-
dir: requestedDir,
|
|
148821
|
-
model: values2.model ? `${values2.model.providerID}/${values2.model.id}` : void 0,
|
|
148822
|
-
perm: values2.perm,
|
|
148823
|
-
hasState: true
|
|
148824
|
-
});
|
|
148825
|
-
const validation = this.deps.validateDir(requestedDir);
|
|
148826
|
-
if (!validation.ok) {
|
|
148827
|
-
this.deps.log.warn("\u76EE\u5F55\u6821\u9A8C\u5931\u8D25", { dir: requestedDir, reason: validation.message });
|
|
148828
|
-
await this.patchCard(
|
|
148829
|
-
action.messageId,
|
|
148830
|
-
await this.renderFormCard(state, { error: validation.message, values: preserved })
|
|
148831
|
-
);
|
|
148832
|
-
return;
|
|
149191
|
+
case "session.execution.started": {
|
|
149192
|
+
const data = event.data;
|
|
149193
|
+
deps.markStarted(data.sessionID);
|
|
149194
|
+
deps.applyRun(data.sessionID, { type: "execution.started" });
|
|
149195
|
+
break;
|
|
148833
149196
|
}
|
|
148834
|
-
|
|
148835
|
-
|
|
148836
|
-
|
|
148837
|
-
|
|
148838
|
-
|
|
148839
|
-
await this.renderFormCard(state, { error: "\u8BF7\u9009\u62E9\u6743\u9650\u6863\u4F4D\u3002", values: { ...preserved, dir: validation.path } })
|
|
148840
|
-
);
|
|
148841
|
-
return;
|
|
149197
|
+
case "session.execution.succeeded": {
|
|
149198
|
+
const data = event.data;
|
|
149199
|
+
deps.markEnded(data.sessionID);
|
|
149200
|
+
deps.applyRun(data.sessionID, { type: "execution.succeeded" });
|
|
149201
|
+
break;
|
|
148842
149202
|
}
|
|
148843
|
-
|
|
148844
|
-
|
|
148845
|
-
|
|
148846
|
-
|
|
148847
|
-
|
|
148848
|
-
|
|
148849
|
-
await this.deps.wizard.cancel(action.chatId);
|
|
148850
|
-
await this.createSessionFromSetup(action.chatId, action, {
|
|
148851
|
-
title,
|
|
148852
|
-
dir: validation.path,
|
|
148853
|
-
perm: values2.perm,
|
|
148854
|
-
...model ? { model } : {}
|
|
148855
|
-
});
|
|
148856
|
-
}
|
|
148857
|
-
/** 表单模型引用 → 尽量补全 name(列表不可用时保留原引用)。 */
|
|
148858
|
-
async resolveFormModel(ref) {
|
|
148859
|
-
const models = await this.loadModels();
|
|
148860
|
-
const matched = matchModel(`${ref.providerID}/${ref.id}`, models);
|
|
148861
|
-
return matched.ok ? matched.model : ref;
|
|
148862
|
-
}
|
|
148863
|
-
/** 建会话统一创建路径(按钮确认 / 表单提交共用)。 */
|
|
148864
|
-
async createSessionFromSetup(chatId, action, opts) {
|
|
148865
|
-
const { title, dir, perm, model } = opts;
|
|
148866
|
-
const permissions = presetToRuleset(perm);
|
|
148867
|
-
const gateMode = presetGateMode(perm);
|
|
148868
|
-
const created = await this.deps.createSession({
|
|
148869
|
-
title,
|
|
148870
|
-
chatId,
|
|
148871
|
-
openId: action.operatorOpenId,
|
|
148872
|
-
setActive: true,
|
|
148873
|
-
directory: dir,
|
|
148874
|
-
permissions,
|
|
148875
|
-
perm,
|
|
148876
|
-
gateMode,
|
|
148877
|
-
...model ? { model } : {}
|
|
148878
|
-
});
|
|
148879
|
-
await this.deps.recent.addDir(dir);
|
|
148880
|
-
if (model) await this.deps.recent.addModel(model);
|
|
148881
|
-
const readyCard = buildSessionReadyCard({
|
|
148882
|
-
title,
|
|
148883
|
-
sessionID: created.id,
|
|
148884
|
-
dir,
|
|
148885
|
-
...model ? { model: modelLabel(model) } : {},
|
|
148886
|
-
perm: presetLabel(perm)
|
|
148887
|
-
});
|
|
148888
|
-
const anchorId = action.messageId || opts.anchorMessageId || "";
|
|
148889
|
-
const res = await this.deps.sender.replyCard(anchorId, readyCard, { replyInThread: true });
|
|
148890
|
-
this.deps.log.info("\u521B\u5EFA\u4F1A\u8BDD\u5E76\u5F00\u8BDD\u9898", {
|
|
148891
|
-
sessionID: created.id,
|
|
148892
|
-
anchorMessageId: anchorId,
|
|
148893
|
-
anchorFromFormCard: Boolean(action.messageId),
|
|
148894
|
-
replyOk: res.ok,
|
|
148895
|
-
replyMessageId: res.messageId,
|
|
148896
|
-
replyThreadId: res.threadId,
|
|
148897
|
-
replyError: res.error
|
|
148898
|
-
});
|
|
148899
|
-
if (!res.ok || !res.messageId) {
|
|
148900
|
-
this.deps.log.warn("\u4E00\u952E\u5F00\u8BDD\u9898\u5931\u8D25", { error: res.error ?? "unknown" });
|
|
148901
|
-
await this.patchCard(
|
|
148902
|
-
action.messageId,
|
|
148903
|
-
buildSessionCreatedCard({
|
|
148904
|
-
title,
|
|
148905
|
-
sessionID: created.id,
|
|
148906
|
-
dir,
|
|
148907
|
-
...model ? { model: modelLabel(model) } : {},
|
|
148908
|
-
perm: presetLabel(perm),
|
|
148909
|
-
note: "\u26A0\uFE0F \u81EA\u52A8\u5F00\u8BDD\u9898\u5931\u8D25\uFF1A\u8BF7\u5728 `/sessions` \u7684\u4F1A\u8BDD\u5361\u4E0A\u624B\u52A8\u300C\u521B\u5EFA\u8BDD\u9898\u300D\uFF0C\u6216\u5728\u4E3B\u804A\u5929\u6D41\u7528 `/use` \u5207\u6362\u540E\u7EE7\u7EED\u3002"
|
|
148910
|
-
})
|
|
148911
|
-
);
|
|
148912
|
-
return;
|
|
149203
|
+
case "session.execution.failed": {
|
|
149204
|
+
const data = event.data;
|
|
149205
|
+
deps.markEnded(data.sessionID);
|
|
149206
|
+
deps.applyRun(data.sessionID, { type: "execution.failed", error: extractErrorText(data.error) });
|
|
149207
|
+
void deps.notifyFailure(data.sessionID, data.error);
|
|
149208
|
+
break;
|
|
148913
149209
|
}
|
|
148914
|
-
|
|
148915
|
-
|
|
148916
|
-
|
|
148917
|
-
|
|
148918
|
-
|
|
148919
|
-
} else {
|
|
148920
|
-
this.deps.log.warn("\u4E00\u952E\u5F00\u8BDD\u9898\u540E\u672A\u8BFB\u5230 thread_id\uFF0C\u8BE5\u4F1A\u8BDD\u6682\u65E0\u6CD5\u81EA\u52A8\u8DEF\u7531", { messageId: res.messageId });
|
|
149210
|
+
case "session.execution.interrupted": {
|
|
149211
|
+
const data = event.data;
|
|
149212
|
+
deps.markEnded(data.sessionID);
|
|
149213
|
+
deps.applyRun(data.sessionID, { type: "execution.failed", error: `\u5DF2\u4E2D\u65AD\uFF08${data.reason ?? "unknown"}\uFF09` });
|
|
149214
|
+
break;
|
|
148921
149215
|
}
|
|
148922
|
-
|
|
148923
|
-
|
|
148924
|
-
|
|
148925
|
-
|
|
148926
|
-
sessionID
|
|
148927
|
-
|
|
148928
|
-
|
|
148929
|
-
|
|
148930
|
-
...threadId ? {} : { note: "\uFF08\u672A\u62FF\u5230\u8BDD\u9898 ID\uFF0C\u82E5\u8BDD\u9898\u672A\u51FA\u73B0\u8BF7\u5728\u4F1A\u8BDD\u5361\u4E0A\u624B\u52A8\u521B\u5EFA\u3002\uFF09" }
|
|
148931
|
-
})
|
|
148932
|
-
);
|
|
148933
|
-
}
|
|
148934
|
-
// ── 卡片渲染辅助 ──────────────────────────────────────────────────────
|
|
148935
|
-
// 说明:`/new` 已改为直接发建会话表单卡,以下分步卡的发送方法已不再使用(已移除)。
|
|
148936
|
-
// 分步卡的**构建函数**(buildDirCard/buildModelCard/buildPermCard/buildConfirmCard)与
|
|
148937
|
-
// 对应卡片回调分支仍保留,用于兼容旧卡片与单测(标注 @deprecated)。
|
|
148938
|
-
async renderModelCard(state, pageOverride) {
|
|
148939
|
-
const models = await this.loadModels();
|
|
148940
|
-
const recent = await this.deps.recent.listModels();
|
|
148941
|
-
return buildModelCard({
|
|
148942
|
-
models,
|
|
148943
|
-
recent,
|
|
148944
|
-
...state.model ? { current: state.model } : {},
|
|
148945
|
-
page: pageOverride ?? state.page ?? 0,
|
|
148946
|
-
pageSize: this.modelPageSize,
|
|
148947
|
-
recentLimit: this.recentModelsLimit()
|
|
148948
|
-
});
|
|
148949
|
-
}
|
|
148950
|
-
/**
|
|
148951
|
-
* 渲染建会话表单卡(P6.1):最近模型 + 常用模型 + 默认预选。
|
|
148952
|
-
* P6.2:目录/权限也从向导状态预填(`/dir` `/perm` 的能力,不再是必经步骤)。
|
|
148953
|
-
*/
|
|
148954
|
-
async renderFormCard(state, over) {
|
|
148955
|
-
const models = await this.loadModels();
|
|
148956
|
-
const recent = await this.deps.recent.listModels();
|
|
148957
|
-
const rootSubdirs = await this.scanRoot(this.deps.allowedRoots?.[0]);
|
|
148958
|
-
const values2 = over?.values ?? {
|
|
148959
|
-
...state?.dir ? { dir: state.dir } : {},
|
|
148960
|
-
...state?.perm ? { perm: state.perm } : {}
|
|
148961
|
-
};
|
|
148962
|
-
return buildSetupFormCard({
|
|
148963
|
-
models,
|
|
148964
|
-
recent,
|
|
148965
|
-
...state?.model ? { defaultModel: state.model } : {},
|
|
148966
|
-
...rootSubdirs.length > 0 ? { rootSubdirs } : {},
|
|
148967
|
-
...this.deps.allowedRoots ? { allowedRoots: this.deps.allowedRoots } : {},
|
|
148968
|
-
...over?.error ? { error: over.error } : {},
|
|
148969
|
-
values: values2
|
|
148970
|
-
});
|
|
148971
|
-
}
|
|
148972
|
-
async sendModelCardToThread(message, sessionID, page) {
|
|
148973
|
-
const current = await this.currentModel(sessionID);
|
|
148974
|
-
const models = await this.loadModels();
|
|
148975
|
-
const recent = await this.deps.recent.listModels();
|
|
148976
|
-
const card = buildModelCard({
|
|
148977
|
-
models,
|
|
148978
|
-
recent,
|
|
148979
|
-
...current ? { current } : {},
|
|
148980
|
-
page,
|
|
148981
|
-
pageSize: this.modelPageSize,
|
|
148982
|
-
recentLimit: this.recentModelsLimit(),
|
|
148983
|
-
sid: sessionID
|
|
148984
|
-
});
|
|
148985
|
-
await this.deps.sender.replyCard(message.messageId, card);
|
|
148986
|
-
}
|
|
148987
|
-
/**
|
|
148988
|
-
* 读回会话**真实**当前模型(优先 `ctx.session.get`),失败则降级到插件记录值。
|
|
148989
|
-
* 展示(运行卡页脚 / `/current` / 模型卡)一律以读回值为准,避免只显示我们记录的那份。
|
|
148990
|
-
*/
|
|
148991
|
-
async currentModel(sessionID) {
|
|
148992
|
-
const link2 = await this.deps.sessionMap.resolveBySession(sessionID);
|
|
148993
|
-
if (this.deps.getSessionModel) {
|
|
148994
|
-
try {
|
|
148995
|
-
const model = await this.deps.getSessionModel(sessionID, link2?.dir);
|
|
148996
|
-
if (model) return model;
|
|
148997
|
-
} catch (err2) {
|
|
148998
|
-
this.deps.log.warn("\u8BFB\u56DE\u4F1A\u8BDD\u6A21\u578B\u5931\u8D25\uFF0C\u56DE\u9000\u8BB0\u5F55\u503C", { sessionID, error: errorMessage(err2) });
|
|
149216
|
+
case "session.status": {
|
|
149217
|
+
const data = event.data;
|
|
149218
|
+
const statusType = data.status?.type;
|
|
149219
|
+
if (statusType === "idle") {
|
|
149220
|
+
deps.markEnded(data.sessionID);
|
|
149221
|
+
deps.applyRun(data.sessionID, { type: "execution.succeeded" });
|
|
149222
|
+
} else if (statusType === "busy" || statusType === "retry") {
|
|
149223
|
+
deps.markStarted(data.sessionID);
|
|
148999
149224
|
}
|
|
149225
|
+
break;
|
|
149000
149226
|
}
|
|
149001
|
-
|
|
149002
|
-
|
|
149003
|
-
|
|
149004
|
-
|
|
149005
|
-
|
|
149006
|
-
await this.deps.sender.replyCard(message.messageId, card);
|
|
149007
|
-
}
|
|
149008
|
-
async patchCard(messageId, card) {
|
|
149009
|
-
if (!messageId) return;
|
|
149010
|
-
const res = await this.deps.sender.patchCard(messageId, card);
|
|
149011
|
-
if (!res.ok) this.deps.log.warn("\u5411\u5BFC\u5361\u7247\u66F4\u65B0\u5931\u8D25", { error: res.error ?? "unknown" });
|
|
149012
|
-
}
|
|
149013
|
-
async loadModels() {
|
|
149014
|
-
try {
|
|
149015
|
-
return [...await this.deps.listModels()];
|
|
149016
|
-
} catch (err2) {
|
|
149017
|
-
this.deps.log.warn("\u6A21\u578B\u5217\u8868\u83B7\u53D6\u5931\u8D25", { error: errorMessage(err2) });
|
|
149018
|
-
return [];
|
|
149019
|
-
}
|
|
149020
|
-
}
|
|
149021
|
-
recentModelsLimit() {
|
|
149022
|
-
return this.deps.recentModelsLimit ?? 5;
|
|
149023
|
-
}
|
|
149024
|
-
/**
|
|
149025
|
-
* 扫描允许根目录的一级子目录(表单目录下拉选项来源)。
|
|
149026
|
-
* 任何失败静默降级为空列表(只保留「手动输入」与根目录两项),绝不抛异常。
|
|
149027
|
-
*/
|
|
149028
|
-
async scanRoot(root) {
|
|
149029
|
-
if (!root) return [];
|
|
149030
|
-
const scan = this.deps.scanRootSubdirs ?? scanRootSubdirs;
|
|
149031
|
-
try {
|
|
149032
|
-
return [...await scan(root)];
|
|
149033
|
-
} catch (err2) {
|
|
149034
|
-
this.deps.log.warn("\u6839\u76EE\u5F55\u5B50\u76EE\u5F55\u626B\u63CF\u5931\u8D25", { error: errorMessage(err2) });
|
|
149035
|
-
return [];
|
|
149036
|
-
}
|
|
149037
|
-
}
|
|
149038
|
-
async threadSessionID(message) {
|
|
149039
|
-
if (!message.threadId) return void 0;
|
|
149040
|
-
const link2 = await this.deps.sessionMap.resolveByThread(message.threadId);
|
|
149041
|
-
return link2?.sessionID;
|
|
149042
|
-
}
|
|
149043
|
-
/** 文本回执:话题内引用触发消息(留在话题),主聊天流直接发送。 */
|
|
149044
|
-
async reply(message, text) {
|
|
149045
|
-
if (message.threadId) {
|
|
149046
|
-
await this.deps.sender.replyText(message.messageId, text);
|
|
149047
|
-
return;
|
|
149227
|
+
case "session.idle": {
|
|
149228
|
+
const data = event.data;
|
|
149229
|
+
deps.markEnded(data.sessionID);
|
|
149230
|
+
deps.applyRun(data.sessionID, { type: "execution.succeeded" });
|
|
149231
|
+
break;
|
|
149048
149232
|
}
|
|
149049
|
-
await this.deps.sender.sendText(message.chatId, text);
|
|
149050
149233
|
}
|
|
149051
|
-
};
|
|
149052
|
-
function confirmInput(state) {
|
|
149053
|
-
return {
|
|
149054
|
-
...state.title ? { title: state.title } : {},
|
|
149055
|
-
...state.dir ? { dir: state.dir } : {},
|
|
149056
|
-
...state.model ? { model: state.model } : {},
|
|
149057
|
-
...state.perm ? { perm: state.perm } : {}
|
|
149058
|
-
};
|
|
149059
|
-
}
|
|
149060
|
-
function permUsageText() {
|
|
149061
|
-
const list = PERMISSION_PRESETS.map((p) => `\`${p.id}\`\uFF08${presetInfo(p.id).icon}${p.label}\uFF09`).join("\u3001");
|
|
149062
|
-
return `\u672A\u77E5\u6743\u9650\u6863\u4F4D\u3002\u53EF\u7528\uFF1A${list}\u3002
|
|
149063
|
-
\u4F8B\u5982\uFF1A\`/perm edit\`\u3002`;
|
|
149064
149234
|
}
|
|
149065
|
-
function
|
|
149066
|
-
|
|
149067
|
-
|
|
149068
|
-
|
|
149069
|
-
if (
|
|
149070
|
-
|
|
149071
|
-
|
|
149072
|
-
|
|
149073
|
-
].join("\n");
|
|
149074
|
-
}
|
|
149075
|
-
if (outcome && !outcome.verified) {
|
|
149076
|
-
return `\u2705 \u5DF2\u8BF7\u6C42\u5207\u6362\u6A21\u578B\uFF1A${ref}
|
|
149077
|
-
\u672A\u80FD\u8BFB\u56DE\u6821\u9A8C${outcome.warning ? `\uFF08${outcome.warning}\uFF09` : ""}\uFF1B\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\u3002`;
|
|
149235
|
+
function extractErrorText(error) {
|
|
149236
|
+
if (!error) return "unknown";
|
|
149237
|
+
if (typeof error === "string") return error.slice(0, 300);
|
|
149238
|
+
if (Array.isArray(error)) return contentToText(error).slice(0, 300) || "unknown";
|
|
149239
|
+
if (typeof error === "object") {
|
|
149240
|
+
const record2 = error;
|
|
149241
|
+
if (typeof record2.message === "string") return record2.message.slice(0, 300);
|
|
149242
|
+
if (typeof record2.type === "string") return record2.type;
|
|
149078
149243
|
}
|
|
149079
|
-
return
|
|
149080
|
-
\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\uFF1B\u8BE5\u4F1A\u8BDD\u6B64\u524D\u7684\u5386\u53F2\u6D88\u606F\u4ECD\u663E\u793A\u65E7\u6A21\u578B\uFF0C\u5C5E\u6B63\u5E38\u3002`;
|
|
149244
|
+
return "unknown";
|
|
149081
149245
|
}
|
|
149082
|
-
function
|
|
149083
|
-
|
|
149084
|
-
if (
|
|
149085
|
-
return
|
|
149086
|
-
|
|
149087
|
-
|
|
149088
|
-
|
|
149089
|
-
|
|
149246
|
+
function contentToText(content) {
|
|
149247
|
+
if (typeof content === "string") return content;
|
|
149248
|
+
if (Array.isArray(content)) {
|
|
149249
|
+
return content.map((item) => {
|
|
149250
|
+
if (typeof item === "string") return item;
|
|
149251
|
+
if (item && typeof item === "object" && typeof item.text === "string") {
|
|
149252
|
+
return item.text;
|
|
149253
|
+
}
|
|
149254
|
+
return "";
|
|
149255
|
+
}).filter(Boolean).join("\n");
|
|
149090
149256
|
}
|
|
149091
|
-
if (
|
|
149092
|
-
return
|
|
149093
|
-
`\u5F53\u524D\u6A21\u578B\uFF1A**${modelLabel(effective)}**`,
|
|
149094
|
-
"\uFF08\u672A\u80FD\u8BFB\u56DE\u6821\u9A8C\uFF1B\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\u3002\uFF09"
|
|
149095
|
-
]);
|
|
149257
|
+
if (content && typeof content === "object" && typeof content.text === "string") {
|
|
149258
|
+
return content.text;
|
|
149096
149259
|
}
|
|
149097
|
-
return
|
|
149098
|
-
`\u5F53\u524D\u6A21\u578B\uFF1A**${modelLabel(effective)}**`,
|
|
149099
|
-
"\uFF08\u5207\u6362\u53EA\u5F71\u54CD**\u540E\u7EED**\u56DE\u590D\uFF1B\u5386\u53F2\u6D88\u606F\u4ECD\u662F\u65E7\u6A21\u578B\uFF0C\u5C5E\u6B63\u5E38\u3002\uFF09"
|
|
149100
|
-
]);
|
|
149260
|
+
return "";
|
|
149101
149261
|
}
|
|
149102
|
-
|
|
149103
|
-
|
|
149104
|
-
|
|
149105
|
-
|
|
149106
|
-
|
|
149107
|
-
|
|
149108
|
-
|
|
149109
|
-
|
|
149110
|
-
|
|
149111
|
-
|
|
149112
|
-
|
|
149113
|
-
|
|
149114
|
-
|
|
149115
|
-
|
|
149116
|
-
|
|
149117
|
-
|
|
149118
|
-
|
|
149262
|
+
|
|
149263
|
+
// src/runtime/card-action-router.ts
|
|
149264
|
+
function routeCardAction(action, deps) {
|
|
149265
|
+
const formResponse = deps.handleForm(action);
|
|
149266
|
+
if (formResponse) return formResponse;
|
|
149267
|
+
const value3 = action.rawValue;
|
|
149268
|
+
if (parseStopActionValue(value3)) {
|
|
149269
|
+
return deps.handleStop(action);
|
|
149270
|
+
}
|
|
149271
|
+
const hasForm = action.formValue !== void 0;
|
|
149272
|
+
const routed = hasForm || isSetupFormAction(value3) || Boolean(parseSessionCardValue(value3)) || Boolean(parseSetupCardValue(value3));
|
|
149273
|
+
deps.log.debug("\u5361\u7247\u56DE\u8C03\u8DEF\u7531", {
|
|
149274
|
+
hasForm,
|
|
149275
|
+
hasValue: value3 !== void 0,
|
|
149276
|
+
valueKeys: value3 && typeof value3 === "object" ? Object.keys(value3) : [],
|
|
149277
|
+
routedTo: routed ? "commands" : "approvals"
|
|
149278
|
+
});
|
|
149279
|
+
if (routed) {
|
|
149280
|
+
return deps.handleCommands(action);
|
|
149119
149281
|
}
|
|
149120
|
-
|
|
149121
|
-
function toast3(type, content) {
|
|
149122
|
-
return { toast: { type, content } };
|
|
149123
|
-
}
|
|
149124
|
-
function clampPageSize(value3) {
|
|
149125
|
-
const base2 = typeof value3 === "number" && Number.isFinite(value3) ? Math.floor(value3) : 8;
|
|
149126
|
-
return Math.min(20, Math.max(5, base2));
|
|
149282
|
+
return deps.handleApprovals(action);
|
|
149127
149283
|
}
|
|
149128
149284
|
|
|
149129
149285
|
// src/index.ts
|
|
@@ -149528,26 +149684,13 @@ async function start(ctx, config, log2, logSink) {
|
|
|
149528
149684
|
log: log2,
|
|
149529
149685
|
logLevel: config.logLevel,
|
|
149530
149686
|
onMessage: (message) => handleMessage(message),
|
|
149531
|
-
onCardAction:
|
|
149532
|
-
|
|
149533
|
-
|
|
149534
|
-
|
|
149535
|
-
|
|
149536
|
-
|
|
149537
|
-
|
|
149538
|
-
const hasForm = action.formValue !== void 0;
|
|
149539
|
-
const routed = hasForm || isSetupFormAction(value3) || Boolean(parseSessionCardValue(value3)) || Boolean(parseSetupCardValue(value3));
|
|
149540
|
-
log2.debug("\u5361\u7247\u56DE\u8C03\u8DEF\u7531", {
|
|
149541
|
-
hasForm,
|
|
149542
|
-
hasValue: value3 !== void 0,
|
|
149543
|
-
valueKeys: value3 && typeof value3 === "object" ? Object.keys(value3) : [],
|
|
149544
|
-
routedTo: routed ? "commands" : "approvals"
|
|
149545
|
-
});
|
|
149546
|
-
if (routed) {
|
|
149547
|
-
return commands.handleCardAction(action);
|
|
149548
|
-
}
|
|
149549
|
-
return approvals.handleCardAction(action);
|
|
149550
|
-
}
|
|
149687
|
+
onCardAction: (action) => routeCardAction(action, {
|
|
149688
|
+
log: log2,
|
|
149689
|
+
handleForm: (a) => formRelay.handleCardAction(a),
|
|
149690
|
+
handleStop: (a) => stop.handleCardAction(a),
|
|
149691
|
+
handleCommands: (a) => commands.handleCardAction(a),
|
|
149692
|
+
handleApprovals: (a) => approvals.handleCardAction(a)
|
|
149693
|
+
})
|
|
149551
149694
|
});
|
|
149552
149695
|
const abort = new AbortController();
|
|
149553
149696
|
const subscription = (async () => {
|
|
@@ -149559,127 +149702,21 @@ async function start(ctx, config, log2, logSink) {
|
|
|
149559
149702
|
if (!abort.signal.aborted) log2.error("\u4E8B\u4EF6\u8BA2\u9605\u5F02\u5E38\u9000\u51FA", { error: errorMessage(err2) });
|
|
149560
149703
|
}
|
|
149561
149704
|
})();
|
|
149705
|
+
const eventDeps = {
|
|
149706
|
+
log: log2,
|
|
149707
|
+
touch: (sessionID) => executions.touch(sessionID),
|
|
149708
|
+
markStarted: (sessionID) => executions.markStarted(sessionID),
|
|
149709
|
+
markEnded: (sessionID) => executions.markEnded(sessionID),
|
|
149710
|
+
applyRun: (sessionID, event) => runs.apply(sessionID, event),
|
|
149711
|
+
onPermissionAsked: (data) => approvals.onAsked(data),
|
|
149712
|
+
onPermissionReplied: (data) => approvals.onReplied(data),
|
|
149713
|
+
onFormCreated: (data) => formRelay.onCreated(data),
|
|
149714
|
+
onFormReplied: (data) => formRelay.onReplied(data),
|
|
149715
|
+
onFormCancelled: (data) => formRelay.onCancelled(data),
|
|
149716
|
+
notifyFailure: (sessionID, error) => notifyFailure(sessionID, error)
|
|
149717
|
+
};
|
|
149562
149718
|
async function handleEvent(event) {
|
|
149563
|
-
|
|
149564
|
-
if (typeof touched === "string") executions.touch(touched);
|
|
149565
|
-
switch (event.type) {
|
|
149566
|
-
case "permission.asked":
|
|
149567
|
-
void approvals.onAsked(event.data).catch((err2) => log2.warn("\u5904\u7406 permission.asked \u5931\u8D25", { error: errorMessage(err2) }));
|
|
149568
|
-
break;
|
|
149569
|
-
case "permission.replied":
|
|
149570
|
-
approvals.onReplied(event.data);
|
|
149571
|
-
break;
|
|
149572
|
-
case "form.created":
|
|
149573
|
-
void formRelay.onCreated(event.data).catch((err2) => log2.warn("\u5904\u7406 form.created \u5931\u8D25", { error: errorMessage(err2) }));
|
|
149574
|
-
break;
|
|
149575
|
-
case "form.replied":
|
|
149576
|
-
formRelay.onReplied(event.data);
|
|
149577
|
-
break;
|
|
149578
|
-
case "form.cancelled":
|
|
149579
|
-
formRelay.onCancelled(event.data);
|
|
149580
|
-
break;
|
|
149581
|
-
case "session.text.started": {
|
|
149582
|
-
const data = event.data;
|
|
149583
|
-
runs.apply(data.sessionID, {
|
|
149584
|
-
type: "text.started",
|
|
149585
|
-
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
149586
|
-
});
|
|
149587
|
-
break;
|
|
149588
|
-
}
|
|
149589
|
-
case "session.text.delta": {
|
|
149590
|
-
const data = event.data;
|
|
149591
|
-
runs.apply(data.sessionID, {
|
|
149592
|
-
type: "text.delta",
|
|
149593
|
-
delta: data.delta,
|
|
149594
|
-
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
149595
|
-
});
|
|
149596
|
-
break;
|
|
149597
|
-
}
|
|
149598
|
-
case "session.text.ended": {
|
|
149599
|
-
const data = event.data;
|
|
149600
|
-
runs.apply(data.sessionID, {
|
|
149601
|
-
type: "text.ended",
|
|
149602
|
-
...data.text ? { text: data.text } : {},
|
|
149603
|
-
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
149604
|
-
});
|
|
149605
|
-
break;
|
|
149606
|
-
}
|
|
149607
|
-
case "session.tool.input.started": {
|
|
149608
|
-
const data = event.data;
|
|
149609
|
-
runs.apply(data.sessionID, {
|
|
149610
|
-
type: "tool.input.started",
|
|
149611
|
-
id: data.id,
|
|
149612
|
-
name: data.name,
|
|
149613
|
-
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
149614
|
-
});
|
|
149615
|
-
break;
|
|
149616
|
-
}
|
|
149617
|
-
case "session.tool.input.ended": {
|
|
149618
|
-
const data = event.data;
|
|
149619
|
-
runs.apply(data.sessionID, { type: "tool.input.ended", id: data.id, input: data.input });
|
|
149620
|
-
break;
|
|
149621
|
-
}
|
|
149622
|
-
case "session.tool.success": {
|
|
149623
|
-
const data = event.data;
|
|
149624
|
-
runs.apply(data.sessionID, {
|
|
149625
|
-
type: "tool.success",
|
|
149626
|
-
id: data.id,
|
|
149627
|
-
output: contentToText(data.content)
|
|
149628
|
-
});
|
|
149629
|
-
break;
|
|
149630
|
-
}
|
|
149631
|
-
case "session.tool.error": {
|
|
149632
|
-
const data = event.data;
|
|
149633
|
-
runs.apply(data.sessionID, {
|
|
149634
|
-
type: "tool.error",
|
|
149635
|
-
id: data.id,
|
|
149636
|
-
output: extractErrorText(data.error ?? data.content)
|
|
149637
|
-
});
|
|
149638
|
-
break;
|
|
149639
|
-
}
|
|
149640
|
-
case "session.execution.started": {
|
|
149641
|
-
const data = event.data;
|
|
149642
|
-
executions.markStarted(data.sessionID);
|
|
149643
|
-
runs.apply(data.sessionID, { type: "execution.started" });
|
|
149644
|
-
break;
|
|
149645
|
-
}
|
|
149646
|
-
case "session.execution.succeeded": {
|
|
149647
|
-
const data = event.data;
|
|
149648
|
-
executions.markEnded(data.sessionID);
|
|
149649
|
-
runs.apply(data.sessionID, { type: "execution.succeeded" });
|
|
149650
|
-
break;
|
|
149651
|
-
}
|
|
149652
|
-
case "session.execution.failed": {
|
|
149653
|
-
const data = event.data;
|
|
149654
|
-
executions.markEnded(data.sessionID);
|
|
149655
|
-
runs.apply(data.sessionID, { type: "execution.failed", error: extractErrorText(data.error) });
|
|
149656
|
-
void notifyFailure(data.sessionID, data.error);
|
|
149657
|
-
break;
|
|
149658
|
-
}
|
|
149659
|
-
case "session.execution.interrupted": {
|
|
149660
|
-
const data = event.data;
|
|
149661
|
-
executions.markEnded(data.sessionID);
|
|
149662
|
-
runs.apply(data.sessionID, { type: "execution.failed", error: `\u5DF2\u4E2D\u65AD\uFF08${data.reason ?? "unknown"}\uFF09` });
|
|
149663
|
-
break;
|
|
149664
|
-
}
|
|
149665
|
-
case "session.status": {
|
|
149666
|
-
const data = event.data;
|
|
149667
|
-
const statusType = data.status?.type;
|
|
149668
|
-
if (statusType === "idle") {
|
|
149669
|
-
executions.markEnded(data.sessionID);
|
|
149670
|
-
runs.apply(data.sessionID, { type: "execution.succeeded" });
|
|
149671
|
-
} else if (statusType === "busy" || statusType === "retry") {
|
|
149672
|
-
executions.markStarted(data.sessionID);
|
|
149673
|
-
}
|
|
149674
|
-
break;
|
|
149675
|
-
}
|
|
149676
|
-
case "session.idle": {
|
|
149677
|
-
const data = event.data;
|
|
149678
|
-
executions.markEnded(data.sessionID);
|
|
149679
|
-
runs.apply(data.sessionID, { type: "execution.succeeded" });
|
|
149680
|
-
break;
|
|
149681
|
-
}
|
|
149682
|
-
}
|
|
149719
|
+
await routeEvent(event, eventDeps);
|
|
149683
149720
|
}
|
|
149684
149721
|
async function notifyFailure(sessionID, error) {
|
|
149685
149722
|
const link2 = await sessionMap.resolveBySession(sessionID);
|
|
@@ -149828,33 +149865,6 @@ async function replyPermission(ctx, input) {
|
|
|
149828
149865
|
throw err2;
|
|
149829
149866
|
}
|
|
149830
149867
|
}
|
|
149831
|
-
function extractErrorText(error) {
|
|
149832
|
-
if (!error) return "unknown";
|
|
149833
|
-
if (typeof error === "string") return error.slice(0, 300);
|
|
149834
|
-
if (Array.isArray(error)) return contentToText(error).slice(0, 300) || "unknown";
|
|
149835
|
-
if (typeof error === "object") {
|
|
149836
|
-
const record2 = error;
|
|
149837
|
-
if (typeof record2.message === "string") return record2.message.slice(0, 300);
|
|
149838
|
-
if (typeof record2.type === "string") return record2.type;
|
|
149839
|
-
}
|
|
149840
|
-
return "unknown";
|
|
149841
|
-
}
|
|
149842
|
-
function contentToText(content) {
|
|
149843
|
-
if (typeof content === "string") return content;
|
|
149844
|
-
if (Array.isArray(content)) {
|
|
149845
|
-
return content.map((item) => {
|
|
149846
|
-
if (typeof item === "string") return item;
|
|
149847
|
-
if (item && typeof item === "object" && typeof item.text === "string") {
|
|
149848
|
-
return item.text;
|
|
149849
|
-
}
|
|
149850
|
-
return "";
|
|
149851
|
-
}).filter(Boolean).join("\n");
|
|
149852
|
-
}
|
|
149853
|
-
if (content && typeof content === "object" && typeof content.text === "string") {
|
|
149854
|
-
return content.text;
|
|
149855
|
-
}
|
|
149856
|
-
return "";
|
|
149857
|
-
}
|
|
149858
149868
|
async function promptSession(ctx, sessionID, text, delivery) {
|
|
149859
149869
|
const api = ctx.session.prompt;
|
|
149860
149870
|
await api({ sessionID, text, delivery });
|