Skip to content

Commit da86f21

Browse files
authored
Merge pull request #140 from ssdeanx/develop
- observation memory update
2 parents b29edbb + 3fa08c5 commit da86f21

49 files changed

Lines changed: 16704 additions & 15048 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,7 @@ src/mastra/public/workspace/swingtimer.lua
231231
.playwright-mcp/*.yml
232232
src/mastra/public/workspace/swingtimer.md
233233
src/mastra/public/workspace/tbc-shaman.md
234+
235+
*storybook.log
236+
storybook-static
237+
src/stories/AGENT.md

.storybook/main.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { StorybookConfig } from '@storybook/nextjs';
2+
3+
const config: StorybookConfig = {
4+
"stories": [
5+
"../src/**/*.mdx",
6+
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
7+
],
8+
"addons": [
9+
"@storybook/addon-a11y",
10+
"@storybook/addon-docs",
11+
"@storybook/addon-onboarding"
12+
],
13+
"framework": "@storybook/nextjs",
14+
"staticDirs": [
15+
"..\\public"
16+
]
17+
};
18+
export default config;

.storybook/preview.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Preview } from '@storybook/nextjs'
2+
3+
const preview: Preview = {
4+
parameters: {
5+
controls: {
6+
matchers: {
7+
color: /(background|color)$/i,
8+
date: /Date$/i,
9+
},
10+
},
11+
},
12+
};
13+
14+
export default preview;
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { auth } from "./src/mastra/auth";
2-
import { NextResponse } from "next/server";
1+
import { auth } from '@/src/mastra/auth'
2+
import { NextResponse } from 'next/server'
3+
4+
export const dynamic = 'force-dynamic'
35

46
export async function GET() {
5-
const configuration = await auth.api.getAgentConfiguration();
6-
return NextResponse.json(configuration);
7+
const configuration = await auth.api.getAgentConfiguration()
8+
return NextResponse.json(configuration)
79
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { oAuthDiscoveryMetadata } from 'better-auth/plugins'
2+
import { auth } from '@/src/mastra/auth'
3+
4+
5+
export const dynamic = 'force-dynamic'
6+
7+
export const GET = oAuthDiscoveryMetadata(auth)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { oAuthProtectedResourceMetadata } from 'better-auth/plugins'
2+
import { auth } from '@/src/mastra/auth'
3+
4+
5+
6+
export const dynamic = 'force-dynamic'
7+
8+
export const GET = oAuthProtectedResourceMetadata(auth)

app/api/auth/[...all]/route.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { toNextJsHandler } from 'better-auth/next-js'
2-
3-
import { auth } from '@/auth'
2+
import { auth } from '@/src/mastra/auth'
43

54
export const { GET, POST } = toNextJsHandler(auth)

app/chat/layout.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Suspense, type ReactNode } from 'react'
22

3-
import { headers } from 'next/headers'
3+
//import { headers } from 'next/headers'
44
import { redirect } from 'next/navigation'
5+
import { authClient } from '@/lib/auth-client'
56

6-
import { auth } from '@/auth'
77

