






















@@ -34,6 +34,33 @@ function makeTypingController() {
34343535function parseInlineDirectivesForTest(body: string) {
3636const normalized = body.trim();
37+if (normalized === "/reasoning stream") {
38+return {
39+cleaned: "",
40+hasThinkDirective: false,
41+hasVerboseDirective: false,
42+hasTraceDirective: false,
43+traceLevel: undefined,
44+rawTraceLevel: undefined,
45+hasFastDirective: false,
46+hasReasoningDirective: true,
47+reasoningLevel: "stream",
48+rawReasoningLevel: "stream",
49+hasElevatedDirective: false,
50+hasExecDirective: false,
51+hasModelDirective: false,
52+hasQueueDirective: false,
53+hasStatusDirective: false,
54+queueReset: false,
55+thinkLevel: undefined,
56+verboseLevel: undefined,
57+fastMode: undefined,
58+elevatedLevel: undefined,
59+rawElevatedLevel: undefined,
60+rawModelDirective: undefined,
61+execSecurity: undefined,
62+};
63+}
3764if (normalized === "/trace on") {
3865return {
3966cleaned: "",
@@ -89,7 +116,11 @@ function parseInlineDirectivesForTest(body: string) {
89116async function resolveHelloWithModelDefaults(params: {
90117defaultThinking: "off" | "low";
91118defaultReasoning: "on";
119+body?: string;
92120sessionEntry?: SessionEntry;
121+agentCfg?: { reasoningDefault?: "off" | "on" | "stream" };
122+commandAuthorized?: boolean;
123+ctx?: Parameters<typeof buildTestCtx>[0];
93124}) {
94125const resolveDefaultThinkingLevel = vi.fn(async () => params.defaultThinking);
95126const resolveDefaultReasoningLevel = vi.fn(async () => params.defaultReasoning);
@@ -105,19 +136,20 @@ async function resolveHelloWithModelDefaults(params: {
105136106137const result = await resolveReplyDirectives({
107138ctx: buildTestCtx({
108-Body: "hello",
109-CommandBody: "hello",
139+Body: params.body ?? "hello",
140+CommandBody: params.body ?? "hello",
141+ ...params.ctx,
110142}),
111143cfg: {},
112144agentId: "main",
113145agentDir: "/tmp/main-agent",
114146workspaceDir: "/tmp",
115-agentCfg: {},
147+agentCfg: params.agentCfg ?? {},
116148sessionCtx: {
117-Body: "hello",
118-BodyStripped: "hello",
119-BodyForAgent: "hello",
120-CommandBody: "hello",
149+Body: params.body ?? "hello",
150+BodyStripped: params.body ?? "hello",
151+BodyForAgent: params.body ?? "hello",
152+CommandBody: params.body ?? "hello",
121153Provider: "whatsapp",
122154} as TemplateContext,
123155sessionEntry: params.sessionEntry ?? makeSessionEntry(),
@@ -129,7 +161,7 @@ async function resolveHelloWithModelDefaults(params: {
129161isGroup: false,
130162triggerBodyNormalized: "hello",
131163resetTriggered: false,
132-commandAuthorized: false,
164+commandAuthorized: params.commandAuthorized ?? false,
133165defaultProvider: "openai",
134166defaultModel: "gpt-4o-mini",
135167aliasIndex: { byAlias: new Map(), byKey: new Map() },
@@ -169,13 +201,13 @@ vi.mock("../commands-text-routing.js", () => ({
169201}));
170202171203vi.mock("./commands-context.js", () => ({
172-buildCommandContext: vi.fn(() => ({
204+buildCommandContext: vi.fn((params: { commandAuthorized?: boolean }) => ({
173205surface: "whatsapp",
174206channel: "whatsapp",
175207channelId: "whatsapp",
176208ownerList: [],
177209senderIsOwner: false,
178-isAuthorizedSender: false,
210+isAuthorizedSender: params.commandAuthorized === true,
179211senderId: undefined,
180212abortKey: "abort-key",
181213rawBodyNormalized: "hello",
@@ -304,7 +336,7 @@ describe("resolveReplyDirectives", () => {
304336isGroup: false,
305337triggerBodyNormalized: "hello",
306338resetTriggered: false,
307-commandAuthorized: false,
339+commandAuthorized: true,
308340defaultProvider: "openai",
309341defaultModel: "gpt-4o-mini",
310342aliasIndex: { byAlias: new Map(), byKey: new Map() },
@@ -465,6 +497,122 @@ describe("resolveReplyDirectives", () => {
465497expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled();
466498});
467499500+it("does not re-enable model reasoning when agentCfg reasoningDefault is explicitly off", async () => {
501+const { result, resolveDefaultReasoningLevel } = await resolveHelloWithModelDefaults({
502+defaultThinking: "off",
503+defaultReasoning: "on",
504+agentCfg: { reasoningDefault: "off" },
505+});
506+507+expect(result).toEqual({
508+kind: "continue",
509+result: expect.objectContaining({
510+resolvedThinkLevel: "off",
511+resolvedReasoningLevel: "off",
512+}),
513+});
514+expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled();
515+});
516+517+it("does not expose configured reasoning defaults to untrusted senders", async () => {
518+const { result, resolveDefaultReasoningLevel } = await resolveHelloWithModelDefaults({
519+defaultThinking: "off",
520+defaultReasoning: "on",
521+agentCfg: { reasoningDefault: "stream" },
522+});
523+524+expect(result).toEqual({
525+kind: "continue",
526+result: expect.objectContaining({
527+resolvedReasoningLevel: "off",
528+}),
529+});
530+expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled();
531+});
532+533+it("ignores inline reasoning directives from untrusted senders", async () => {
534+const { result, resolveDefaultReasoningLevel } = await resolveHelloWithModelDefaults({
535+body: "/reasoning stream",
536+defaultThinking: "off",
537+defaultReasoning: "on",
538+});
539+540+expect(result).toEqual({
541+kind: "continue",
542+result: expect.objectContaining({
543+resolvedReasoningLevel: "off",
544+}),
545+});
546+expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled();
547+});
548+549+it("does not expose session reasoning state to untrusted senders", async () => {
550+const { result, resolveDefaultReasoningLevel } = await resolveHelloWithModelDefaults({
551+defaultThinking: "off",
552+defaultReasoning: "on",
553+sessionEntry: makeSessionEntry({ reasoningLevel: "stream" }),
554+});
555+556+expect(result).toEqual({
557+kind: "continue",
558+result: expect.objectContaining({
559+resolvedReasoningLevel: "off",
560+}),
561+});
562+expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled();
563+});
564+565+it("allows session reasoning state for authorized senders", async () => {
566+const { result, resolveDefaultReasoningLevel } = await resolveHelloWithModelDefaults({
567+defaultThinking: "off",
568+defaultReasoning: "on",
569+sessionEntry: makeSessionEntry({ reasoningLevel: "stream" }),
570+commandAuthorized: true,
571+});
572+573+expect(result).toEqual({
574+kind: "continue",
575+result: expect.objectContaining({
576+resolvedReasoningLevel: "stream",
577+}),
578+});
579+expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled();
580+});
581+582+it("allows configured reasoning defaults for operator gateway clients", async () => {
583+const { result, resolveDefaultReasoningLevel } = await resolveHelloWithModelDefaults({
584+defaultThinking: "off",
585+defaultReasoning: "on",
586+agentCfg: { reasoningDefault: "stream" },
587+ctx: { GatewayClientScopes: ["operator.admin"] },
588+});
589+590+expect(result).toEqual({
591+kind: "continue",
592+result: expect.objectContaining({
593+resolvedReasoningLevel: "stream",
594+}),
595+});
596+expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled();
597+});
598+599+it("allows configured reasoning defaults for authorized senders", async () => {
600+const { result, resolveDefaultReasoningLevel } = await resolveHelloWithModelDefaults({
601+defaultThinking: "off",
602+defaultReasoning: "on",
603+agentCfg: { reasoningDefault: "stream" },
604+commandAuthorized: true,
605+});
606+607+expect(result).toEqual({
608+kind: "continue",
609+result: expect.objectContaining({
610+resolvedReasoningLevel: "stream",
611+}),
612+});
613+expect(resolveDefaultReasoningLevel).not.toHaveBeenCalled();
614+});
615+468616it("keeps consumed text reset triggers empty after directive cleanup", async () => {
469617const sessionCtx = {
470618Body: "",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。