feat(dsh桥): withScope 在非邮件轮次回落到 /tmp 默认会话 —— 配套 15e4fe9 的收严
`reverseMap` 只在邮件投递时填 ⇒ 在 dsh 界面里直接对话时 `mailSessionOf(exec)` 为空 ⇒ withScope「取不到就原样返回」⇒ 请求不带 session_id ⇒ 服务端 403 (AgentMayReadSession 收严后,2026-09-15 的迁移期放行已作废)。 不改的后果很具体:**在 dsh 界面里读不到任何信**。 回落答案只能向服务端问 —— dsh 侧给不出 session_id: opencode 有宿主 session id 可反查(`sessionWorkspace`),pi 有 worker 闭包, 而 dsh 的 exec 里只有 dsh 自己的会话 id,与 AgentMail 会话 uuid 无映射。 这与 workspace 不同(workspace 能从目录/cwd 得到)。 装配期问一次(`loadDefaultSession`),不塞进 withScope: withScope 是取值函数,IO 混进来就变成「拼 URL 时顺带发 HTTP」, 判据也无法用裸对象驱动(同 homeagent 那次的教训)。 问不到就当没有:不带 session_id → 服务端 403(可见的错误), 而不是静默放行成越权。 判据 4 格(接线形状 + 常量一致性 + 不得伪造 id + 失败要留日志)。 三个变异各红 3 格(判据之间交叉命中,说明它们各自都在钉不同的东西)。 tsc --noEmit 通过;桥全量 429/429 绿。
This commit is contained in:
@ -381,6 +381,13 @@ interface PluginConfig {
|
||||
|
||||
export function apply(ctx: any, config: PluginConfig): void {
|
||||
const GW = config.gateway.url || 'http://127.0.0.1:8180';
|
||||
|
||||
/*
|
||||
* 默认落点必须与 handler.DefaultFallbackWorkspace 一致:
|
||||
* 不一致 = 桥问的是<E79A84><E698AF><EFBFBD>个 key、服务端认的是另一个 ⇒ 永远问不到。
|
||||
* 选 /tmp 的理由见服务端那个常量的注释(非邮件轮次没有真实工作目录)。
|
||||
*/
|
||||
const DEFAULT_FALLBACK_WORKSPACE = '/tmp';
|
||||
const AGENT_NAME = config.gateway.agentName || 'dsh';
|
||||
let AGENT_KEY = config.gateway.agentKey || '';
|
||||
const AGENT_SECRET = config.gateway.agentSecret || '';
|
||||
@ -393,9 +400,36 @@ export function apply(ctx: any, config: PluginConfig): void {
|
||||
|
||||
const client = new GatewayClient(GW, AGENT_NAME, AGENT_KEY, AGENT_SECRET);
|
||||
|
||||
/*
|
||||
* ★ 2026-10-02:装配期问一次「我的默认会话」(非邮件轮次的 session_id 出口)。
|
||||
*
|
||||
* withScope 在 `mailSessionOf(exec)` 为空时用它兜底 —— 那发生在**界面里直接
|
||||
* 对话**时(reverseMap 只在邮件投递时填)。服务端对未声明 session_id 的读信
|
||||
* 一律 403(AgentMayReadSession 收严),所以不留这个兜底就等于
|
||||
* 「在 dsh 界面里读不到信」。
|
||||
*
|
||||
* 那个端点是**纯只读**的:默认工作区一封都没通过时返回 session_id=null,
|
||||
* 不建会话。所以这里拿到空是正常形状,不是错误 —— 后果只是那次读信 403,
|
||||
* 而不是被静默放行成越权。
|
||||
*/
|
||||
let defaultSessionId = '';
|
||||
const loadDefaultSession = async (): Promise<void> => {
|
||||
try {
|
||||
const r = await client.get(`/agent/session/default?workspace=${encodeURIComponent(DEFAULT_FALLBACK_WORKSPACE)}`);
|
||||
defaultSessionId = String(r?.session_id ?? '');
|
||||
if (defaultSessionId) {
|
||||
ctx.logger.info(`[dsh-mail-bridge] 默认会话(/tmp): ${defaultSessionId}`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
// 问不到就当没有:不带 session_id,服务端会 403(可见)而不是静默放行。
|
||||
ctx.logger.error(`[dsh-mail-bridge] 取默认会话失败(本次不带 session_id): ${e?.message || e}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 注册 Agent
|
||||
(async () => {
|
||||
try {
|
||||
await loadDefaultSession();
|
||||
await client.register();
|
||||
ctx.logger.info(`[dsh-mail-bridge] 已接入 ${GW},身份 ${AGENT_NAME}`);
|
||||
} catch (e: any) {
|
||||
@ -984,7 +1018,13 @@ export function apply(ctx: any, config: PluginConfig): void {
|
||||
* 也不猜一个 —— 猜错会拼出投不到或投到别处的地址。
|
||||
*/
|
||||
function withScope(path: string, exec: any): string {
|
||||
const sid = mailSessionOf(exec);
|
||||
// ★ 2026-10-02:取不到邮件会话时回落到**默认会话**(/tmp),不再原样返回。
|
||||
//
|
||||
// 旧写法「原样返回」在 2026-09-15 之前是服务端放行的兜底(migration 期语义);
|
||||
// 那个放行已作废(AgentMayReadSession 对未声明 session_id 一律 403),
|
||||
// 而 dsh 的 `reverseMap` 只在邮件投递时才填 ⇒ 在界面里直接对话时
|
||||
// `mailSessionOf(exec)` 为空 ⇒ 读信 403。
|
||||
const sid = mailSessionOf(exec) || defaultSessionId;
|
||||
if (!sid) return path;
|
||||
const sep = path.includes('?') ? '&' : '?';
|
||||
return `${path}${sep}session_id=${encodeURIComponent(sid)}`;
|
||||
|
||||
Reference in New Issue
Block a user