88
/**
99
* Protects the entire chat subtree with a server-side session check.
@@ -16,9 +16,7 @@ async function ChatSessionGate({
1616
}: {
1717
children: ReactNode
1818
}) {
19-
const session = await auth.api.getSession({
20-
headers: await headers(),
21-
})
19+
const session = await authClient.getSession()
2220

2321
if (!session) {
2422
redirect('/login?next=/chat')
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
import { useEffect, useMemo, useState } from 'react'
5+
import { useRouter } from 'next/navigation'
6+
import { AlertCircle, CheckCircle2, Loader2, ShieldCheck } from 'lucide-react'
7+
8+
import {
9+
approveDeviceAuthorization,
10+
denyDeviceAuthorization,
11+
normalizeDeviceUserCode,
12+
} from '@/lib/auth-client'
13+
import { useAuthQuery } from '@/lib/hooks/use-auth-query'
14+
import { Button } from '@/ui/button'
15+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/ui/card'
16+
17+
/**
18+
* Interactive Better Auth device approval screen.
19+
*/
20+
export function DeviceApprovalForm({
21+
initialUserCode,
22+
}: {
23+
initialUserCode: string | null
24+
}) {
25+
const router = useRouter()
26+
const authQuery = useAuthQuery()
27+
const [isProcessing, setIsProcessing] = useState(false)
28+
const [errorMessage, setErrorMessage] = useState('')
29+
30+
const normalizedUserCode = useMemo(() => {
31+
return initialUserCode ? normalizeDeviceUserCode(initialUserCode) : ''
32+
}, [initialUserCode])
33+
34+
useEffect(() => {
35+
if (authQuery.isPending) {
36+
return
37+
}
38+
39+
if (!authQuery.data) {
40+
router.replace(`/login?next=${encodeURIComponent(`/device/approve?user_code=${normalizedUserCode}`)}`)
41+
}
42+
}, [authQuery.data, authQuery.isPending, normalizedUserCode, router])
43+
44+
const approvalSummary = normalizedUserCode ? normalizedUserCode.match(/.{1,4}/g)?.join(' ') ?? normalizedUserCode : '—'
45+
46+
const handleApprove = async () => {
47+
if (!normalizedUserCode) {
48+
setErrorMessage('Missing device code.')
49+
return
50+
}
51+
52+
setIsProcessing(true)
53+
setErrorMessage('')
54+
55+
try {
56+
const response = await approveDeviceAuthorization({ userCode: normalizedUserCode })
57+
58+
if (response.error) {
59+
setErrorMessage(response.error.error_description ?? 'Unable to approve this device request.')
60+
return
61+
}
62+
63+
router.replace('/chat')
64+
} catch (error) {
65+
setErrorMessage(error instanceof Error ? error.message : 'Unable to approve this device request.')
66+
} finally {
67+
setIsProcessing(false)
68+
}
69+
}
70+
71+
const handleDeny = async () => {
72+
if (!normalizedUserCode) {
73+
setErrorMessage('Missing device code.')
74+
return
75+
}
76+
77+
setIsProcessing(true)
78+
setErrorMessage('')
79+
80+
try {
81+
const response = await denyDeviceAuthorization({ userCode: normalizedUserCode })
82+
83+
if (response.error) {
84+
setErrorMessage(response.error.error_description ?? 'Unable to deny this device request.')
85+
return
86+
}
87+
88+
router.replace('/chat')
89+
} catch (error) {
90+
setErrorMessage(error instanceof Error ? error.message : 'Unable to deny this device request.')
91+
} finally {
92+
setIsProcessing(false)
93+
}
94+
}
95+
96+
if (authQuery.isPending || !authQuery.data) {
97+
return (
98+
<div className="flex min-h-screen items-center justify-center bg-background px-4">
99+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
100+
<Loader2 className="size-4 animate-spin" />
101+
Loading approval screen...
102+
</div>
103+
</div>
104+
)
105+
}
106+
107+
return (
108+
<div className="flex min-h-screen items-center justify-center bg-linear-to-br from-background via-background to-muted/30 px-4 py-10">
109+
<Card className="w-full max-w-xl shadow-xl shadow-black/5">
110+
<CardHeader className="space-y-3">
111+
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
112+
<ShieldCheck className="size-4" />
113+
Device approval
114+
</div>
115+
<CardTitle className="text-3xl tracking-tight">Approve this agent request?</CardTitle>
116+
<CardDescription>
117+
Confirm that the code below matches the agent you want to authorize.
118+
</CardDescription>
119+
</CardHeader>
120+
<CardContent className="space-y-5">
121+
<div className="rounded-lg border border-border/60 bg-muted/40 px-4 py-3 text-sm">
122+
<div className="text-xs uppercase tracking-[0.2em] text-muted-foreground">User code</div>
123+
<div className="mt-1 font-mono text-base font-semibold tracking-[0.28em]">
124+
{approvalSummary}
125+
</div>
126+
</div>
127+
128+
{errorMessage ? (
129+
<div className="flex items-start gap-2 rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive">
130+
<AlertCircle className="mt-0.5 size-4 shrink-0" />
131+
<span>{errorMessage}</span>
132+
</div>
133+
) : (
134+
<div className="flex items-start gap-2 rounded-lg border border-emerald-500/20 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-700 dark:text-emerald-300">
135+
<CheckCircle2 className="mt-0.5 size-4 shrink-0" />
136+
<span>You are signed in, so you can approve or reject this request.</span>
137+
</div>
138+
)}
139+
140+
<div className="flex flex-wrap gap-3">
141+
<Button
142+
type="button"
143+
onClick={handleApprove}
144+
disabled={isProcessing || !normalizedUserCode}
145+
>
146+
{isProcessing ? (
147+
<>
148+
<Loader2 className="mr-2 size-4 animate-spin" />
149+
Working
150+
</>
151+
) : (
152+
'Approve'
153+
)}
154+
</Button>
155+
<Button
156+
type="button"
157+
variant="outline"
158+
onClick={handleDeny}
159+
disabled={isProcessing || !normalizedUserCode}
160+
>
161+
Deny
162+
</Button>
163+
</div>
164+
</CardContent>
165+
</Card>
166+
</div>
167+
)
168+
}

0 commit comments

Comments
 (0)