@mhfire/dsh-im-bridge 0.3.0 → 0.4.2
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 +49 -6
- package/README.md +49 -6
- package/cordis.patch.yml +6 -0
- package/lib/client.js +126 -10
- package/lib/client.js.map +1 -1
- package/lib/index.js +8061 -12
- package/package.json +24 -9
- package/src/client/WecomCard.tsx +75 -0
- package/src/client/card-controller.ts +74 -1
- package/src/client/card-form.ts +5 -0
- package/src/client/fields.module.css +32 -0
- package/src/client/fields.tsx +3 -1
- package/src/client/index.ts +3 -1
- package/src/client/locales.ts +23 -2
- package/src/index.ts +281 -10
- package/src/session-key.ts +1 -0
- package/src/wecom-cli.ts +1015 -0
- package/tsdown.host.ts +1 -0
package/src/index.ts
CHANGED
|
@@ -31,6 +31,36 @@ import {
|
|
|
31
31
|
WecomSessionReject,
|
|
32
32
|
type WecomSessionRef,
|
|
33
33
|
} from './session-key.ts'
|
|
34
|
+
import {
|
|
35
|
+
ALLOW_FROM_REQUIRED_MESSAGE,
|
|
36
|
+
IM_BRIDGE_RPC_CHANNEL,
|
|
37
|
+
INSTALL_SKILLS_ENDPOINT,
|
|
38
|
+
SKILLS_RPC_UNAVAILABLE_MESSAGE,
|
|
39
|
+
WECOM_CLI_NO_OFFICE_PROMPT,
|
|
40
|
+
WECOM_CLI_PROMPT,
|
|
41
|
+
WECOM_CLI_TOOL_NAME,
|
|
42
|
+
WORKSPACE_WECOMCLI_LEAK_MESSAGE,
|
|
43
|
+
authInitHint,
|
|
44
|
+
countWecomcliSkills,
|
|
45
|
+
countWorkspaceWecomcliLeaks,
|
|
46
|
+
ensureConfigDir,
|
|
47
|
+
ensureOnPath,
|
|
48
|
+
installOfficialWecomSkills,
|
|
49
|
+
loadWecomSkills,
|
|
50
|
+
probeAuth,
|
|
51
|
+
registerWecomCliTool,
|
|
52
|
+
registerWecomOfficeSkills,
|
|
53
|
+
resolveConfigDir,
|
|
54
|
+
resolveSkillsDir,
|
|
55
|
+
resolveWecomBin,
|
|
56
|
+
senderHasOfficeAccess,
|
|
57
|
+
shouldEnableWecomCli,
|
|
58
|
+
shouldInjectWecomOfficeSkills,
|
|
59
|
+
skillsInstallHint,
|
|
60
|
+
trySeedAuth,
|
|
61
|
+
type InstallWecomSkillsResult,
|
|
62
|
+
type WecomSkill,
|
|
63
|
+
} from './wecom-cli.ts'
|
|
34
64
|
import {
|
|
35
65
|
DEFAULT_THINKING,
|
|
36
66
|
footerOf,
|
|
@@ -81,6 +111,25 @@ const ThinkingSchema = z.object({
|
|
|
81
111
|
outputSpin: z.array(String).default(DEFAULT_THINKING.outputSpin),
|
|
82
112
|
})
|
|
83
113
|
|
|
114
|
+
const WecomCliSchema = z.object({
|
|
115
|
+
enabled: z.boolean().default(false),
|
|
116
|
+
skillsDir: z.string().default(''),
|
|
117
|
+
configDir: z.string().default(''),
|
|
118
|
+
allowFrom: z.array(String).default([]),
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
/** Optional wecom-cli office skills (mail, calendar, docs, …). */
|
|
122
|
+
export interface WecomCliConfig {
|
|
123
|
+
/** Explicit opt-in; off by default because it shares the authorized identity. */
|
|
124
|
+
enabled: boolean
|
|
125
|
+
/** Skills root; empty = `$DSH_HOME/wecom-cli-skills`. */
|
|
126
|
+
skillsDir: string
|
|
127
|
+
/** Credential directory; empty = `<workspace>/.dsh/wecom-cli`. */
|
|
128
|
+
configDir: string
|
|
129
|
+
/** Office-command userid list; empty skips PATH / auth. Independent of chat `allowFrom`. */
|
|
130
|
+
allowFrom: string[]
|
|
131
|
+
}
|
|
132
|
+
|
|
84
133
|
/** Plugin config: secrets come from the profile patch or Settings. */
|
|
85
134
|
export interface Config {
|
|
86
135
|
botId: string
|
|
@@ -99,6 +148,7 @@ export interface Config {
|
|
|
99
148
|
thinking: ThinkingConfig
|
|
100
149
|
deniedMessage: string
|
|
101
150
|
welcomeMessage: string
|
|
151
|
+
wecomCli: WecomCliConfig
|
|
102
152
|
}
|
|
103
153
|
|
|
104
154
|
/** Schemastery schema for the composition entry and settings namespace. */
|
|
@@ -119,6 +169,7 @@ export const Config: z<Config> = z.object({
|
|
|
119
169
|
thinking: ThinkingSchema.default(DEFAULT_THINKING),
|
|
120
170
|
deniedMessage: z.string().default('无权访问本服务'),
|
|
121
171
|
welcomeMessage: z.string().default('👋 办公助手已就绪。直接发消息即可,例如查文件、整理文档、查资料或处理日常事务。'),
|
|
172
|
+
wecomCli: WecomCliSchema.default({ enabled: false, skillsDir: '', configDir: '', allowFrom: [] }),
|
|
122
173
|
})
|
|
123
174
|
|
|
124
175
|
interface LoggedEvent {
|
|
@@ -153,6 +204,7 @@ interface ChunkData {
|
|
|
153
204
|
interface LiveAgent {
|
|
154
205
|
whenIdle(): Promise<void>
|
|
155
206
|
followup(message: unknown): void
|
|
207
|
+
ctx: Context
|
|
156
208
|
session: {
|
|
157
209
|
seq: number
|
|
158
210
|
events: readonly LoggedEvent[]
|
|
@@ -164,6 +216,14 @@ interface ChatState {
|
|
|
164
216
|
agent?: LiveAgent
|
|
165
217
|
sessionId?: string
|
|
166
218
|
kind?: 'single' | 'group'
|
|
219
|
+
/** True when the current inbound sender is on `wecomCli.allowFrom`. */
|
|
220
|
+
office: boolean
|
|
221
|
+
/** Prompt sections already registered on this Agent. */
|
|
222
|
+
wecomPromptInstalled: boolean
|
|
223
|
+
/** wecomcli-* already registered on this Agent. */
|
|
224
|
+
officeSkillsRegistered: boolean
|
|
225
|
+
/** The gated `wecom_cli` tool already registered on this Agent. */
|
|
226
|
+
officeToolRegistered: boolean
|
|
167
227
|
queue: Promise<unknown>
|
|
168
228
|
lastActivity: string
|
|
169
229
|
activityClearAt: number
|
|
@@ -175,6 +235,10 @@ interface ChatState {
|
|
|
175
235
|
/** Placeholder Map value so later messages on the same key share one queue. */
|
|
176
236
|
function emptyChatState(): ChatState {
|
|
177
237
|
return {
|
|
238
|
+
office: false,
|
|
239
|
+
wecomPromptInstalled: false,
|
|
240
|
+
officeSkillsRegistered: false,
|
|
241
|
+
officeToolRegistered: false,
|
|
178
242
|
queue: Promise.resolve(),
|
|
179
243
|
lastActivity: '',
|
|
180
244
|
activityClearAt: 0,
|
|
@@ -184,6 +248,20 @@ function emptyChatState(): ChatState {
|
|
|
184
248
|
}
|
|
185
249
|
}
|
|
186
250
|
|
|
251
|
+
/** Duck-typed Connection RPC result (no apiproxy import). */
|
|
252
|
+
type SkillsRpcResult =
|
|
253
|
+
| { ok: true; value: InstallWecomSkillsResult }
|
|
254
|
+
| { ok: false; error: { code: 'internal' | 'cancelled'; message: string; details: Record<string, never> } }
|
|
255
|
+
|
|
256
|
+
/** Optional Host Connection used by the Settings install button. */
|
|
257
|
+
interface HostConnectionRpc {
|
|
258
|
+
handle(
|
|
259
|
+
channel: string,
|
|
260
|
+
handler: (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<SkillsRpcResult>,
|
|
261
|
+
options: { authority: 'loopback' },
|
|
262
|
+
): () => Promise<void>
|
|
263
|
+
}
|
|
264
|
+
|
|
187
265
|
interface DefaultModel {
|
|
188
266
|
currentSelection(): { provider: string; model: string; reasoningEffort?: string }
|
|
189
267
|
}
|
|
@@ -252,6 +330,16 @@ interface AgentPresets {
|
|
|
252
330
|
mount(agentCtx: Context, id: string): Promise<unknown>
|
|
253
331
|
}
|
|
254
332
|
|
|
333
|
+
interface SystemPromptService {
|
|
334
|
+
section(entry: { name: string; order: number; text: () => string }): unknown
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Caller-bound prompt registry on an Agent context (not a raw `get()` result). */
|
|
338
|
+
interface PromptHost {
|
|
339
|
+
get(name: string): unknown
|
|
340
|
+
systemPrompt?: SystemPromptService
|
|
341
|
+
}
|
|
342
|
+
|
|
255
343
|
interface SettingsReader {
|
|
256
344
|
get(ns: ReturnType<typeof settingsNamespace>): unknown
|
|
257
345
|
}
|
|
@@ -412,6 +500,79 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
412
500
|
})
|
|
413
501
|
const cfg = (): Config => source()
|
|
414
502
|
|
|
503
|
+
const chats = new Map<string, ChatState>()
|
|
504
|
+
let wecomCliReady = false
|
|
505
|
+
let officeSkills: WecomSkill[] = []
|
|
506
|
+
/** Launcher and credential directory the gated tool spawns with; set once wecom-cli is usable. */
|
|
507
|
+
let officeCli: { binJs: string; configDir: string } | undefined
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Install the office layer on one Agent: wecomcli-* skills and the gated
|
|
511
|
+
* `wecom_cli` tool. Both register through the Agent's own context, so a group
|
|
512
|
+
* chat or the GUI never sees them. Idempotent per Agent, and retried on each
|
|
513
|
+
* inbound message because skills can arrive from the Settings button later.
|
|
514
|
+
*/
|
|
515
|
+
function installOfficeLayer(agentCtx: Context, st: ChatState): void {
|
|
516
|
+
if (!wecomCliReady) return
|
|
517
|
+
if (!shouldInjectWecomOfficeSkills(st.kind, st.office)) return
|
|
518
|
+
if (!st.officeSkillsRegistered) {
|
|
519
|
+
const count = registerWecomOfficeSkills(agentCtx, officeSkills)
|
|
520
|
+
if (count > 0) {
|
|
521
|
+
st.officeSkillsRegistered = true
|
|
522
|
+
console.log(`[im-bridge] 已在该 Agent 注册 ${String(count)} 个 wecomcli-*`)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (st.officeToolRegistered || officeCli === undefined) return
|
|
526
|
+
try {
|
|
527
|
+
st.officeToolRegistered = registerWecomCliTool(agentCtx, officeCli.binJs, officeCli.configDir)
|
|
528
|
+
if (st.officeToolRegistered) {
|
|
529
|
+
console.log(`[im-bridge] 已在该 Agent 注册 ${WECOM_CLI_TOOL_NAME} 工具`)
|
|
530
|
+
}
|
|
531
|
+
} catch (error) {
|
|
532
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
533
|
+
console.error(`[im-bridge] 注册 ${WECOM_CLI_TOOL_NAME} 失败: ${message}`)
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
ctx.inject(['connection'], (bound) => {
|
|
538
|
+
const rpc = (bound.get('connection') as { rpc?: HostConnectionRpc } | undefined)?.rpc
|
|
539
|
+
if (rpc === undefined) {
|
|
540
|
+
console.warn(`[im-bridge] ${SKILLS_RPC_UNAVAILABLE_MESSAGE}`)
|
|
541
|
+
return
|
|
542
|
+
}
|
|
543
|
+
bound.effect(
|
|
544
|
+
() => rpc.handle(IM_BRIDGE_RPC_CHANNEL, async (endpoint, _payload, signal) => {
|
|
545
|
+
if (endpoint !== INSTALL_SKILLS_ENDPOINT) {
|
|
546
|
+
return {
|
|
547
|
+
ok: false,
|
|
548
|
+
error: { code: 'internal', message: `unknown endpoint ${endpoint}`, details: {} },
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (signal.aborted) {
|
|
552
|
+
return {
|
|
553
|
+
ok: false,
|
|
554
|
+
error: { code: 'cancelled', message: '安装已取消', details: {} },
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
const dest = resolveSkillsDir(cfg().wecomCli.skillsDir, cfg().workspace)
|
|
559
|
+
const result = await installOfficialWecomSkills(dest, { signal })
|
|
560
|
+
officeSkills = loadWecomSkills(result.dest).filter(skill => skill.name.startsWith('wecomcli-'))
|
|
561
|
+
for (const st of chats.values()) {
|
|
562
|
+
if (st.agent === undefined) continue
|
|
563
|
+
installOfficeLayer(st.agent.ctx, st)
|
|
564
|
+
}
|
|
565
|
+
console.log(`[im-bridge] 已安装 ${String(result.count)} 个 wecomcli-* 到 ${result.dest}`)
|
|
566
|
+
return { ok: true, value: result }
|
|
567
|
+
} catch (error) {
|
|
568
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
569
|
+
return { ok: false, error: { code: 'internal', message, details: {} } }
|
|
570
|
+
}
|
|
571
|
+
}, { authority: 'loopback' }),
|
|
572
|
+
'im-bridge: wecomcli.installSkills',
|
|
573
|
+
)
|
|
574
|
+
})
|
|
575
|
+
|
|
415
576
|
void (async () => {
|
|
416
577
|
const loader = ctx.get('loader') as LoaderTree | undefined
|
|
417
578
|
await loader?.await()
|
|
@@ -423,7 +584,57 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
423
584
|
return
|
|
424
585
|
}
|
|
425
586
|
|
|
426
|
-
const
|
|
587
|
+
const wecomCli = cfg().wecomCli
|
|
588
|
+
if (wecomCli.enabled) {
|
|
589
|
+
const skillsDir = resolveSkillsDir(wecomCli.skillsDir, cfg().workspace)
|
|
590
|
+
officeSkills = loadWecomSkills(skillsDir).filter(skill => skill.name.startsWith('wecomcli-'))
|
|
591
|
+
const wecomcliCount = countWecomcliSkills(officeSkills)
|
|
592
|
+
if (wecomcliCount === 0) {
|
|
593
|
+
console.warn(
|
|
594
|
+
`[im-bridge] 未找到 wecomcli-* skills(${skillsDir})。${skillsInstallHint(skillsDir)}`,
|
|
595
|
+
)
|
|
596
|
+
} else {
|
|
597
|
+
console.log(
|
|
598
|
+
`[im-bridge] 已从 ${skillsDir} 加载 ${String(wecomcliCount)} 个 wecomcli-*,将在办公单聊 Agent 上注册`,
|
|
599
|
+
)
|
|
600
|
+
}
|
|
601
|
+
const leakCount = countWorkspaceWecomcliLeaks(cfg().workspace)
|
|
602
|
+
if (leakCount > 0) {
|
|
603
|
+
console.warn(`[im-bridge] ${WORKSPACE_WECOMCLI_LEAK_MESSAGE}(${String(leakCount)})`)
|
|
604
|
+
}
|
|
605
|
+
if (!shouldEnableWecomCli(true, wecomCli.allowFrom)) {
|
|
606
|
+
console.warn(`[im-bridge] ${ALLOW_FROM_REQUIRED_MESSAGE}`)
|
|
607
|
+
} else {
|
|
608
|
+
wecomCliReady = true
|
|
609
|
+
const binJs = resolveWecomBin()
|
|
610
|
+
if (binJs === undefined) {
|
|
611
|
+
console.warn('[im-bridge] 未找到 @wecom/cli 二进制,办公命令不可用。请确认插件依赖已安装。')
|
|
612
|
+
} else {
|
|
613
|
+
const configDir = ensureConfigDir(resolveConfigDir(wecomCli.configDir, cfg().workspace))
|
|
614
|
+
officeCli = { binJs, configDir }
|
|
615
|
+
console.log(`[im-bridge] wecom-cli 凭证目录: ${configDir}`)
|
|
616
|
+
const pathResult = ensureOnPath()
|
|
617
|
+
console.log(
|
|
618
|
+
`[im-bridge] PATH 上的 wecom-cli 已改为拒绝执行: ${pathResult.shimDir}${pathResult.shadowed ? '(已遮蔽另一个 wecom-cli)' : ''}`,
|
|
619
|
+
)
|
|
620
|
+
let status = await probeAuth(binJs, configDir)
|
|
621
|
+
if (status === 'unauthorized') {
|
|
622
|
+
if ((await trySeedAuth(binJs, cfg().botId, cfg().secret, configDir)) === undefined) {
|
|
623
|
+
status = await probeAuth(binJs, configDir)
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (status === 'authorized') {
|
|
627
|
+
console.log('[im-bridge] wecom-cli 已授权')
|
|
628
|
+
} else if (status === 'unauthorized') {
|
|
629
|
+
console.warn(
|
|
630
|
+
`[im-bridge] wecom-cli 未能用 botId/secret 完成授权。请在 host 上执行 ${authInitHint(configDir)}(输入同一套密钥,不要全局安装)。`,
|
|
631
|
+
)
|
|
632
|
+
} else {
|
|
633
|
+
console.warn('[im-bridge] wecom-cli auth show 失败。')
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
427
638
|
|
|
428
639
|
/**
|
|
429
640
|
* Add the channel prefix to a title the Host generated. `session/event`
|
|
@@ -494,6 +705,52 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
494
705
|
}
|
|
495
706
|
}
|
|
496
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Register persona + wecom prompt on this Agent's layer, synchronously.
|
|
710
|
+
* Must use `agentCtx.systemPrompt` (caller-bound). `inject()` without await
|
|
711
|
+
* yields a microtask and can publish the Agent before the section exists;
|
|
712
|
+
* `adopt` never re-runs setup.
|
|
713
|
+
*/
|
|
714
|
+
function installWecomChannel(agentCtx: Context, st: ChatState): void {
|
|
715
|
+
if (st.wecomPromptInstalled) return
|
|
716
|
+
const host = agentCtx as Context & PromptHost
|
|
717
|
+
if (host.get('systemPrompt') === undefined || host.systemPrompt === undefined) {
|
|
718
|
+
console.warn('[im-bridge] 当前 Agent 没有 systemPrompt,企微提示词未注入')
|
|
719
|
+
return
|
|
720
|
+
}
|
|
721
|
+
const prompt = host.systemPrompt
|
|
722
|
+
try {
|
|
723
|
+
prompt.section({
|
|
724
|
+
name: 'deployment:persona',
|
|
725
|
+
order: 0,
|
|
726
|
+
text: () => resolvePersona(cfg(), settings),
|
|
727
|
+
})
|
|
728
|
+
} catch (error) {
|
|
729
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
730
|
+
console.warn(`[im-bridge] 人设段未覆盖(可能已由 preset 注册): ${message}`)
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
prompt.section({
|
|
734
|
+
name: 'channel:wecom-cli',
|
|
735
|
+
order: 1,
|
|
736
|
+
// Re-read per request: a group Agent is shared, and its sender —
|
|
737
|
+
// hence `st.office` — changes message to message. Only an office
|
|
738
|
+
// 1:1 gets the tool, so only it may get the office prompt.
|
|
739
|
+
// Always inject: WeCom has no GUI even when wecomCli is off.
|
|
740
|
+
text: () => wecomCliReady && shouldInjectWecomOfficeSkills(st.kind, st.office)
|
|
741
|
+
? WECOM_CLI_PROMPT
|
|
742
|
+
: WECOM_CLI_NO_OFFICE_PROMPT,
|
|
743
|
+
})
|
|
744
|
+
console.log(
|
|
745
|
+
`[im-bridge] 已注入企微提示词 office=${String(st.office)} kind=${st.kind ?? '?'}`,
|
|
746
|
+
)
|
|
747
|
+
} catch (error) {
|
|
748
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
749
|
+
console.error(`[im-bridge] 注入企微提示词失败: ${message}`)
|
|
750
|
+
}
|
|
751
|
+
st.wecomPromptInstalled = true
|
|
752
|
+
}
|
|
753
|
+
|
|
497
754
|
async function ensureAgent(ref: WecomSessionRef): Promise<ChatState> {
|
|
498
755
|
let st = chats.get(ref.key)
|
|
499
756
|
if (st === undefined) {
|
|
@@ -502,10 +759,18 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
502
759
|
}
|
|
503
760
|
st.kind = ref.kind
|
|
504
761
|
if (st.agent !== undefined) {
|
|
505
|
-
if (st.sessionId === undefined || !archivedSessions().has(st.sessionId))
|
|
762
|
+
if (st.sessionId === undefined || !archivedSessions().has(st.sessionId)) {
|
|
763
|
+
// Office access is decided per inbound sender, and skills can arrive
|
|
764
|
+
// from the Settings button after this Agent was created.
|
|
765
|
+
installOfficeLayer(st.agent.ctx, st)
|
|
766
|
+
return st
|
|
767
|
+
}
|
|
506
768
|
console.log(`[im-bridge] 会话 ${st.sessionId} 已归档,改开新会话`)
|
|
507
769
|
st.agent = undefined
|
|
508
770
|
st.sessionId = undefined
|
|
771
|
+
st.wecomPromptInstalled = false
|
|
772
|
+
st.officeSkillsRegistered = false
|
|
773
|
+
st.officeToolRegistered = false
|
|
509
774
|
}
|
|
510
775
|
|
|
511
776
|
const persistence = ctx.get('sessionPersistence') as SessionPersistence | undefined
|
|
@@ -522,6 +787,12 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
522
787
|
st.agent = agent
|
|
523
788
|
st.sessionId = sessionId
|
|
524
789
|
st.kind = ref.kind
|
|
790
|
+
if (agent.ctx === undefined) {
|
|
791
|
+
console.warn('[im-bridge] Agent 没有 ctx,无法注入企微提示词')
|
|
792
|
+
} else {
|
|
793
|
+
installWecomChannel(agent.ctx, st)
|
|
794
|
+
installOfficeLayer(agent.ctx, st)
|
|
795
|
+
}
|
|
525
796
|
void unpinLegacyWecomTitle(agent)
|
|
526
797
|
const cwd = agent.session.header?.cwd ?? stored?.cwd
|
|
527
798
|
if (cwd !== undefined && cwd !== cfg().workspace) {
|
|
@@ -552,13 +823,7 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
552
823
|
const selected = { current: selection, assembled: undefined }
|
|
553
824
|
installModelSelection(agentCtx, selected)
|
|
554
825
|
if (presets !== undefined) await presets.mount(agentCtx, resolvedId)
|
|
555
|
-
agentCtx
|
|
556
|
-
promptCtx.systemPrompt.section({
|
|
557
|
-
name: 'deployment:persona',
|
|
558
|
-
order: 0,
|
|
559
|
-
text: () => resolvePersona(cfg(), settings),
|
|
560
|
-
})
|
|
561
|
-
})
|
|
826
|
+
installWecomChannel(agentCtx, st)
|
|
562
827
|
}
|
|
563
828
|
const agentOptions = { provider: selection.provider, model: selection.model }
|
|
564
829
|
|
|
@@ -637,7 +902,13 @@ export function apply(ctx: Context, config: Config): void {
|
|
|
637
902
|
}
|
|
638
903
|
|
|
639
904
|
async function handle(frame: WecomFrame, ref: WecomSessionRef, content: string): Promise<void> {
|
|
640
|
-
|
|
905
|
+
let st = chats.get(ref.key)
|
|
906
|
+
if (st === undefined) {
|
|
907
|
+
st = emptyChatState()
|
|
908
|
+
chats.set(ref.key, st)
|
|
909
|
+
}
|
|
910
|
+
st.office = senderHasOfficeAccess(cfg().wecomCli.allowFrom, ref.sender)
|
|
911
|
+
st = await ensureAgent(ref)
|
|
641
912
|
const startedAt = Date.now()
|
|
642
913
|
const streamId = generateReqId('stream')
|
|
643
914
|
let stopThinking: (() => void) | null = null
|
package/src/session-key.ts
CHANGED
|
@@ -163,6 +163,7 @@ export function planWecomBind(key: string, state: WecomBindState): WecomBindPlan
|
|
|
163
163
|
if (state.stored.has(sessionId)) return { sessionId, bind: 'resume', epoch }
|
|
164
164
|
return { sessionId, bind: 'create', epoch }
|
|
165
165
|
}
|
|
166
|
+
|
|
166
167
|
throw new Error(`im-bridge: no free session epoch for ${key} within ${String(limit)} candidates`)
|
|
167
168
|
}
|
|
168
169
|
|