Compare commits

...

2 Commits

Author SHA1 Message Date
hongjr03 df0b12e38b fix: hide per-run cost from Feishu replies 2026-07-11 14:11:59 +08:00
hongjr03 83ec835d4c fix: show Feishu OAuth completion page 2026-07-11 14:07:43 +08:00
6 changed files with 57 additions and 7 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@paradigm/hub",
"version": "0.0.12",
"version": "0.0.14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@paradigm/hub",
"version": "0.0.12",
"version": "0.0.14",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.202",
"@fastify/cookie": "^11.0.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@paradigm/hub",
"version": "0.0.12",
"version": "0.0.14",
"private": true,
"type": "module",
"engines": {
+32 -1
View File
@@ -273,6 +273,34 @@ export async function registerAuthRoutes(app: FastifyInstance, config: AuthRoute
return reply.status(204).send();
});
app.get("/auth/feishu/complete", async (request, reply) => {
const query = request.query as { org?: string };
const organizationName = typeof query.org === "string" && query.org.trim() !== ""
? query.org.trim()
: "当前组织";
return reply.type("text/html").send(`<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Educraft 登录成功</title>
<style>
body{font-family:system-ui,sans-serif;display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0;background:#f6f7f9;color:#1a1a1a}
.card{background:#fff;padding:2rem 2.5rem;border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.08);max-width:25rem;text-align:center}
.ok{font-size:3rem;margin:0 0 .5rem}.hint{color:#646a73;line-height:1.6}
</style>
</head>
<body>
<main class="card">
<p class="ok">✅</p>
<h1>登录并加入组织成功</h1>
<p>你已加入 ${escapeHtml(organizationName)}。</p>
<p class="hint">现在可以关闭本页面,返回飞书群再次 @机器人继续使用。</p>
</main>
</body>
</html>`);
});
app.get("/api/me", async (request, reply) => {
try {
const auth = await requireSession(request, reply, guardDeps);
@@ -401,10 +429,13 @@ async function resolvePostLoginRedirect(
revokedAt: null,
organization: { status: "ACTIVE" },
},
select: { organization: { select: { slug: true } } },
select: { organization: { select: { slug: true, name: true } } },
});
if (intended === null) return "/admin?error=not_an_active_org_member";
const orgRoot = `/admin/org/${intended.organization.slug}`;
if (returnTo === "/admin") {
return `/auth/feishu/complete?org=${encodeURIComponent(intended.organization.name)}`;
}
return returnTo === orgRoot || returnTo.startsWith(`${orgRoot}/`) ? returnTo : orgRoot;
}
if (returnTo !== "/admin" && returnTo.startsWith("/admin")) {
+1 -2
View File
@@ -35,7 +35,6 @@ import type { RuntimeSettings } from "../settings/runtime.js";
import { currentLockRunId, releaseLock } from "../lock.js";
import { createPermissionAuthorizer, type AuthorizationDecision, type PermissionAuthorizer } from "../permission.js";
import { writeAudit } from "../audit.js";
import { formatRunCostLine } from "../agent/cost.js";
import { createAgentSdkStderrSink } from "../agent/diagnostics.js";
import { InactiveOrganizationError, lockActiveOrganization } from "../org/status.js";
import { StreamingAgentCard } from "./card/streaming-card.js";
@@ -550,7 +549,7 @@ export function makeTriggerHandler(deps: TriggerDeps): TriggerHandler {
: result.status === "failed" && result.error !== undefined
? `\u5904\u7406\u5931\u8D25: ${result.error}`
: result.text;
await card.finish(finalText, { interrupted, footerText: formatRunCostLine(result.costUsd) });
await card.finish(finalText, { interrupted });
const metadataPatch = sessionMetadataPatch(result.sdkSessionId);
if (metadataPatch !== null) {
await deps.prisma.agentSession.update({
+19
View File
@@ -336,6 +336,25 @@ describe("admin auth + org API guards", () => {
},
})).resolves.toBe(1);
const defaultStart = await app.inject({ method: "GET", url: "/auth/feishu/test-default" });
const defaultAuthorize = new URL(String(defaultStart.headers.location));
const defaultState = defaultAuthorize.searchParams.get("state");
expect(defaultState).not.toBeNull();
const defaultCallback = await app.inject({
method: "GET",
url: `/auth/feishu/callback?code=ok&state=${encodeURIComponent(defaultState!)}`,
headers: { cookie: cookiePair(defaultStart.headers["set-cookie"], OAUTH_STATE_COOKIE_NAME) },
});
expect(defaultCallback.statusCode).toBe(302);
expect(defaultCallback.headers.location).toBe(
"/auth/feishu/complete?org=Test%20Default%20Organization",
);
const complete = await app.inject({ method: "GET", url: defaultCallback.headers.location! });
expect(complete.statusCode).toBe(200);
expect(complete.headers["content-type"]).toContain("text/html");
expect(complete.body).toContain("登录并加入组织成功");
expect(complete.body).toContain("Test Default Organization");
await connections.disable({ organizationId: DEFAULT_ORG_ID, actorUserId: "scoped-owner" });
const revoked = await app.inject({ method: "GET", url: "/api/me", headers: { cookie: sessionCookie } });
expect(revoked.statusCode).toBe(401);
+2 -1
View File
@@ -168,7 +168,8 @@ describe("trigger full lifecycle (integration)", () => {
msgType: "interactive",
replyInThread: undefined,
});
expect(rt.sentTexts.some((text) => text.includes("mock response") && text.includes("本次成本: $0.0023"))).toBe(true);
expect(rt.sentTexts.some((text) => text.includes("mock response"))).toBe(true);
expect(rt.sentTexts.some((text) => text.includes("本次成本"))).toBe(false);
expect(runAgentCalls).toHaveLength(1);
expect(runAgentCalls[0]?.providerProxyEnv).toMatchObject({
ANTHROPIC_BASE_URL: "http://127.0.0.1:12345",